프로그램이 예외를 처리하지 않으면 즉시 종료됩니다. 예외 처리는 예외 발생 시 대체 경로를 활성화하여 프로그램이 정상적으로 계속 작동하도록 보장하는 메커니즘입니다.
자바에서는 예외 처리를 위해 try, catch, finally 세 가지 블록을 사용합니다. 이 중 try와 catch는 반드시 함께 존재해야 하며, finally는 선택 사항이지만, 항상 실행되도록 설계되어 있습니다. try 블록 내부에는 예외가 발생할 수 있는 코드가 포함되고, catch는 해당 예외가 발생했을 때 실행되는 복구 로직이며, finally는 예외 여부에 관계없이 반드시 실행됩니다.
public class ExceptionHandlingExample { public static void main(String[] args) { try { String message = "안녕하세요"; System.out.println(message + " 누구세요?"); int value = Integer.parseInt("abc"); // 유효하지 않은 형식의 문자열 System.out.println(value); } catch (NumberFormatException e) { System.err.println("숫자 형식 오류: " + e.getMessage()); System.out.println("입력값이 올바르지 않습니다."); } finally { System.out.println("정리 작업 완료 - 항상 실행됨"); } }}
또한 자바는 사용자가 직접 예외 클래스를 정의할 수 있도록 지원합니다. 특정 조건을 만족하지 않을 때 커스텀 예외를 던질 수 있으며, 메서드 내부에서 throw를 사용해 예외를 발생시키고, 메서드 선언 시 throws를 통해 그 예외를 외부로 전달할 수 있습니다. 하나의 메서드는 단일 예외만 선언할 수 있으며, 예외가 발생하면 해당 메서드는 더 이상 실행되지 않습니다.
class AttendanceException extends Exception { public AttendanceException(String reason) { super(reason); }}public class StudentAttendance { public static void main(String[] args) { try { checkAttendance("김철수", "결석"); } catch (AttendanceException e) { System.out.println("출석 처리 실패: " + e.getMessage()); } } static void checkAttendance(String studentName, String status) throws AttendanceException { if ("결석".equals(status)) { throw new AttendanceException(studentName + "은 결석 처리되었습니다."); } }}
예외는 try-catch 구조로 포착될 수 있으며, 자바 표준 라이브러리에는 다양한 예외 유형이 존재합니다. 예를 들어, 배열 인덱스 접근 오류나 산술 오류 등이 대표적입니다.
public class NestedExceptionDemo { public static void main(String[] args) { try { try { throw new ArrayIndexOutOfBoundsException("배열 범위 초과"); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("내부 예외 처리: " + e.getMessage()); } throw new ArithmeticException("0으로 나누기 오류"); } catch (ArithmeticException e) { System.out.println("외부 예외 처리: " + e.getMessage()); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("외부 예외 처리: " + e.getMessage()); } }}
실행 결과:
내부 예외 처리: 배열 범위 초과
외부 예외 처리: 0으로 나누기 오류
내부 catch 블록이 먼저 실행된 후, 외부 예외 처리가 진행됩니다. 만약 예외가 모든 catch 블록에 의해 처리되지 않으면, 예외는 상위 호출 스택으로 계속 전파됩니다.
중첩된 try-catch-finally 구조에서도 실행 순서는 내부부터 외부까지 차례대로 수행됩니다. 각 레벨의 finally 블록은 해당 레벨의 try 또는 catch 이후에 실행됩니다.
public class DeepNestedFinally { public static void main(String[] args) { try { System.out.println("레벨 1 진입"); try { System.out.println("레벨 2 진입"); try { System.out.println("레벨 3 진입"); int result = 100 / 0; // 예외 발생 } catch (Exception e) { System.out.println("레벨 3 예외: " + e.getClass().getSimpleName()); } finally { System.out.println("레벨 3 finally 실행"); } } catch (Exception e) { System.out.println("레벨 2 예외: " + e.getClass().getSimpleName()); } finally { System.out.println("레벨 2 finally 실행"); } } catch (Exception e) { System.out.println("레벨 1 예외: " + e.getClass().getSimpleName()); } finally { System.out.println("레벨 1 finally 실행"); } }}
하지만 finally 블록이 항상 실행되는 것은 아닙니다. 다음 경우에서는 finally가 실행되지 않습니다:
System.exit(0)이 호출된 경우: JVM이 즉시 종료되므로, 이후 코드는 실행되지 않습니다.- 프로그램 내부에서 무한 루프나 디드락이 발생한 경우: 프로세스가 멈추어
finally실행이 불가능합니다. - JVM 자체가 비정상적으로 종료된 경우 (하드웨어 오류, 운영체제 오류 등)
package exceptiontest;public class ExitAndFinally { public static void main(String[] args) { try { System.out.println("메인 실행 중"); throw new RuntimeException("예외 발생"); } catch (RuntimeException e) { System.out.println("예외 메시지: " + e.getMessage()); System.exit(0); // JVM 종료 } finally { System.out.println("이 블록은 실행되지 않음"); } }}
결과: 이 블록은 실행되지 않음은 출력되지 않습니다. 이는 System.exit() 호출로 인해 프로그램이 즉시 종료되었기 때문입니다.
따라서 finally는 일반적인 상황에서는 보장되지만, 프로그램 전체의 종료가 강제되는 상황에서는 예외적으로 실행되지 않을 수 있음을 인지해야 합니다.