▶ Exception Event
- 예외(Exception) : Exception Event의 약자로, 프로그램의 실행 중에 발생하는 이벤트로서 프로그램의 정상적인 실행 흐름을 중단시킨다. 즉, 자바에서 발생하는 오류를 의미한다.
- 예외 객체(Exception Object) : 메소드 안에서 오류가 발생하면 메소드가 자바 런타임 시스템으로 넘기는 객체로, 발생된 오류를 설명한다. 이 객체는 오류의 타입과 오류 발생 시의 프로그램의 상태 등의 정보를 포함하고 있다.
- 예외 던지기 : 예외 객체를 생성하는 것을 표현하는 말이다.
- 예외 잡기 : 예외 객체를 처리하는 것을 표현하는 말이다.
▶ 예외 처리기
- 예외가 발생한 지점 이후의 문장들은 실행되지 않는다. 그러나 프로그램 실행 상태를 유지하기 위해 예외가 발생하는 즉시 프로그램을 종료하지 않고, 예외를 처리하는 방법을 자바에서 제공한다.
- 예외 처리기의 기본 형식
예외가 발생했을 경우 : try → catch (→ finally)
예외가 발생하지 않았을 경우 : try (→ finally)
try {
//예외가 발생할 것 같은 코드
} catch(예외타입명 참조변수명) { //예외의 최상위 객체는 Exception 이다.
//예외를 처리하는 코드
} finally {
// try 또는 catch 블록이 끝나면 무조건 시작하는 코드로, 해당 블록은 생략할 수 있다.
}
- try와 catch 블록은 별도의 독립된 블록이므로 try 블록에서 선언된 변수는 catch 블록에서 사용할 수 없다.
- 예제 : 0으로 나누는 경우
public class DivideByZero { public static void main(String[] args) { int x = 10; int y = 0; try { int reault = x/y; } catch(ArithmeticException e) { // Arithmetic 은 “산술”의 의미를 가진다. System.out.println(“0으로 나눌 수 없습니다.”); } System.out.println(“프로그램 진행중”); } }
[ 출력 결과 ]
출력 결과를 보면, 예외가 발생하더라도 예외 처리를 통해 프로그램이 종료되지 않고 계속 실행되는 것을 볼 수 있다. 하나의 예제를 또 살펴보자.
- 예제 : 배열에서 인덱스가 배열의 크기를 벗어나는 경우
public class ArrayException { public static void main(String[] args) { int[] array = {1, 2, 3, 4, 5}; int i = 5; try { int result = array[5]; } catch (ArrayIndexOutOfBoundsException e) { System.out.println("array 배열은 0~4까지의 인덱스를 가지므로 인덱스 5는 참조불가"); } System.out.println("프로그램 진행중"); } }
[ 출력 결과 ]
배열의 인덱스가 4가 마지막인데 5번 인덱스를 찾으면 위처럼 ArrayIndexOutOfBoundsException이 발생하게 된다. 그러나 예외 처리를 통해 프로그램이 여전히 실행 중인 것을 볼 수 있다.
- 예외 처리를 하는 목적 : 오류 처리 코드 즉, 오류가 발생할 수 있는 코드를 안전하고 정상적인 코드와 분리할 수 있다. 제일 중요한 것은 오류가 나지 않게 코딩을 하는 것이다. 하지만 초보 개발자에겐 어렵기 때문에 예외 처리를 꼼꼼히 하는 것이 좋다.
▶ finally 블록
- finally 블록은 try-catch 블록이 종료될 때 반드시 실행된다.
- finally 블록을 사용하는 이유는 자원 정리를 하지 않고 try-catch 블록을 종료하는 것을 방지하기 위함이다.
- 자원을 정리한다는 것은 파일이나 메모리와 같은 자원을 반납하는 코드를 사용하는 것을 말한다. 따라서 이와 같은 코드는 finally 블록 안에 작성하는 것이 좋다.
- finally 블록을 적절하게 사용하면 자원의 누출을 막을 수 있다.
- 예제 : 파일 작성하기
public class FileError { private int[] intArray; private static final int SIZE = 10; public FileError() { intArray = new int[SIZE]; for(int i = 0; i < SIZE; i++) { intArray[i] = i; } writeList(); } public void writeList() { PrintWriter out = null; //PrintWriter란 파일에 데이터를 기록하는 출력 스트림 객체이다. try { out = new PrintWriter(new FileWriter("file.txt")); for(int i = 0; i < SIZE; i++) { out.println("배열 원소 " + i + " = " + intArray[i]); } } catch (ArrayIndexOutOfBoundsException e) { //배열 인덱스 오류 발생 시 실행 System.err.println("ArrayIndexOutOfBoundsException : "); } catch (IOException e) {//입출력 오류 발생 시 실행 System.err.println("IOException : "); } finally { if(out != null) { System.out.println("finally 블록에서 자원 정리"); out.close(); } } } public static void main(String[] args) { new FileError(); } }
[ 출력 결과 ]
정상적으로 파일이 생성되었으며, 자원 정리까지 마친 모습이다.
디렉토리 경로를 서술할 때 "file.txt" 처럼 파일명만 적게 되면 프로젝트 폴더(Chapter) 아래에 파일이 생성된다. 즉, 작업 디렉토리 아래에 파일이 생성되는 것이다.
파일의 내용 또한 정상적으로 기재되었다.
▶ try-with-resources 문장
- 하나 이상의 자원을 선언하는 try 문장이다. 즉, 메모리 공간을 빌려 쓰는 등의 경우가 무조건 있다면! try-with-resource 문장을 사용하는 것이 자원을 반납하는데 용이하다.
- 자원은 프로그램이 종료되면서 반드시 반납해야 하는데, try-with-resources 문장은 문장의 끝에서 자원들을 자동으로 반납할 수 있게 한다.
- Java SE 7버전부터 추가되었다.
- 아래의 예시는 readList() 메소드에서 예외가 발생하면 해당 메소드를 호출한 곳으로 예외를 던지는 경우이다. 즉, 메소드 내에서 예외를 처리하지 않는다.
이 기능을 사용하려면 자원 객체가 java.lang.AutoCloseable 인터페이스를 구현하여야 한다. Java SE 7버전부터 BufferedReader 클래스가 이 인터페이스를 구현하고 있다.
static String readList() throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(“file.txt”)) {
return br.readLine();
}
}
- try 키워드 바로 다음에 소괄호가 있으면 소괄호 안의 내용을 자원으로 취급한다.
- try 문장이 정상적으로 종료되건 예외가 발생하건 간에 무조건 자원은 닫힌다.
▶ 예외 처리
- 예외는 하나의 객체이다.
- 예외의 종류는 크게 3가지가 있다.
Error
RuntimeException
기타 예외
- 예외의 종류에 따라 처리되지 않아도 되는 예외도 존재한다.
- 모든 예외는 Throwable 클래스로부터 상속되어서 Error와 Exception 이라고 하는 두 개의 클래스로 나누어진다. Exception은 다시 RuntimeException과 기타 예외로 나누어진다.
- Error : 자바 가상 기계 안에서 치명적인 오류가 발생하면 생성된다.
대부분의 애플리케이션은 이러한 오류를 예측하거나 복구할 수 없다. 예를 들어, 하드웨어의 오류로 파일을 읽을 수 없는 경우가 있는데, 이런 경우 IOError가 발생한다.
Error는 자주 발생하지 않으며, 예외 처리 대상이 아니다. 따라서 컴파일러는 체크하지 않는다.
- RuntimeException : 주로 프로그래밍 버그나 논리 오류에서 기인한다.
예를 들어 파일 이름을 FileReader 생성자로 전달하는 과정에서 논리 오류로 항상 null값을 전달한다면 생성자가 NullPointerException을 발생한다. 애플리케이션은 이러한 예외를 물론 잡아서 처리(예외 처리기)할 수 있지만 보다 합리적인 방법은 예외를 일으킨 버그를 잡는 것이다.
RuntimeException은 예외 처리의 주된 대상이 아니므로 컴파일러가 체크하지 않는다.
- Error와 RuntimeException을 합쳐서 비체크 예외(unchecked exceptions)라고 한다.
- 기타 예외 : 체크 예외(checked exception)라고 하며, 충분히 예견될 수 있고 회복할 수 있으므로 프로그램은 반드시 이 예외들을 처리하여야 한다.
예를 들어 애플리케이션이 사용자에게 입력 파일 이름을 받아서 파일을 오픈하는 상황이 있다면, 정상적인 작동에 의해 사용자가 이미 존재하는 파일 이름을 입력하고 FileReader 객체가 생성될 것이다. 그러나 실수로 잘못된 파일의 이름을 입력한 경우 FileNotFoundException 예외가 발생한다. 이 때 프로그램이 실행 중단되길 바라는 게 아니라, “예외 처리의 주된 대상”으로 보고 예외를 잡아서 다시 사용자에게 정확한 파일 이름을 받게 해야 한다.
기타 예외는 컴파일러가 예외를 처리했는지 확인한다. 만약 처리하지 않았다면 컴파일 오류가 발생한다.
- 예외의 포괄적 처리 : 예외도 객체임은 앞서 설명했었다. 따라서 처리할 때 상위 클래스를 이용하여 하위 클래스까지 포괄적으로 처리할 수 있다.
try {
getInputExample(); //이 함수에 예외가 발생한다고 가정하자.
} catch (NumberException e) { //NumberException 의 하위 클래스를 모두 잡는다.
//예외 처리 코드
}
위의 방법 외에도 예외의 최상위 클래스인 Exception을 예외종류로 catch 소괄호에 선언 catch (Exception e) 하면 모든 예외를 잡을 수 있다. 그러나 어떤 예외가 발생했는지 분간할 수 없다. 그러므로 구체적인 클래스 이름을 먼저 쓰고 일반적인 클래스 이름(상위 클래스)은 나중에 쓰는 것이 좋다. 즉, catch 블록을 사용할 때는 하위 클래스부터 상위 클래스 순서로 작성하여야 한다.
▶ 예외와 메소드
- 자바에서는 예외를 처리하는 방법이 크게 두 가지가 있다.
예외를 잡아서 그 자리에서 처리하는 방법 : try-catch 블록 사용
메소드가 예외를 발생시킨다고 기술하는 방법 : throws 키워드 사용하여 다른 메소드한테 예외 처리 맡기기
- “메소드가 예외를 발생시킨다고 기술하는 방법“은 가끔 메소드가 발생되는 예외를 그 자리에서 처리하지 않고, 자신을 호출한 상위 메소드로 예외를 전달하는 편이 더 적절할 때 사용된다.
이와 같은 방식을 사용하는 이유는 발생하는 모든 예외를 그 자리에서 처리하는 것은 상당한 양의 코드를 필요로 하고 또 반드시 상위 메소드가 그 예외를 처리하도록 해야 하는 경우도 있기 때문이다.
상위 메소드가 예외를 처리하도록 하려면, 반드시 메소드가 이들 예외를 던진다고 메소드 정의에 표시하여야 한다.
public void writeList() throws IOException {
...
}
- 비체크 예외는 상위 메소드로 전달하지 않아도 된다. 그러나 기술할 수는 있다.
public void writeList() throws IOException, ArrayIndexOutOfBoundsException {
...
}
- 보통 RuntimeException은 비체크 예외지만, 프로그램의 다른 영역으로 넘기지 말고 직접 처리해야 한다.
- 계속 언급해온 “예외 처리”라는 것은 정확하게 실제 발생한 예외의 종류와 예외 처리기가 처리하기로 한 예외의 종류가 일치하는 것을 의미한다.
- 예외 처리 과정
어떤 메소드 안에 예외가 발생하면 런타임 시스템은 그 메소드 안에 예외 처리기가 있는 지를 살핀다. 만약 그 자리에 예외 처리기가 없다면 호출 스택(call stack)에 있는 상위 메소드를 조사하게 된다.
즉, 탐색은 예외가 발생한 메소드부터 시작하여서 메소드가 호출된 순서의 역순으로 진행된다. 아래의 과정 중 예외가 전달되는 상황에서 호출 스택을 탐색하게 되는 것이다.
예외가 발생한다. → 예외가 전달된다. → 예외를 잡는다.
만약 전체 호출 스택을 런타임 시스템이 다 뒤졌는데도 처리기를 발견하지 못하면 그냥 프로그램을 종료시킨다.
호출 스택은 메소드의 호출 순서를 기억하며, 지금까지 호출된 메소드를 스택에 모아놓는다. 호출 스택이 있어야만 메소드가 종료되었을 경우, 되돌아갈 메소드를 찾을 수 있다.
▶ 예외 생성하기
- 예외는 주로 자바 라이브러리에서 많이 발생하지만 실제로는 어떤 코드라도 예외를 발생시킬 수 있다.
- 자바에서는 예외 객체를 생성하는 키워드를 throw 라고 한다.
- throws는 예외를 던지는 키워드이고, throw는 예외 객체를 생성하는 키워드임에 주의해야 한다.
- 자바에서는 오류가 감지되면 throw 문을 사용하여 예외를 생성한다.
- throw 문장은 Throwable 객체만 하나의 인수로 요구한다. Throwable 객체는 Throwable 클래스를 상속받는 자식 클래스들의 인스턴스(객체)이다. 예외를 던질 때는 예외를 다중으로 던질 수 있지만 예외를 생성할 때는 throw 키워드 하나에 오직 하나의 예외만 생성할 수 있는 것이다.
- catch 블록에서 예외를 처리하지 못하거나 다른 예외 처리기로 작업을 위임할 경우에는 예외를 다시 발생시켜서 전달할 수도 있다. 즉, 연속적인 예외가 발생하는 것이다.
단, 아래에 서술한 ExampleException은 예시이므로 실제로 생성할 예외 클래스는 미리 정의되어 있어야 한다.
try {
...
} catch (IOException e) {
throw new ExampleException(“다른 예외”, e);
}
새로운 예외 객체에는 원래의 예외 객체가 첨부된다. 즉, 위에는 ExampleException과 IOException이 서로 세트처럼 붙어 다니게 된다. 따라서 이 두 개를 모두 처리할 수 있는 올바른 처리기를 만날 때까지 자바 런타임 시스템은 메소드 호출 스택을 거슬러 올라가며 탐색한다.
▶ 사용자 정의 예외
- 다른 예외와 구별하여 특별히 처리하려는 오류가 있다면 사용자 정의 예외 클래스를 생성한다.
- Exception 클래스를 상속하여 만든다.
public class MyException extends Exception {
...
}
'Programming Language > JAVA' 카테고리의 다른 글
37. Collection, Vector - 컬렉션, Vector 클래스, Collection 인터페이스 (0) | 2017.07.23 |
---|---|
36. Generic - 제네릭 클래스, 제네릭 프로그래밍 (9) | 2017.07.23 |
34. Package - 패키지 (0) | 2017.07.23 |
33. JTable - 테이블 (0) | 2017.07.22 |
32. JOptionPane, JDialog - 대화 상자 (0) | 2017.07.22 |