프로그램이 실행되는 도중에 오류(runtime error)가 발생할 수 있는데, Java는 이를 오류(Error)와 예외(exception)으로 나눴다.
일반적인 OS의 fault랑 exception과 동치는 아니고, 발생시 수습이 안 가능하냐 / 가능하냐로 나눈 것이라고 생각하면 된다.
Java의 error과 exception은 Error
과 Exception
이라는 class로 정의되어 있다.
Exception
class 하위의 여러 class들은 RuntimeException
이라는 하위 class에 속하는지, 아닌지로 크게 분류가 가능하다.
RuntimeException
은 오류를 일으킨 프로그램을 작성한 프로그래머가 원인이 되는 오류들이 해당된다. (ArrayIndexOUtOfBoundsException
, ClassCastException
, NullPointerException
, ArithmeticException
등)
RuntimeException
에 해당 안되는 Exception
class들은 오류를 일으킨 프로그램을 사용한 사용자가 원인이 되는 오류들이 해당된다.
(FileNotFoundException
, ClassNotFoundException
, DataFormatException
)
모든 Exception
class는 프로그래머가 try-catch 구문을 통해 처리(handle)하는게 가능하다. 이 코드 자체를 handler이라고도 보통 부른다.
이는 일반적인 OS에서 exception을 handle하는 코드를 exception handler이라고 보통 부르는 것에 유래되었다.
원래 exception은 각 OS가 알아서 처리를 한다. 다만 Java는 JVM에서 작동을 하기 때문에 Linux와 비교했을 때 좀 동작 방식이 다른데, 일단 JVM에도 exception 발생시 기본으로 처리를 해주는 UncaughtExceptionHandler
이라는 것이 존재한다. 저 handler이 작동하면 프로그램은 비정상종료가 된다.
try-catch 구문을 사용하면 특정 코드에서 발생한 Exception들 중 특정 Exception들을 처리하는게 가능해진다. syntax는 C++의 try-catch랑 매우 유사하다.
try {
//code
} catch (Exception1 e1) {
//code for handling Exception1
}
지정된 exception만 handle이 되며, 지정되지 않은 녀석들은 여전히 UncaughtExceptionHandler
에서 처리한다.
하나의 코드에 대해 여러개의 catch
를 각 Exception
에 대해 만드는것도 가능하며, 이 경우 exception 발생시 그 exception의 class에 대응되는 handler 하나만 실행된다.
try {
//code
} catch (Exception e) {
try {
//another code
} catch (Exception e) { //error
//another code2
}
}
위 코드는 오류를 일으킨다. 이유는 catch 영역 안의 try-catch문에서 외부 catch가 사용한 exception 참조용 변수랑 같은 이름의 exception 참조 변수를 선언했기 때문이다. 오류를 안일으킬거면 하나는 e1
, 다른 하나는 e2
와 같은 형식으로 두어야 한다.
만일 모든 부류의 Exception
을 처리하는 catch 구문을 만들고 싶으면 catch에서 받는 exception의 class를 Exception
으로 설정하면 되며, 특정 부류를 받고 싶으면 해당 부류의 class 이름으로 설정하면 된다.
만일 특정 부류의 Exception
을 처리하는 catch 구문들을 몇개 만들고 나머지는 공통된 특정 handler에서 처리하게 만들고 싶으면 다음과 같이 코드를 짜자.
try {
//code
} catch (Exception1 e) {
//handle Exception1
} catch (Exception2 e) {
//handle Exception2
} catch (Exception e) {
//handle exceptions that are not Exception1 and Exception2
}
먼저 try 구문을 수행한다. 이 때 exception이 발생하지 않으면 try-catch문 전체를 빠져나가고 계속 진행
try 구문 내 실행 도중 exception 발생시 나머지 try 내 구문을 실행하지 않고 즉시 해당 exception과 일치하는 catch 블럭을 찾는다. 만약 존재하지 않으면 UncaughtExceptionHandler
이 작동한다.
만약 존재시 해당 catch 블럭 내 구문을 수행한다. 완료되면 try-catch문 전체를 빠져나가고 계속 진행
catch 구문 내에도 try-catch가 존재하는게 가능하며, 이 경우 재귀적으로 처리가 된다.
printStackTrace()
, getMessage()
exception instance에는 exception에 대한 정보가 들어가 있으며, 위 두 함수를 통해 관련 정보를 얻는게 가능하다.
printStackTrace()
에는 exception 발생시 call stack에 있던 method 정보와 exception message, exception 자체를 출력한다.
getMessage()
는 exception message를 따로 얻는게 가능하다.
try {
//code
} catch (Exception1 | Exception2 e) {
//handler
}
주의해야하는게 몇가지 있는데, 먼저 연결된 exception class가 서로 hierarchy 관계이면 컴파일 오류가 발생한다. 저렇게 쓸 이유가 없기 때문.
또 해당 catch문 안에서는 저 오류가 무슨 오류인지 구체적으로 파악하는게 불가능하다. 위의 경우에는 e
가 Exception1
인지 Exception2
인지 파악을 못한다는 것이다. 둘 다 가능성이 있기 때문. 이 때문에 기본적으로 e
에는 저 둘의 common ancestor class에 해당하는 method만 사용이 가능하다.
단 e
가 어떤 Exception
class에 해당하는지 파악 후에 해당 class의 method를 사용하는건 가능하다. 어지간하면 그렇게 복잡하게 짤 일이 없다만.
OS의 interrupt/trap처럼 exception을 코드에서 만드는 것이 가능하다. 이를 throwing exception이라고 보통 표현한다.
이름이 저런 이유는, exception을 만드는 구문이 throw
를 사용하기 때문.
try {
Exception e = new Exception("LOL");
throw e;
throw new Exception("LOL, but never thrown..."); //shortened version
} catch (Exception e) {
System.out.println("message : " + e.getMessage());
}
Exception
class와 RuntimeException
class에 해당된다.void method() throws Exception1 {
//code
}
이러면 method
가 Exception1
및 Exception1
의 자식 class를 throw할 수 있다는 것을 의미한다.
해당 method에서 저 Exception
을 처리하기 싫고 상위 함수에게 넘기고 싶을 때 사용된다.
당연히 상위 함수에서도 본인 상위 함수에게 넘길 수도 있다. (...) 이를 재귀적으로 반복하다가 처리가 안된 경우에는 UncaughtExceptionHandler
이 결국 발동된다. 이를 방지하려면 어딘가에서 try-catch 구문을 사용해야 한다.
exception을 throw하는 method를 사용시 그 exception을 처리하는 try-catch를 꼭 만들거나, 그냥 본인도 그 exception을 throw할 수 있어야 한다. 폭탄 돌리기 강요를 하는 이유는 실수를 방지하기 위해서다.
try-catch 구문 뒤에 집어넣는게 가능하다.
try가 정상적으로 다 실행되었든, 중간에 중단되어 catch가 실행되어 정상적으로 끝난 경우든 간에 마지막에 실행되는 코드를 집어넣는 공간이다.
try {
//code
} catch (Exception e) {
//code when Exception happens in try
} finally {
//code that alwyas runs after try or catch
}
finally
구문이 있으면 finally
내 코드가 실행된 다음에 return을 한다.JDK 1.7이상에서만 존재
try 내에서 만든 특정 resource가, try 내의 Exception이 발생해서 catch로 넘어가게 되었거나 그냥 정상적으로 끝났을 때 자동으로 반환이 될 수 있도록 지원해주는 구문이다.
finally
를 통해 resource를 반환하는 것이 좋지 않은 이유는 그 안에서 exception 발생시 또 문제가 되어 이게 재귀적으로 문제가 되기 때문이다.
try (FileInputStream fis = new FileInputStream("resource.txt");
DataInputSTream dis = new DataInputStream(fis)) {
//code
} catch (Exception e) {
//exception code
}
이러면 fis
, dis
는 try를 벗어나면 자동으로 반환이 된다.
해당 resource 관련 class가 AutoCloseable
를 구현했을 때만 가능하다.
사용자 정의 Exception
을 만드는것도 가능하며, 이 경우 Exception
class를 상속받는다. RuntimeException
을 상속받아 사용자 정의 RuntimeException
을 만드는 것도 가능하다(...)
exception rethrowing이라는 것이 있다. 특정 exception에 대한 catch문 내에서 그 exception을 다시 throw하는 것이다. 보통 이러는 경우가 본인과 상위 호출 함수 양측에서 해당 exception에 대해서 처리를 해야 하는 경우에 사용된다. 당연히 이런 경우 Exception 처리를 위한 try-catch와 더불어 해당 method가 그 Exception을 throw한다는 것을 선언부에다가도 지정해야 한다.
try-catch가 함수 전체의 마무리 부분을 담당하는데 그 함수가 void가 아닌 값을 반환하는 함수인 경우, try랑 catch 모두 return 값이 필요한것은 자연스럽게 생각할 수 있다. 그러나 해당 함수가 exception을 throw하는게 가능할 경우 catch문에서 return 대신 exception을 throwing하고 마무리 지을 수 있도록 정의하는 것도 가능하다.
chained exception이라는 것이 있다. exception이 발생해 catch문으로 넘어갔을 때 그 exception을 다른 exception에 포장하고 또 throw를 하는 것이다. 어떤 공통된 오류를 일으키나 원인이 다른 것을 나타내는 상속관계가 없는 exception들을 상위 exception으로 포장한 다음에 기존 exception을 원인 exception 형태로 포함시킬 때 사용되는 경우가 사례 1. 또 다른 활용법은 check가 강요되는 exception을 check가 강요되지 않는 exception (RuntimeException
)으로 바꿀 때 쓸 수 있따.