Information Security Study
240118 자바 예외처리(throw, 사용자 정의 예외, 자바의 예외처리 전략), 자바 API(java.lang, Object, System) 본문
240118 자바 예외처리(throw, 사용자 정의 예외, 자바의 예외처리 전략), 자바 API(java.lang, Object, System)
gayeon_ 2024. 1. 18. 15:54예외 강제 발생시키기
- 사용자가 직접 선언한 예외 클래스나 자바가 제공하는 예외 API에서 예외를 강제 발생시키려면 throw라는 키워드를 이용한다.
throw 예제
package exception.throw_;
public class ThrowExample {
public static int calcSum(int n) throws Exception{
/*
* 프로그램이 throw 구문을 만나는 순간 예외를 즉시 발생시키고
* 해당 예외를 처리해줄 catch 블록이 있는지 검색한다.
*/
if(n <= 0) { // 11번 라인 거치는 순간 예외 발생
throw new Exception(); // 예외도 클래스로 정의되기 때문에 인스턴스 생성
}
int sum = 0;
for(int i = 1; i <=n; i++) {
sum += i;
}
return sum;
}
public static void main(String[] args) {
try {
int result1 = calcSum(100);
System.out.println("1 ~ 100까지의 누적합: " + result1);
int result2 = calcSum(-100);
System.out.println("1 ~ 100까지의 누적합: " + result2);
} catch (Exception e) {
e.printStackTrace();
System.err.println("매개값을 양수로 전달해 주세요.");
}
}
}
throw 키워드를 11번 라인에 붙였기 때문에 예외가 발생한다.
throw 키워드를 붙이지 않으면 예외가 발생하지 않는다.
사용자 정의 예외
- 프로그램을 개발하다보면 자바 표준 API에서 제공하는 예외 클래스만으로 다양한 종류의 예외를 표현할 수 없다.
- 개발자가 만든 어플리케이션에서 자체적으로 생길 수 있는 예외는 개발자가 직접 예외 클래스를 정의해서 만들어야 한다.
- 사용자 정의 예외 클래스는 일반 예외로 선언할 경우 Exception 클래스를 상속하여 사용하고, 실행 예외로 선언할 경우 RuntimeException 클래스를 상속하면 된다.
- 사용자 정의 예외 클래스의 이름은 Exception으로 끝나는 것이 좋다.
사용자 정의 예외 예제 (잔고 불충분 예외)
package exception.custom;
// 잔고 불충분 예외
// 사용자 정의 예외 클래스를 만드려면 Exception 클래스나 RuntimeExcepton 클래스를 상속한다.
public class BalanceInsufficientException extends RuntimeException {
// 일반적으로 사용자 정의 예외 클래스를 만들 때는
// 기본 생성자와 예외원인 메시지를 받는 생성자를
// 두 개 오버로딩해서 선언만 한다.
public BalanceInsufficientException() {}
public BalanceInsufficientException(String message) {
super(message);
}
}
// 음수 입금 불가 예외
package exception.custom;
public class DepositMinusMoneyException extends RuntimeException {
public DepositMinusMoneyException() {}
public DepositMinusMoneyException(String message) {
super(message);
}
}
// 잔고 클래스
// 입금하려는 돈이 음수거나 출금하려는 돈이 잔고보다 많으면 예외 발생
package exception.custom;
public class Account {
private long balance;
public long getBalance() {
return this.balance;
}
public void deposit(int money) throws DepositMinusMoneyException {
if(money < 0) {
throw new DepositMinusMoneyException("음수로 입금할 수 없습니다.");
}
this.balance += money;
}
public void withdraw(int money) throws BalanceInsufficientException {
if(this.balance < money) {
throw new BalanceInsufficientException("잔고가 부족합니다.");
}
this.balance -= money;
}
}
package exception.custom;
public class MainClass {
public static void main(String[] args) {
Account acc = new Account();
try {
acc.deposit(100000);
System.out.println("입금 후 잔액: " + acc.getBalance() + "원");
acc.withdraw(1000000);
} catch(BalanceInsufficientException e) {
// 예외 클래스가 제공하는 getMessage() 메서드는 예외의 원인 메세지를
// String 타입으로 리턴한다. 자바 표준 API에서 제공하는 다양한 예외 클래스들은
// 각각의 예외 원인 메세지가 기본적으로 객체 안에 저장되어 있다.
e.printStackTrace();
// 생성자에서 제공해준 메세지를 그대로 출력
System.err.println(e.getMessage());
} catch (DepositMinusMoneyException e) {
e.printStackTrace();
System.err.println(e.getMessage());
}
System.out.println("출금 후 잔액: " + acc.getBalance() + "원");
}
}
생성자에서 제공해준 메세지를 그대로 출력하도록 작성했다.
음수를 입금했을 때의 실행 결과
사용자 정의 예외를 사용하는 이유
- 그냥 Exception만 쓰는 것에 비해 사용자 정의 예외를 사용하면 클래스명부터 어떤 종류의 예외인지 정확하게 알 수 있어 유지, 보수하기 좋다.
부록 : 자바의 예외 처리 전략
'예외를 처리하는 방법 (예외 복구, 예외 처리 회피, 예외 전환)'
public void exceptionTest() {
try {
// 예외가 발생할 가능성이 있는 시도
...
} catch (SomeException e) {
// 다른 흐름으로 유도하여 정상적으로 작업이 진행되도록
...
}
}
'예외 복구'
예외가 발생했을 때 다른 작업 흐름으로 유도하는 예외 복구다. (try - catch - finally)
예외 복구의 핵심은 예외가 발행하여도 어플리케이션은 정상적인 흐름으로 진행된다. 예외 발생 시 재시도를 통해 정상적인 흐름을 타게 한다거나, 예외가 발생하면 이를 미리 예측하여 다른 흐름으로 유도시키도록 구현하
면 비록 예외가 발생하더라도 정상적으로 작업을 종료할 수 있다.
// 일반적으로 예외를 던지는 경우public void exceptionTest() throws SomeException {
}
// 어느정도 처리하고 예외를 던지는 경우public void exceptionTest() throws SomeException {
try {
...
} catch (SomeException e) {
// 예외 처리의 필요성이 있을 때 어느정도 처리하고 던지는 경우throw e;
}
}
'예외 처리 회피'
간단해보이지만 신중해야 하는 로직이다. 예외 처리를 직접 담당하지 않고 호출한 쪽으로 던져 회피하는 방법이다.
예외 처리의 필요성이 있다면 두 번째 예시처럼 어느 정도 처리하고 던질 수도 있다. 예외 처리 회피에서는 무책임하게 상위 메서드로 throw를 던지는 행위는 상위 메서드의 책임이 그만큼 증가하기 때문에 적절한 경우에만 사용하는
것이 좋다.
public void exceptionTest() {
try {
...
} catch (SQLException e) {
// 더 명확하게 인지할 수 있도록 다른 예외로 전환해서 던지는 방법
throw new DuplicationUserIdException(message);
}
}
'예외 전환'
예외를 잡아서 다른 적절한 예외로 전환하여 자신을 호출한 메서드로 던져버리는 방법이다.
호출한 쪽에서 예외를 받아서 처리할 때 좀 더 명확하게 인지할 수 있도록 돕기 위한 방법이다.
어쩔수없이 api에서 가져다 쓰는 기능(Thread.sleep() 등…)은 checked exception이 강제되는 경우가 있는데
이 경우, 어떤 로직을 호출하거나 실행하려 하는지 명시가 되어버리는 문제가 있다.
try ~ catch 를 쓰되, catch 블럭에서 다른 runtimeExcpetion을 throw 해버린다.
이러면 예외를 유발했던 요소 대신 catch블럭에서 일으킨 예외가 대신 콘솔에 찍힌다.
즉, throw되는 예외를 바꿔서 어떤 로직이 예외를 유발했는지 정확하게 기능단위로 추론하기 어렵게 만들어서
캡슐화를 유지할 수 있게 된다.
checked exception을 unchecked exception으로 전환시키는 것이 좋다고 최근 자바 개발자들끼리
결론을 내렸다.
자바 API 등
JAVA API(Application Programming Interface)
- API는 라이브러리라고 부르며 프로그램 개발에 자주 사용되는 클래스 및 인터페이스의 모음을 말한다.
- 자바 표준 API 문서 주소: https://docs.oracle.com/javase/8/docs/api
java.lang 패키지
- java.lang 패키지는 자바 프로그램의 기본적인 클래스들을 담고 있는 패키지다.
- java.lang 패키지에 있는 클래스와 인터페이스는 import 구문 없이 사용할 수 있다.
java.lang 패키지 주요 클래스
- Object: 자바 클래스의 최상위 클래스
- System: 표준 입력장치(키보드)로부터 데이터를 입력받거나 표준 출력장치(모니터)로 출력하기 위해 사용
- Class: 클래스를 메모리에 로딩할 때 사용
- String: 문자열을 저장하고 문자열의 여러 가지 정보를 얻을 때 사용
- StringBuffer, StringBuilder: 문자열을 저장하고 내부 문자열을 조작할 때 사용
- Math: 수학 함수를 이용할 때 사용
- Wrapper(Byte, Short, Integer, Long, Float, Double, Boolean, Character) : 기본 데이터 타입의 객체를 만들 때 사용
Object 클래스
- 클래스를 선언할 때 extends 키워드로 다른 클래스를 상속하지 않으면 묵시적으로 Object 클래스를 상속한다.
- 따라서 모든 자바의 클래스는 Object 클래스의 자식이거나 자손클래스다.
- 모든 클래스가 Object를 상속하기 때문에 Object의 메서드는 모든 클래스에서 사용 가능하다.
예제
package api.lang.object;
public class ObjectInformaion {
// toString 오버라이딩 후
// System.out.println() 등으로 객체변수 조회 시
// 해당 인스턴스의 클래스 경로와 주소값 대신
// toString에서 리턴한 문자가 콘솔에 찍힌다.
@Override
public String toString() {
return "조회 시 출력 문자";
}
}
package api.lang.object;
public class MainClass {
public static void main(String[] args) {
ObjectInformaion oi = new ObjectInformaion();
System.out.println(oi);
}
}
실행 시 아래와 같이 오버라이딩된 toString()이 적용된다.
System 클래스
- 자바 프로그램은 운영체제상에서 바로 실행되는 것이 아니라 JVM 위에서 실행된다. 따라서 운영체제의 모든 기능을 자바 코드로 직접 접근하기는 어렵다.
- 하지만 System 클래스를 이용하면 운영체제의 일부 기능을 이용할 수 있다. 프로그램 종료, 키보드로 입력, 모니터로 출력, 메모리 정리, 현재 시간 읽기 등이 가능하다.
- System 클래스의 모든 멤버는 static으로 구성되어 있어 클래스 이름으로 바로 접근이 가능하다.
System 클래스 주요 메서드
- exit(): 현재 실행하고 있는 프로세스를 강제 종료시킨다. 정상 종료일경우 매개값으로 0을 주고, 비정상 종료인경우 0 이외에 다른 값을 준다.
- currentTimeMillis() : 컴퓨터의 시계로부터 현재 시간을 읽어서 밀리세컨드(1/1000초) 단위와 나노세컨드(1/10^9초)단위의 long값을 리턴한다. 주로 프로그램의 실행 소요 시간 측정으로 성능을 테스트할 때 사용한다.
- getProperty(): JVM이 시작할 때 자동 설정되는 시스템의 속성값을 구한다.
- gc(): Garbage Collector를 실행시킨다.
예제
package api.lang.system;
public class SystemTimeExample {
public static void main(String[] args) {
/*
* currentTimeMillis()와 nanoTime() 메서드는 UNIX 시간을 사용한다.
* UNIX시간이란 1970년도 1월 1일 00시 00분 00초를 기점으로
* 얼마나 시간이 지났는지를 숫자로 표현하는 체계이다.
* 현재까지의 시간을 1000분의 1초로 변환한 에폭시간과
* 현재까지의 시간을 10억분의 1초로 변환한 에폭시간을 long타입으로 리턴한다.
*/
long start = System.currentTimeMillis();
System.out.println("시작 시간: " + start);
long sum = 0;
for(int i = 1; i < 1000000000L; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("종료 시간: " + end);
System.out.println("계산에 소요된 시간: " + (end - start));
}
}
실행 결과