[Java] 예외 (Exception)

이상현·2023년 10월 14일
0

Java

목록 보기
6/21
post-thumbnail

오류

프로그램 실행 중 오류에는 여러가지 종류가 있다.

  • 컴파일 에러: 컴파일 할때 발생하는 오류
  • 런타임 에러: 프로그램의 실행 도중 발생하는 오류
  • 논리적 에러: 컴파일도 실행도 잘 되지만 의도와 다르게 동작할 때

자바에서는 이 오류들을 에러(Error)와 예외(Exception)로 구분한다.

  • 에러(Error): 발생하면 복구할 수 없는 심각한 것을
  • 예외(Exception): 발생하더라도 수습할 수 있는 것을 예외라고 한다.

프로그래머는 예외 처리 코드를 작성함으로써 프로그램에 예외가 발생했을 때 이를 처리하여 비정상적인 프로그램 종류를 방지할 수 있다.

예외 클래스의 계층구조

예외 클래스의 계층구조

모든 예외의 조상은 Exception 클래스이다.

예외 클래스들은 다음과 같이 두개의 그룹으로 나누어 질 수 있다.

A. RuntimeException 클래스와 그 자손 클래스들
B. A를 제외한 Exception 클래스와 그 자손 클래스들

A (RuntimeException 클래스) 는 주로 프로그래머의 실수에 의해서 발생될 수 있는 예외들이다.
ex) 인덱스 배열 범위 벗어남, null인 참조변수의 멤버 호출, 클래스 형변환 잘못함, 정수를 0으로 나눔 등

B (Exception 클래스) 는 외부의 영향으로 발생할 수 있는 예외들로서, 프로그램을 사용하는 사용자들의 동작에 의해서 발생하는 경우가 많다.
ex) 파일 제목 잘못 입력, 클래스 이름 실수, 입력 데이터 형식 오류 등

예외 처리하기 (try-catch)

예외 처리란, 프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하여 프로그램의 비정상 종료를 방지하고 정상적인 실행상태를 유지할 수록 있는 것이다.

이를 위해 try-catch 문을 사용하고 구조는 다음과 같다.

try {
	// 예외 발생 가능성 있는 문장을 작성한다.
} catch (Exception1 e1) {
	// Exception1이 발생했을 경우 처리하는 문장을 작성한다.
} catch (Exception2 e2) {
	// Exception2이 발생했을 경우 처리하는 문장을 작성한다.
} ...

하나의 try 블럭 다음에는 여러 종류의 예외를 처리할 수 있는 여러 catch 블럭이 올 수 있다. 이 중에 예외와 일치하는 단 하나의 catch 블럭만 수행딘다. 일치하는 예외가 없으면 예외는 처리되지 않는다.

try 또는 catch 블럭 내에서도 예외가 발생할 수 있기 떄문에 try 또는 catch 블럭 안에도 try 문을 적어서 중첩할 수 있다.

public static void main(String[] args) {
    int number = 100;
    int result;

    for(int i = 0; i < 10; i++) {
        try {
            result = number / (int) (Math.random() * 10);
            System.out.println(result);
        } catch (Exception e) {
            System.out.println("0 으로 나누는 예외 발생");
        }
    }
}

위 코드에서는 예외가 발생한다면 ArithmeticException 예외가 발생하는데, 이것은 Exception 클래스의 자손이므로 instanceof 연산결과가 true가 되어 Exception e 에서 예외가 잡혀서 catch 블럭이 수행된다.

예외 정보 얻기

예외 클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨져 있다.
이를 얻을 수 있는 메소드는 다음과 같다.

  • getMessage()
    발생한 예외클래스의 인스턴스에 저장된 메세지를 얻을 수 있다.
  • printStackTrace()
    예외 발생 당시의 Call stack에 있었던 메서드의 정보와 예외 메세지를 출력한다. 예외가 발생하여 비정상적으로 종료되었을때의 메세지와 비슷하지만, 예외를 처리하여 프로그램을 정상적으로 작동하게 한다.

이 두 메소드는 catch 블럭 내에서만 사용 가능하다.

...
catch (ExceptionA | ExceptionB e) {
	e.printStackTrace();
	System.out.println("예외메세지: " + e.getMessage());
}

참고: | 기호를 사용하여 여러 예외를 하나의 catch블럭으로 다룰 수 있다.

예외 발생시키기

키워드 throw 를 사용해서 고의로 예외를 발생시킬 수 있다.

throw new Exception();

이 때, Exception 생성자에 String을 넣으면, 그것이 Exception 인스턴스에 메세지로 저장된다. -> getMessage로 얻을 수 있다.

위에서 설명한 A 그룹 (RuntimeException 클래스와 그 자손 클래스들) 을 고의로 발생시키는 코드가 포함되어도 컴파일이 정상적으로 작동한다.

하지만 B 그룹 (A를 제외한 Exception 클래스와 그 자손 클래스들) 은 고의로 발생하고 예외 처리 (try-catch)를 해주지 않으면 컴파일 자체가 안된다.

A 그룹은 프로그래머의 실수로 발생하는 것들이기 때문에 예외처리를 강제하지는 않는 것이다.
그래서 A 그룹은 unchecked 예외 라고 부르고,
예외처리를 확인하는 B그룹은 checked 예외 라고 부른다.

메서드에 예외 선언

void method() throws Exception1, Exception2 {
	// 메서드 내용
}

다음과 같이 메서드에 예외를 선언할 수 있는데, 이것은 이 메소드가 해당 예외들을 발생시킬 수도 있다고 표시해주는 것이다.

일반적으로 RuntimeException (unchecked 예외)는 꼭 처리할 필요는 없으므로 메소드 선언부에 작성하지 않는다.

해당 메소드에서 예외를 처리하지 않고, 자신을 호출한 메소드에서 예외 처리를 떠넘겨서 맡길 때 작성하면 된다. (예외 전달)

  1. 예외가 발생한 메서드에서 바로 예외 처리

    public void a() {
        method1();
    }
    
    static void method1() {
        try {
            throw new Exception();
        } catch (Exception e) {
            System.out.println("method1 에서 예외 처리됨.");
            e.printStackTrace();
        }
    }
  2. 상위 메서드에서 예외 처리

    public void a() {
        try {
            method1();
        } catch (Exception e) {
            System.out.println("main 에서 예외 처리됨.");
            e.printStackTrace();
        }
    }
    
    static void method1() throws Exception {
        throw new Exception();
    }

이 두 가지의 차이를 보자. 1번에서는 예외가 발생해도 method1 내에서 처리됐기 때문에 a 메소드는 예외가 발생했다는 사실도차도 모르게 된다.

하지만 2번에서는 예외가 발생한 메소드에서 예외를 처리하지 않고 호출한 메서드로 넘겨줬기 때문에 a 메소드는 method1()이 호출된 부분에서 예외가 발생했다고 판단한다.

이처럼 예외 처리 부분을 결정할 수 있다.

finally 블럭

finally 블럭은 try-catch 문과 함께 마지막 순서로 선택적으로 쓰인다.
예외 발생 여부와 상관없이 실행된다.

try {
	// 예외 발생 가능성 문장
} catch (Exception1 e) {
	// 예외 처리 문장
} finally {
	// 예외 발생 여부와 상관없이 실행할 문장
}
  • 예외가 발생할 경우
    try -> catch -> finally 순서로 실행
  • 예외가 발생하지 않을 경우
    try -> finally 순서로 실행

try문과 catch 문에 중복으로 작성해야할 코드가 생겼을때 해당 코드를 finally 블럭 안으로 옮겨주면 좋다.

참고: try 또는 catch 블럭에서 return 문이 실행되어도 finally 블럭의 문장이 먼저 실행된 다음 return된다.

0개의 댓글