포스트

예외 처리

예외 처리


오류(error)와 예외(exception)

  • 자바 프로그램을 작성할 때 자바 문법에 맞지 않게 코드를 작성하고 컴파일하려고 하면, 자바 컴파일러는 문법 오류(syntax error)를 발생시킨다. 또한, 자바 문법에는 맞게 작성되었다 하더라도 프로그램이 실행되면서 예상하지 못한 오류가 발생할 수 있다.
  • 이렇게 컴퓨터 시스템이 동작하는 도중에 예상하지 못한 사태가 발생하여 실행 중인 프로그램이 영향을 받는 것을 오류(error)와 예외(exception) 두 가지로 구분할 수 있다.

  • 오류(error)는 시스템 레벨에서 프로그램에 심각한 문제를 야기하여 실행 중인 프로그램을 종료시킨다. 오류는 개발자가 미리 예측하여 처리할 수 없는 것이 대부분이므로, 오류에 대한 처리는 할 수 없다.
  • 하지만 예외(exception)는 오류와 마찬가지로 실행 중인 프로그램을 비정상적으로 종료시키지만, 발생할 수 있는 상황을 미리 예측하여 처리할 수 있다.
  • 따라서 개발자는 예외 처리(exception handling)를 통해 예외 상황을 처리할 수 있도록 코드의 흐름을 바꿀 필요가 있다.

예외 처리(exception handling)

  • 자바에서는 프로그램이 실행되는 도중 발생하는 예외를 처리하기 위해 try / catch / finally 문을 사용할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
try {
    // 예외를 처리하길 원하는 실행 코드;
} catch (e1) {
    // e1 예외가 발생할 경우에 실행될 코드;

} catch (e2) {
    // e2 예외가 발생할 경우에 실행될 코드;
}
...
finally {
    // 예외 발생 여부와 상관없이 무조건 실행될 코드;
}
  1. try 블록: 기본적으로 맨 먼저 실행되는 코드로 여기에서 발생한 예외는 catch 블록에서 처리된다.
  2. catch 블록: try 블록에서 발생한 예외 코드나 예외 객체를 인수로 전달받아 그 처리를 담당한다.
  3. finally 블록 : 이 블록은 try 블록에서 예외가 발생하건 안 하건 맨 마지막에 무조건 실행된다.
  • catch 블록과 finally 블록은 선택적인 옵션으로 반드시 사용할 필요는 없다. 따라서 사용할 수 있는 모든 적합한 try 구문은 다음과 같다.
1
2
3
4
5
try / catch

try / finally

try / catch / ... / finally
  • 다른 제어문과는 달리 예외 처리문은 중괄호({})를 생략할 수 없다.

예외 처리 메커니즘

  • 자바에서 예외 처리는 다음과 같은 순서로 진행된다.
  1. try 블록에 도달한 프로그램의 제어는 try 블록 내의 코드를 실행한다.

    이때 만약 예외가 발생(throw)하지 않고, finally 블록이 존재하면 프로그램의 제어는 바로 finally 블록으로 이동한다.

  2. try 블록에서 예외가 발생하면 catch 핸들러는 다음과 같은 순서로 적절한 catch 블록을 찾게 된다.

    1) 스택에서 try 블록과 가장 가까운 catch 블록부터 차례대로 검사한다.

    2) 만약 적절한 catch 블록을 찾지 못하면, 바로 다음 바깥쪽 try 블록 다음에 위치한 catch 블록을 차례대로 검사한다.

    3) 이러한 과정을 가장 바깥쪽 try 블록까지 계속 검사하게 된다.

    4) 그래도 적절한 catch 블록을 찾지 못하면, 예외는 처리되지 못한다.

  3. 만약 적절한 catch 블록을 찾게 되면, throw 문의 피연산자예외 객체의 형식 매개변수로 전달된다.

  4. 모든 예외 처리가 끝나면 프로그램의 제어는 finally 블록으로 이동한다.

  5. finally 블록이 모두 처리되면, 프로그램의 제어는 예외 처리문 바로 다음으로 이동한다.


  • 다음 그림은 위에서 설명한 예외 처리 메커니즘을 그림으로 표현한 것이다.

이미지

  1. 만약 ①번 try 블록에서 예외가 발생하지 않고, 바깥쪽 try 블록에서도 예외가 발생하지 않으면, ⑥번 finally 블록이 바로 실행될 것이다.
  2. 하지만 ①번 try 블록에서 예외가 발생하면, ②번과 ③번 catch 블록에서 해당 예외를 처리할 수 있는지 검사하게 된다.
  3. 만약 적절한 catch 블록을 찾지 못하면, 바깥쪽 try 블록의 ④번과 ⑤번 catch 블록도 차례대로 검사하게 된다.
  4. 이때 해당 예외를 처리할 수 있는 catch 블록을 찾게 되면, 해당 catch 블록을 실행한 후 ⑥번 finally 블록을 실행한다.
  5. 하지만 모든 catch 블록이 해당 예외를 처리할 수 없으면, 예외는 처리되지 못한 채 해당 프로그램은 강제 종료된다.


예외 발생 및 회피


예외 발생시키기

  • 자바에서는 throw 키워드를 사용하여 강제로 예외를 발생시킬 수 있다.
1
2
3
4
5
Exception e = new Exception("오류메시지");

...

throw e;
  • 위의 예제처럼 생성자에 전달된 문자열은 getMessage() 메소드를 사용하여 오류 메시지로 출력할 수 있다.

예외 회피하기

  • 메소드 선언부에 throws 키워드를 사용하여 해당 메소드를 사용할 때 발생할 수 있는 예외를 미리 명시할 수도 있다. 이렇게 하면 해당 메소드를 사용할 때 발생할 수 있는 예외를 사용자가 충분히 인지할 수 있으며, 그에 대한 처리까지도 강제할 수 있다.
  • 따라서 더욱 안정성 있는 프로그램을 손쉽게 작성할 수 있도록 도와줄 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 호출된 메소드에서 발생한 예외를 호출된 메소드에서 처리하는 예제
public class prog {
	static void handlingException() {
		try {
			throw new Exception();
		} catch (Exception e) {
			System.out.println("호출된 메소드에서 예외가 처리됨!");
		}
	}
	
	public static void main(String[] args) {
		try {
			handlingException();
		} catch (Exception e) {
			System.out.println("main() 메소드에서 예외가 처리됨!");
		}
	}
}

이미지

  • 이때 호출된 메소드의 try / catch 문을 생략하면 컴파일 오류가 발생한다. 또한, 이 메소드를 호출한 main() 메소드는 호출된 메소드에서 예외가 발생한 사실을 알 수 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
// throws 키워드를 사용하여 호출된 메소드에서 발생한 예외를 호출한 메소드로 넘기는 예제
public class Exception04 {
    static void handlingException() throws Exception { throw new Exception(); }

    public static void main(String[] args) {
        try {
            handlingException();

        } catch (Exception e) {
            System.out.println("main() 메소드에서 예외가 처리됨!");
        }
    }
}
  • 이렇게 함으로써 호출된 메소드에는 try / catch 문을 생략할 수 있다. 그리고 호출된 메소드에서 발생한 예외를 해당 메소드를 호출한 main() 메소드에서 처리할 수 있게 된다.

사용자 정의 예외 클래스

  • 자바에서는 Exception 클래스를 상속받아 자신만의 새로운 예외 클래스를 정의하여 사용할 수 있다.
  • 사용자 정의 예외 클래스에는 생성자뿐만 아니라 필드 및 메소드도 원하는 만큼 추가할 수 있다. 요즘에는 아래와 같이 Exception 클래스가 아닌 예외 처리를 강제하지 않는 RuntimeException 클래스를 상속받아 작성하는 경우가 많다.
1
2
3
4
5
class MyException extends RuntimeException {
    MyException(String errMsg) {
        super(errMsg);
    }
}

try-with-resources 문

  • Java SE 7부터는 사용한 자원을 자동으로 해제해 주는 try-with-resources 문을 사용할 수 있다.
1
2
3
try (// 파일을 열거나 자원을 할당하는 명령문) {
     ...
}
  • 위와 같이 try 블록에 괄호(())를 추가하여 파일을 열거나 자원을 할당하는 명령문을 명시하면, 해당 try 블록이 끝나자마자 자동으로 파일을 닫거나 할당된 자원을 해제해 준다.
1
2
3
4
5
6
7
8
9
10
// 파일에서 문자열을 한 줄 읽어오는 예제
static String readFile(String filePath) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(filePath));
    try {
        return br.readLine();
    } finally {
        if (br != null)
            br.close();
    }
}
  • 위와 같이 Java SE 7 이전에서는 finally 블록을 사용하여 사용한 파일을 닫아줘야 했다. 하지만 try-with-resources 문을 사용하면 다음과 같이 자동으로 파일의 닫기를 수행할 수 있다.
1
2
3
4
5
static String readFile(String filePath) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
        return br.readLine();
    }
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.