Java의 예외 클래스는 계층 구조를 통해 다양한 종류의 예외를 체계적으로 관리할 수 있으며, 개발자는 필요한 예외를 선택하여 적절히 처리할 수 있다.
모든 예외 클래스는 java.lang.Throwable 클래스를 상속받는다.
Error 클래스와 Exception 클래스는 Throwable 클래스를 상속받는다.
Error 클래스 : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
Exception 클래스 : 프로그램에서 코드에 의해서 수습될 수 있는 미약한 오류
try-catch-finally
예외 처리를 위한 구문으로, 각 블록은 다음과 같은 역할을 한다.
try: 예외가 발생할 수 있는 코드
catch: 예외가 발생했을 때 수행할 코드
프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지
finally: 예외 발생 여부와 상관없이 항상 수행되어야 하는 코드
보통 자원 해제 코드 작성
class Example {
public static void main(String[] args) {
try {
// 예외가 발생할 수 있는 코드
} catch (Exception1 e1) {
// Exception1 예외가 발생했을 때 처리하는 코드
} catch (Exception2 e2) {
// Exception2 예외가 발생했을 때 처리하는 코드
} catch (Exception3 e3) {
// Exception3 예외가 발생했을 때 처리하는 코드
} catch (ExceptionA | ExceptionB e5) {
// ExceptionA, ExceptionB 예외가 발생했을 때 처리하는 코드
} finally {
// 예외 발생 여부와 상관없이 항상 수행되어야 하는 코드
}
// 발생한 예외와 일치하는 catch 블록이 없으면 예외는 처리되지 않는다.
}
}
예외 발생 정보
printStackTrace() : 예외 발생 당시 호출 스택(Call Stack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력
getMessage() : 발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻어옴
class Example {
public static void main(String[] args) {
try {
int result = 3 / 0;
System.out.println(result);
} catch (ArithmeticException e) {
e.printStackTrace(); // java.lang.ArithmeticException: / by zero, at Example.main(Example.java:5)
System.out.println(e.getMessage()); // / by zero
}
}
}
메서드에 예외 선언
메서드 선언부에 예외를 선언함으로써 메서드를 사용하려는 사람에게 이에 대한 처리를 강요할 수 있으며 그와 동시에 어떤 예외가 발생할 수 있는지 알려줄 수 있다.
class Exception {
public static void main(String[] args) {
try {
File f = createFile(args[0]);
System.out.println(f.getName() + "파일이 성공적으로 생성되었습니다.");
} catch (Exception e) {
System.out.println(e.getMessage() + " 다시 입력해 주시기 바랍니다.");
}
}
static File createFile(String fileName) throws Exception {
if (fileName == null || fileName.equals("")) {
throw new Exception("파일이름이 유효하지 않습니다.");
}
File f = new File(fileName);
f.createNewFile();
return f;
}
}
Unchecked Exception
컴파일러가 예외 처리를 강제하지 않는 예외
RuntimeException 클래스를 상속받은 예외들은 unchecked exception인데, 이 예외는 별도로 예외 처리를 강제하지 않는다.
class Example {
public static void main(String[] args) {
try {
int[] arr = new int[3];
arr[3] = 10;
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("배열의 인덱스가 범위를 벗어났습니다.");
}
}
}
위 예외는 unchecked exception이기 때문에 try-catch나 메서드에 throws절을 사용하지 않아도 컴파일 에러가 발생하지 않는다.
코드가 복잡해지고 가독성이 떨어질 수 있는 것을 방지할 수 있고, 호출한 메서드에 불필요하게 에러 처리를 강요하지 않게 할 수 있다.
사용자정의 예외
보통 Exception 혹은 RuntimeException 클래스를 상속받아 사용자 정의 클래스를 만들며, 필요에 따라 알맞는 클래스를 선택하면 된다.
class MyException extends Exception {
private final int ERR_CODE;
MyException(String msg, int errCode) {
super(msg);
ERR_CODE = errCode;
}
MyException(String msg) {
this(msg, 100);
}
public int getErrorCode() {
return ERR_CODE;
}
}
위 예시처럼 Exception 클래스를 상속받는 대신 RuntimeException 클래스를 상속 받으면 예외 처리를 강제하지 않을 수 있다.
예외 되던지기
예외를 처리한 후에 인위적으로 다시 예외를 발생시켜, 상위 호출 메서드로 예외를 전달하는 방법이다.
class ExceptionEx3 {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
System.out.println("main 메서드에서 예외가 처리되었습니다.");
}
}
static void method1() throws Exception { // method1 에서 발생한 예외를 main 메서드로 넘겨준다.
try {
throw new Exception();
} catch (Exception e) {
System.out.println("method1 메서드에서 예외가 처리되었습니다.");
throw e; // method1 에서 발생한 예외를 다시 발생시킨다.
}
}
}