📖/자바의 정석(~ing)
[자바의 정석] Chapter 08 예외처리
모팔구
2023. 3. 27. 17:44
728x90
반응형
1. 예외처리(exception handling)
1.1 프로그램 오류
- 프로그램 에러 = 오류: 프로그램이 실행 중 어떤 원인에 의해서 오작동을 하거나 비정상적으로 종료되는 경우를 초래하는 원인.
- 발생 시점에 따른 에러의 종류
- 컴파일 에러(compile-time error): 컴파일 시 발생하는 에러
- 런타임 에러(runtime error): 실행 시 발생하는 에러
- 논리적 에러: 실행은 되지만 의도와 다르게 동작하는 것
- 수습 가능한 수준에 따른 에러의 종류
1. 에러: 메모리 부족(OutOfMemoryError)이나 스택오버플로우(StackOverflowError)와 같이 일단 발생하면 복구할 수 없는 오류
2. 예외: 발생하더라도 수습 가능한 오류
- 발생 시점에 따른 에러의 종류
1.2 예외 클래스의 계층구조
- 자바에서는 오류를 Error(에러)와 Exception(예외) 두 클래스로 정의하였다. 두 클래스 역시 Object클래스의 자손이다. 모든 예외의 최고 조상은 Exception이다. 예외 클래스들은 Exception클래스와 그 자손들, RuntimeException클래스와 그 자손들 두 그룹으로 나눌 수 있다.
- RuntimeException클래스들은 주로 프로그래머의 실수에 의해 발생될 수 있는 예외들로, 자바의 프로그래밍 요소들과 관계가 깊다.
- ArrayIndexOutOfBoundsException(배열 범위 이탈)
- NullPointerException(값이 null인 참조변수의 멤버를 호출)
- ClassCastException(클래스간의 형변환을 잘못했을 경우)
- ArithmeticException(정수를 0으로 나누는 경우)
- Exception클래스들은 외부의 영향으로 발생할 수 있는 예외로, 사용자의 동작에 의해 발생하는 경우가 많다.
1. FileNotFoundException(존재하지 않는 파일의 이름 입력)
2. ClassNotfoundException(클래스 이름을 잘못 적은 경우)
3. DataFormatException(입력값의 데이터 형식이 잘못된 경우)
- RuntimeException클래스들은 주로 프로그래머의 실수에 의해 발생될 수 있는 예외들로, 자바의 프로그래밍 요소들과 관계가 깊다.
1.3 예외처리하기 - try-catch문
- 예외처리(exception handling)
: 프로그램 실행 시 발생할 수 있는 예외에 대비하는 코드를 작성하는 것으로 프로그램의 비정상 종료를 막고 정상적인 실행상태를 유지하기 위함이다. - 예외를 처리하기 위해 try-catch문을 사용한다하나의 try블럭 다음에는 여러 종류의 예외를 처리할 수 있도록 하나 이상의 catch블럭이 올 수 있으며 이 중 발생한 예외의 종류와 일치하는 단 하나의 catch블럭만 수행된다. 일치하는 catch블럭이 없으면 예외는 처리되지 않는다.
하나의 메서드 안에 여러 개의 try-catch문이 사용될 수 있고 try블럭 또는 catch 블럭에 또 다른 try-catch문이 포함될 수 있다. catch블럭 내에 또 다른 try-catch문이 있을 경우 같은 이름의 참조변수를 사용해서는 안된다. - try { // 예외가 발생할 가능성이 있는 문장 } catch (Exception1 e) { // Exception1이 발생했을 경우, 이를 처리하기 위한 문장을 작성함. } catch (Exception2 e) { // Exception2이 발생했을 경우, 이를 처리하기 위한 문장을 작성함. }
1.4 try-catch문에서의 흐름
- try블럭 내에서 예외가 발생한 경우
→ 발생한 예외와 일치하는 catch블럭이 있는지 확인한다.
→ 일치하는 catch블럭을 찾으면 해당 블럭을 수행하고 try-catch문을 빠져나가 다음 문장을 수행한다.
→ 일치하는 catch블럭을 찾지 못하면, 예외는 처리되지 못한다. - try 블럭 내에서 예외가 발생하지 않는 경우
→ catch블럭을 거치지 않고 전체 try-catch문을 빠져나가서 수행을 계속한다.
1.5 예외의 발생과 catch블럭
- catch블럭은 괄호와 블럭으로 나뉘는데 괄호 내에는 처리하고자 하는 예외와 같은 타입의 참조변수 하나를 선언해야 한다. ex) catch(ArithmeticException ae) {}
여기서 예외가 발생하면 해당 클래스의 인스턴스가 만들어진다. 예외가 발생한 문장이 try블럭에 포함되어 있다면, 이 예외를 처리할 수 있는 catch블럭이 있는지 찾게 된다. 첫 번째 catch블럭부터 차례로 내려가면서 catch블럭의 괄호 내에 선언된 참조변수의 종류와 생성된 예외클래스의 인스턴스에 instanceof 연산자를 이용해 검사하고, 검사 결과가 true인 catch블럭을 만날 때까지 계속한다.
- 모든 예외 클래스는 Exception클래스의 자손이므로 catch블럭의 괄호에 Exception클래스 타입의 참조변수를 선언하면 어떤 종류의 예외가 발생하더라도 이 catch블럭에서 처리된다.
- printStackTrace()와 getMessage()
- printStackTrace(): 예외발생 당시의 호출 스택에 있었던 메서드의 정보와 예외 메시지를 화면에 출력한다.
- getMessage(): 발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
- 멀티 catch블럭
: jdk 1.7부터 여러 catch블럭을 | 기호를 이용해 하나의 catch블럭으로 합칠 수 있다. 이를 멀티 catch블럭이라고 하는데 결국 하나의 catch블럭이기 때문에 지정한 예외 중 어떤 예외가 발생했는지 알 수 없다. 그래서 참조변수로 연결된 예외 클래스들의 공통 조상 예외 클래스에 선언된 멤버만 사용될 수 있다. 두 예외 클래스가 조상과 자손에 관계에 있다면 컴파일 에러가 발생한다. 그냥 조상 클래스만 선언하면 되기 때문이다.try { } catch(ExceptionA e) { e.printStackTrace(); } catch(ExceptionB e) { e.printStackTrace(); } ⬇︎ try { } catch(Exception A | Exception B e) { e.printStackTrace(); }
1.6 예외 발생시키기
- 고의로 예외를 발생시킬 때는 throw를 사용한다.
// 1. 연산자 new를 이용해 발생시키려는 예외 클래스의 객체를 만든다 Exception e = new Exception("고의로 발생시킨 예외"); // 2. 키워드 throw를 이용해 예외를 발생시킨다. throw e;
- Exception클래스들은 예외가 발생할 가능성이 있는 문장들에 대해 예외처리를 해주지 않으면 컴파일이 되지 않는다. 이렇게 예외처리를 확인하는 Exception클래스를 checked예외라고 한다.
- RuntimeException클래스들은 예외처리를 하지 않아도 성공적으로 컴파일이 된다. 해당 예외는 프로그래머에 의해 실수로 발생하는 것이기 때문에 예외처리를 강제하지 않는다. 이러한 예외를 unchecked예외라고 한다.
1.7 메서드에 예외 선언하기
- 예외를 선언하는 방법
- try-catch문 사용
- 예외를 메서드에 선언
메서드에 예외를 선언할 때에는 메서드의 선언부에 키워드 throws를 사용해 메서드 내에서 발생할 수 있는 예외를 쓰면 된다. 예외가 여러 개일 경우 반점으로 구분 가능하다.
throws 뒤에 Exception클래스를 선언하면 모든 종류의 예외가 발생할 가능성이 있다는 뜻이다. 이렇게 선언하면 이 예외뿐만 아니라 그 자손타입의 예외까지도 발생할 수 있다.void method() throws Exception1, Exception2 { }
- 사실 예외를 메서드의 throws에 명시하는 것은 자신을 호출한 메서드에게 예외를 전달하여 예외처리를 떠맡기는 것이다.
예외를 전달받은 메서드가 또 다시 자신을 호출한 메서드에게 전달할 수 있으며, 이런 식으로 계속 호출스택에 있는 메서드들을 따라 전달되다가 제일 마지막에 있는 main메서드에서도 예외가 처리되지 않으면 main메서드마저 종료되어 프로그램 전체가 종료된다.
예외가 발생한 메서드에서 자신을 호출한 메서드에게 예외를 넘겨줄 수는 있지만, 이것으로 예외가 처리된 것은 아니다. 단순히 전달만 하는 것으로 결국 어느 한 곳에서는 반드시 try-catch문으로 예외처리를 해야 한다.
예외가 발생한 메서드 내에서 자체적으로 처리해도 되는 것은 메서드 내에서 try-catch문을 사용해 처리하고class Example1 { // 메서드 내에서 예외를 처리 public static void main(String args[]) { method1(); } } static void method1() { try { throw new Exception(); } catch(Exception e) { e.printStackTrace(); } } // ***************************************************** class Example2 { // 메인 메서드에서 예외를 처리 public static void main(String args[]) { try { method1(); } catch(Exception e) { e.printStackTrace(); } } static void method1() throws Exception { throw new Exception(); } }
메서드에 호출 시 넘겨받아야 할 값을 다시 받아야 하는 경우(메서드 내에서자체적으로 해결이 안 되는 경우)에는 예외를 메서드에 선언해서 호출한 메서드에서 처리해야 한다.
1.8 finally블럭
- finally블럭은 예외의 발생여부에 상관없이 실행되어야할 코드를 포함시킬 목적으로 사용함
try문에서 에러가 발생하는 경우 try → catch → finally 순서로 진행try { } catch(Exception e) { } finally { }
try문에서 에러가 발생하지 않는 경우 try → finally 순서로 진행
1.9 자동 자원 반환 - try-with-resources문
- jdk 1.7부터 추가된 try-catch문의 변형이다. 입출력에 사용되는 클래스를 사용한 후에 꼭 close()를 해줘야 하는 것들이 있다. 그렇게 해야 자원(resource)가 반환되기 때문이다. 이 close()가 예외를 발생시킬 수 있고 이를 처리하는 것이 쉽지 않다.
이런 식으로 close()에서 발생할 수 있는 예외를 처리하도록 변경했는데 보기에 좋지 않을 뿐더러 try블럭과 finally블럭에서 모두 예외가 발생하면 try블럭의 예외는 무시된다는 것이다. 이러한 점을 개선하기 위해 try-with-resources문이 추가된 것이다.try { fis = new FileInputStream("score.date"); dis = new DataInputStream(fis); ... } catch (IOException e) { e.printStackTrace(); } finally { try { // close()를 에외처리하는 try-catch문 if (dis != null) { dis.close(); } } catch (IOException e) { e.printStackTrace(); } }
try문의 괄호 안에 객체를 생성하는 문장을 넣으면 이 객체는 따로 close()를 하지 않아도 try블럭을 벗어나는 순간 자동으로 close()가 호출된다. 이후 catch블럭 또는 finally블럭이 실행된다.try (FileInputStream fis = new FileInputStream("score.dat"); DataInputStream dis = new DataInputStream(fis);) { ... } catch (EOFException e) { ... }
자동으로 객체의 close()가 호출되려면 클래스가 AutoCloseable인터페이스를 구현한 것이어야 한다.
1.10 사용자정의 예외 만들기
<생략>
1.11 예외 되던지기(exception re-throwing)
- 한 메서드에서 발생할 수 있는 예외가 여럿인 경우, 몇 개는 try-catch문으로 메서드 내에서 예외를 처리하고 나머지는 선언부에 지정하여 호출한 메서드에서 처리하도록 할 수 있다. 심지어 단 하나의 예외에 대해서도 예외가 발생한 메서드와 호출한 메서드 양쪽에서 처리하도록 할 수 있다.
이는 예외를 처리한 후 인위적으로 다시 발생시키는 방법을 통해서 가능한데 이를 예외 되던지기라고 한다. - 먼저 예외가 발생할 것 같은 메서드에 try-catch문을 사용해 예외를 처리하고 catch문에서 필요한 작업을 행한 후 throw문으로 예외를 다시 발생시킨다. 다시 발생한 예외는 이 메서드를 호출한 메서드로 전달되고 호출한 메서드의 try-catch문에서 예외를 처리한다.
- 이 방법은 하나의 예외에 대해 예외가 발생한 메서드와 이를 호출한 메서드 양쪽 모두에서 처리해줘야 할 작업이 있을 대 사용된다. 주의할 점으로 예외가 발생할 메서드에서는 try-catch문을 사용 + 선언부에 발생할 예외를 throws로 지정해야한다.
1.12 연결된 예외(chained exception)
- A예외가 B예외를 발생시킬 수 있는데 이 경우 A를 B의 원인 예외(cause exception)라고 한다. 이렇게 예외를 발생시키는 이유는 여러가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위해서이다.
이유1: 공통 조상을 가지는 두 예외를 경계하고자 조상클래스를 사용하면 결국 두 예외 중 어느 예외가 발생하는지 알 수 없다. 그래서 생각한 것이 예외가 원인 예외를 포함할 수 있게 한 것. .
이유2: checked예외를 unchecked예외로 바꿀 수 있도록 하기 위해서이다. checked예외가 발생해도 예외를 처리할 수 없는 상황이 발생하는데 이럴 경우 checked예외를 unckecked예외로 바꾸면 예외처리가 선택적이 되므로 억지로 예외를 처리하지 않아도 된다.
728x90
반응형