예외처리의 종류는?
- 오라클 문서에서는 예외(Exception)을 3가지로 구분하고있다.
1) Checked Exception
: 컴파일러가 예외처리를 제대로 했는지, 확인하는 예외들을 의미한다. 다시 말하여, 프로그래머인 우리가 catch문에서 예외를 처리해주든, 혹은 throws를 통하여 처리해달라고 요청을 하여 예외를 처리할 수 있게끔해야 한다.
2) Error
: 프로그램 외부에서 발생하는 예외를 의미한다. 오라클 문서에서는 Error 를 예외로 분류하고 있으나, Error.java의 주석을 읽어보면 비정상적인 상황으로 기인한 Runtime Exception으로 간주되어 진다.
3) Unchecked Exception(=Runtime Exception)
: 프로그램 내부에서 발생하는 예외이나, 컴파일러가 확인하지 않는 예외들을 의미한다. 이는 보통 실행시에 발생하는 예외들이다.
현상만 놓고보자면, Checked Exception 이 발생되는 경우에는 컴파일이 안될 것이며, 그외는 런타임에서 발생한다
예외처리는 어떻게하는가?
- 예외처리는 try-catch-(finally) 로 처리하면 된다.
public void tryCatchFinally() {
int[] array = new int[5];
try {
// 처리해야하는 코드 - (1)
System.out.println(array[5]);
}
catch(NullPointerException e){
// 예외가 잡혔을 때 처리할 코드 - (2)
// 부모클래스를 먼저 catch 문의 조건으로 잡으면 컴파일이 되지 않음. - (2.1)
// java: exception java.lang.NullPointerException has already been caught
// NullPointerException 이 아닌경우 해당 catch 문은 수행되지 않음 - (2.2)
e.printStackTrace();
}
catch (Exception e) {
// 예외가 잡혔을 때 처리할 코드 - (2)
// Exception은 Throwable 을 상속받음 - (2.3)
// printStackTrace - (2.4)
e.printStackTrace();
// getMessage() - (2.5)
System.out.println(e.getMessage());
// getCause - (2.6)
System.out.println(e.getCause());
}
finally {
// 예외 발생과는 상관없이 언제나 실행되는 코드, 생략가능 - (3)
System.out.println("Finally Block is called");
}
}
위의 코드를 보면
(1) : try 블록 내에서는 예외가 발생할 수 있는 코드를 작성한다.
(2) : catch 블록 내에서는 해당하는 예외를 처리하는데,
(2.1) Exception 을 catch하는 경우 더 포괄적인 부분을 작성한 경우 컴파일이 되지 않는다. 이 예제 기준으로 만약 Throwable 혹은 Exception을 먼저 catch하게끔 작성한 경우 NullPointerException 예외는 검사되지 않는다.
고로 Exception은 상세한 내용부터 catch하여 포괄적인 내용으로 catch 되어야 한다.
(2.2) ArrayIndexOutOfBoundsException 이 발생했는데, 다른 NullPointerException 같은걸로 잡으려면 안잡힌다.
그렇지만 부모 클래스인 IndexOutOfBoundsException 으로 catch는 가능하다.
(2.4 ~ 2.6)
- printStackTrace : 예외가 발생했을 때의 호출스택을 보여준다
- getMeassage : 예외가 왜 발생했는지를 알려준다
- getCause : 예외의 종류 + 예외가 왜 발생했는지를 알려준다
(3) : finally 블록 내에서는 예외 발생과 상관없이 언제나 실행되는 코드들이며, 생략가능한 블록이다. 오라클 공식문서에서는 try block이 exit하기 전에 언제나 수행된다고 설명되어있다(다음 예외처리의 동작흐름에 대해 별도로 설명할 예정)
즉, 특별히 return 을 하지 않는 이상 try -> finally 또는 try -> 오류발생(오류 발생지점 이후 try문은 수행되지 않음) -> catch -> finally 이다. 다만 특이한 점이 있어 따로 흐름에 대해서는 다음 내용에 정리하겠다.
try-with-resources !
try-with-resources는 자원해제(close()) 처리를 까먹는 경우를 예방하기 위함이다. 이렇게 사용하기 위해서는
java.lang.AutoCloseable(Closeable포함) 인터페이스를 구현한 것들에 한해 사용이 가능하다.
아래의 코드에서 out.write(b) 가 실패하는 경우, out 자원을 해제하기 위해 finally 블록에서 자원해제를 해주는 코드가 필여요하다. (애초에 try 블록에서 안하면 되지만 어쨋든 코드의 중복을 얘기하고 싶었다)
private void writeByte(byte[] b) {
// get current path
String path = System.getProperty("user.dir");
FileOutputStream out = null;
File file = new File(path.toString()+"/writeBeforeJDK1_7.txt");
try{
out = new FileOutputStream(file, true);
out.write(b); // Write byte[] in here - (1)
out.close(); // Close File in here
} catch (IOException e) {
e.printStackTrace();
return;
}
finally {
// Check whether outputStream is closed or not. - (2)
if (out !=null){
try{
out.close();
}
catch (Exception e ){
// Handle Exception
e.printStackTrace();
}
}
}
}
위의 코드를 try-with-resources 를 활용하여 작성하면 이렇게 많은 생산성을 확보하고 실수가능성을 줄일 수 있다.
private void writeByteTryWithResources(byte[] toWrite){
// get current path
String path = System.getProperty("user.dir");
File file = new File(path.toString()+"/writeAfterJDK1_7.txt");
try(FileOutputStream out = new FileOutputStream(file, true)){
out.write(toWrite);
} catch (IOException e) {
e.printStackTrace();
return;
}
}
예외처리는 어떻게 흘러가는가?
위에서 소결론을 내렸으나, return 에 따라 특이한 부분이 존재하여 별도로 정리해보고자 한다.
아래는 실제 return을 catch문과 finally문에 작성해본 코드이다.
public String noReturnInCatchAndFinally() {
System.out.println("noReturnInCatchAndFinally method is Called!!");
int[] array = new int[5];
try {
System.out.println("try Block is called");
System.out.println(array[5]);
}
catch (Exception e) {
System.out.println("catch Block is called");
e.printStackTrace();
}
finally {
System.out.println("finally Block is called");
}
return "endOfControl";
}
public String returnInCatch_noReturnInFinally() {
System.out.println("returnInCatch_noReturnInFinally method is called!!");
int[] array = new int[5];
try {
System.out.println("try Block is called");
System.out.println(array[5]);
}
catch (Exception e) {
System.out.println("catch Block is called");
return "catch";
}
finally {
System.out.println("finally Block is called");
}
// (1) try 문으로 이어짐.
return "OutSideOffinally";
}
public String noReturnInCatch_returnInFinally() {
System.out.println("noReturnInCatch_returnInFinally method is called!!");
int[] array = new int[5];
try {
System.out.println("try Block is called");
System.out.println(array[5]);
return "try";
}
catch (Exception e) {
System.out.println("catch Block is called");
}
finally {
System.out.println("finally Block is called");
// (2) try 문의 return은 사라짐
return "OutSideOffinally";
}
}
이 코드를 디컴파일해보면,
(1) catch 블록에 return 이 있는 경우
:별도의 String 변수를 생성하여, Exception이 발생하는 경우 이를 return 함
(2) finally 블록에 return 이 있는 경우
: finally에만 return 이 남게됨. 이는 try, catch에 아무리 return 을 넣어도 결과적으로 finally에서 return을 통해 exit를 하기 때문에 컴파일러가 컴파일 시, try 및 catch 블록에 return 을 만들지 않음!!!
이를 통해 얻는 교훈은, finally 블록안에는 절대 return 예약어를 사용하지 말것!!!!
예외는 어떻게 발생시키는가(+throws)?
// Exception handling by try-catch - (1)
public void howToThrowExceptionWithTryCatch() {
try {
throw new Exception("예외 발생!");
} catch (Exception e) {
e.printStackTrace();
}
}
// Exception handling with throws - (2)
public void howToThrowExceptionWithOutTryCatch() throws Exception {
throw new Exception("예외 발생!");
}
// Throw Runtime Exception - (3)
public void throwUncheckedException() {
throw new RuntimeException("Unchecked Exception!");
}
(1) : throw 예약어를 통해 예외를 발생시키되, try-catch로 예외를 처리를 해주는 경우
(2) : 메소드 시그니쳐에 throws 를 추가하여 메소드를 호출한 쪽에서 예외를 처리하게끔 요청하는 경우
-> 호출한 쪽에서 예외를 처리(try-catch 혹은 throws)를 해야하는데, throws로 처리하는 경우, 프로그램의 시작점인 main메소드에서도 throws를 하는 경우 프로그램은 종료.
(3) : 컴파일러가 위의 1번과 2번 케이스처럼 예외처리를 요청하지 않음. 용어그대로, Unchecked Exception이기 때문!
커스텀한 예외는 어떻게 만드는가?
// extends Exception or RuntimeException
public class CustomException extends Exception {
public CustomException() {
super();
}
public CustomException(String message) {
super(message);
}
public CustomException(String message, Throwable cause) {
super(message, cause);
}
public static void main(String[] args) throws Exception {
throw new CustomException("Custom Error", new Throwable("Error happens with..."));
}
}
Exception 또는 RuntimeException을 상속받아서, message, cause 등을 다 받을 수 있게끔 처리하면 된다.
'가치코딩 [Java] > 기초문법' 카테고리의 다른 글
[Java 문법] enum (0) | 2021.02.04 |
---|---|
[Java 문법] 패키지 (1) | 2021.02.03 |
[Java 문법] 클래스 (0) | 2020.12.18 |
[Java 문법] 선택문 및 반복문 (0) | 2020.12.08 |
[Java 문법] 자바 연산자 (0) | 2020.11.28 |