Test153~154 예외(Exception) 처리 - (2) throws(예외 떠넘기기)
throws (예외 떠넘기기)
메서드 내부에서 예외가 발생했을 때 예외를 try - catch 문으로 잡아서 처리할 수 있지만 경우에 따라서 현재 메서드를 호출한 메서드로 예외를 떠넘길 수 있다. 예외를 떠넘기는 방법은 다음과 같이 throws 키워드를 메서드 뒤에 붙여주면 된다.
public static void generateException() throws NullPointerException { //NullPointerException 발생 }
만약 떠넘겨야할 예외 종류가 여러개라면 쉼표(,)를 기준으로 나열하여 선언할 수 있다.
public static void generateException() throws NullPointerException, ArithmeticException { //예외 발생 }
예외가 발생하는 경우 try - catch문을 통해 처리하지 않고 throws를 이용해 떠넘기면 현재 메서드를 호출한 곳으로 던져지게 된다. 만약 모든 메서드에서 throws 를 이용해 예외를 떠넘기다 보면 최초 호출 지점인 main() 메서드 내부로 예외가 던져지게 되며 main() 메서드에서 마저 예외를 떠넘기게 된다면 JVM의 예외처리기까지 도달하여 프로그램은 그대로 종료된다.
이렇게 되면 사실상 예외를 처리하지 않은것이나 다름 없으므로 매우 무의미한 행동이라 할 수 있다. 의도적인 경우가 아니라면 throws는 많은 생각과 필요에 의해 사용되어야 한다.
예외를 떠넘기는 이유
예외가 발생한 경우 굳이 메서드 내에서 try-catch 문으로 예외를 처리하지 않고 throws문으로 떠넘기는 이유는 무엇일까?
첫번째 이유는 메서드 선언부에 선언된 throws문을 통해 해당 API를 사용했을 때 어떤 예외가 발생할 수 있는지를 예측할 수 있다.
다음은 java api 공식 문서이다. 선언부를 보면 IOException이 던져진다는 것을 알 수 있다.
두번째 이유로는 현재 메서드 내에서 예외를 처리할 필요가 없다고 판단했을 경우이다. 예외 처리에는 생각보다 많은 코드가 필요하게 되며 이는 코드를 읽기 어렵게 만들고 불필요한 코드가 많이 추가되게 만들어 버그를 만들기 쉽다. 또한 API를 만드는데에 있어서 내가 처리하기 보다는 내가 만든 API를 사용하는 다른 개발자에게 원하는 처리를 하도록 기회를 줄 수 있다.
다음 코드는 BufferedReader 클래스의 readLine메소드로 인해 발생하는 예외(Checked Exception)를 throws 처리한 것이다. main에서 호출한 메소드에서 IOException을 throws 하여 main메소드로 던져진 IOException을 다시 throws하는 코드이다.
import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.IOException; public class Test153 { private String[] data = new String[3]; public void proc() throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String str; int n = 0; System.out.print("이름 입력[종료:Ctrl+z] : "); while ((str = br.readLine()) != null ) { data[n++] = str; System.out.print("이름 입력[종료:Ctrl+z] : "); } System.out.println("입력된 내용..."); for (String s : data) { if (s != null) { System.out.println(s); } } } public static void main(String[] args) throws IOException // check~!!! { Test153 ob = new Test153(); ob.proc(); } }
Unchecked Exception은 명시적인 예외처리를 하지 않아도 된다. 이 예외는 피할 수 있지만 개발자가 부주의해서 발생하는 경우가 대부분이며, 미리 예측하지 못했던 상황에서 발생하는 예외가 아니기 때문에 굳이 로직으로 처리를 할 필요가 없도록 만들어져 있다.
아래 코드는 위 코드에서 발생할 수 있는 Unchecked Exception에대한 처리를 try ~ catch로 처리하였다.
import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.IOException; public class Test154 { private String[] data = new String[3]; public void proc() { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String str; int n = 0; try { System.out.print("이름 입력[종료:Ctrl+z] : "); while ((str = br.readLine()) != null ) { data[n++] = str; System.out.print("이름 입력[종료:Ctrl+z] : "); } } catch(ArrayIndexOutOfBoundsException e) { System.out.println("======[예외 발생]======"); System.out.println("getMessage : " + e.getMessage()); System.out.println("toString : " + e.toString()); System.out.println("printStackTrace............"); e.printStackTrace(); } catch (IOException e) { System.out.println(e.toString()); } System.out.println("\n입력된 내용..."); for (String s : data) { if(s != null) System.out.println(s); } } public static void main(String[] args) { Test154 ob = new Test154(); ob.proc(); } }
실행 결과
// 실행 결과 /* 이름 입력[종료:Ctrl+z] : 조윤상 이름 입력[종료:Ctrl+z] : 김철수 이름 입력[종료:Ctrl+z] : 배철수 이름 입력[종료:Ctrl+z] : ^Z 입력된 내용... 조윤상 김철수 배철수 계속하려면 아무 키나 누르십시오 . . . */
런타임 중 예외 발생시
입력받을 문자열 배열의 크기가 초과하는 예외 (ArrayIndexOutOfBoundsException) 즉, (Unchecked Exception) 이 발생하였을 때 catch 블록에 정의된 대로 실행되었다.