Exception handling in Java

김민욱·2025년 12월 9일

Exception

프로그램에서 예외(Exception)는 실행 중 발생하는 예상치 못한 비정상적인 상황이나 오류를 칭한다.
Java에서는 예외 역시 객체로 취급한다. 따라서 클래스로 정의한다. Exception의 루트 클래스는 java.lang.Throwable이다.

Object <- Throwable <-+- Exception <-+--IOException
					  |		         |
                      |              .
                      |              .
                      |
                      +- Error <-+------LinkageError
                                 |
                                 .
                                 .

Error, Exception 등 이름 때문에 헷갈릴 수 있지만 모두 런타임에 발생하는 예외다.

Throwable 클래스는 예외 클래스의 루트다. 모든 Java 예외 클래스는 Throwable을 직간접적으로 상속한다.
Exception 클래스나 그 subclass를 상속함으로써 커스텀 예외 클래스도 만들 수 있다.

예외 클래스는 크게 3가지 타입으로 나뉜다.

  • system errors
  • exceptions
  • runtime exceptions

System errors
JVM에 의해 던져지고 Error 클래스에서 다뤄진다. Error 클래스는 내부 시스템 오류를 묘사하지만 이러한 오류는 거의 발생하지 않는다. 만약 발생한다면 사용자에게 알림을 보내고 프로그램을 살포시 종료하는 것 외에 우리가 할 수 있는 것은 없다.

Error의 subclass 예시

  • LinkageError: 의존성을 가지는 이전 클래스와 호환이 안됨.
  • VirtualMachineError: JVM이 망가졌거나 작업을 계속하는데 필요한 리소스가 망가짐.

Exceptions
Exception 클래스에서 우리가 작성한 프로그램이나 외부 환경에 의해 발생하는 오류를 다룬다. 이런 오류들은 당신의 프로그램에게 잡혀서 핸들링 된다.

Exception의 subclass 예시

  • ClassNotFoundException: 존재하지 않는 클래스를 사용하려 함.
  • IOException: 입출력이 잘못됨.

Runtime exceptions
RuntimeException 클래스에서 잘못된 형변환, array 범위를 벗어남, 수치 오류 등 프로그램 실행 중 발생하는 오류를 다룬다. 일반적으로 JVM에 의해 던져진다.

RuntimeException의 subclass 예시

  • ArithmeticException: Divided by zero. (부동소수점 산술연산은 예외를 던지지 않음)
  • NullPointerException: null을 참조하는 변수에 접근.
  • IndexOutOfBoundsException: 인덱스 범위를 넘어서 접근.
  • IllegalArgumentException: 메소드에 부적절한 인자가 넘어감.

Exception handling

Java의 예외 처리(Exception handling)은 다음 세 단계를 거친다.

  1. 예외 선언 (Declaring exceptions)
  2. 예외 던짐 (Throwing exceptions)
  3. 예외 받고 처리함 (Catching and processing exceptions)

예외를 선언하기 위해서는 throws 키워드를 메소드의 헤더에 사용할 수 있다.

public void myMethod() throws IOException { ... }

throws 키워드는 myMethodIOException을 던질 수 있음을 나타낸다. 만약 메소드가 다수의 예외를 던질 수 있다면 콤마(,)로 구분된 예외 리스트를 추가한다.

public void myMethod() throws Exception1, Exception2, ..., ExceptionN

Throwing Exceptions
오류를 감지한 프로그램은 적절한 예외 인스턴스를 생성해서 던질 수 있다. 이 것이 throwing an exception이라고 알려진 동작이다.
예를 들어, 프로그램이 메소드 정의와 맞지 않는 잘못된 인자가 들어왔음을 감지했다면 IllegalArgumentException 인스턴스를 생성해서 던진다.

IllegalArgumentException ex = new IllegalArgumentException("Wrong Argument");
throw ex;

또는

throw new IllegalArgumentException("Wrong Argument");

Catching Exceptions
던져진 예외는 try-catch 블럭에서 핸들링 된다.

try {
  statements; // 예외 발생 가능성이 있는 구문
} catch (Exception1 ex1) {
  handler for ex1;
} catch (Exception2 ex2) {
  handler for ex2;
}
...
} catch (ExceptionN exN) {
  handler for exN;
}

우리는 다음과 같이 메소드가 중첩되어 호출되는 시나리오를 가정해볼 것이다.

  1. main 메소드에서 m1을 호출
  2. m1에서 m2 호출
  3. m2에서 m3 호출
  4. m3에서 예외 던짐
public static void main(String[] args) {
  try {
    m1();
    statement1;
  } catch (Exception1 ex1) {
  	// Process ex1...
  }
  statement2;
}

public void m1() {
  try {
    m2();
    statement3;
  } catch (Exception2 ex2) {
    // Process ex2...
  }
  statement4;
}

public void m2() {
  try {
    m3(); // 예외 발생!
    statement5;
  } catch (Exception3 ex3) {
    // Process ex3...
  }
  statement6;
}

만약 발생한 예외 타입이 Exception3라면 m2catch 블럭에 잡혀서 핸들링 될 것이다. statement5는 생략되고 바로 statement6으로 넘어간다.

발생한 예외 타입이 Exception2라면 m2는 중단되고 제어는 m1으로 반환된다. 예외는 m1catch 블럭에서 처리되고 statement4를 실행한다.

발생한 예외 타입이 Exception1이라면 m1은 중단되고 제어는 main으로 넘어간다. 예외는 maincatch 블럭에서 처리되고 statement2를 실행한다.

만약 발생한 예외 타입이 m2, m1, main에서 잡을 수 없는 타입이면 프로그램은 강제종료되고 statement1statement2는 실행되지 않는다.

예외에서 정보 얻기

Throwable 클래스는 다음과 같은 메소드를 제공한다.

  • getMessage()
    발생한 예외 객체의 메시지를 반환한다.
  • toString()
    {예외 클래스 풀네임}: {getMessage()}로 구성된 문자열을 반환한다.
  • printStackTrace()
    Throwable 객체와 호출 스택 추적 정보를 콘솔에 출력한다.
  • getStackTrace()
    이 예외와 관련된 스택 추적 요소들의 배열을 반환한다.

finally clause
finally 절은 예외 발생 여부와 관계 없이 항상 실행된다.

try {
  statements;
} catch (Exception ex) {
  handling ex;
} finally {
  finalStatements;
}

언제 예외 처리를 하는가

우리가 예상 못할 오류가 발생할 수 있는 조건을 다뤄야할 때 에러 처리가 필요하다. 하지만, 단순한 조건 검사에는 try-catch 블럭을 사용하지 않는 것을 권장한다.
(try-catch도 오버헤드임)

Rethrowing Exceptions
Java는 핸들러가 예외를 처리하지 못하거나 단순히 호출자에게 예외를 알리고 싶을 때 예외를 다시 던지는 것을 허용한다.

try {
  statements;
} catch (Exception ex) {
  perform operation before exits;
  throw ex;
}

Chained Exceptions

public class Main {
  public static void main(String[] args) {
    try {
      method1();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  public static void method1() throws Exception {
    try {
      method2();
    } catch (Exception ex) {
      throw new Exception("New info from method1 ", ex);
    }
  }

  public static void method2() throws Exception {
    throw new Exception("New info from method2");
  }
}
java.lang.Exception: New info from method1 
	at Main.method1(Main.java:26)
	at Main.main(Main.java:16)
Caused by: java.lang.Exception: New info from method2 
	at Main.method2(Main.java:31)
	at Main.method1(Main.java:24)
	... 1 more

Custom Exception Class

// MyException.java
public class MyException extends Exception {
  public MyException() {
    super("I made my own Exception");
  }
}
// Main.java
public class Main {

  static void callDriver() throws ClassNotFoundException {
    // 예외 발생을 위한 존재하지 않는 클래스 찾기
    Class.forName("oracle.jdbc.driver.OracleDriver");
    System.out.println("Finished"); // 실행되지 않을 것.
  }

  static void callException() throws MyException {
    // Do something wrong, throw exception.
    throw new MyException();
  }

  public static void main(String[] args) {
    try {
      callDriver(); 
    } catch (ClassNotFoundException e) {
      // 핸들링 예시
      System.out.println("Cannot find Class");
    }

    try {
      callException();
    } catch (MyException e) {
      // Throwable 객체의 메소드를 활용한 방법
      System.out.println(e.getMessage());
    }
  }
}
Cannot find Class
I made my own Exception

<참고자료>
탁성우, "플랫폼기반프로그래밍", 부산대학교

0개의 댓글