예외처리(4) - finally블럭, 자동 자원 반환: try-with-resources문, 사용자 정의 예외
finally블럭
finally 블럭은 try-catch문과 함께 예외의 발생여부에 상관없이 실행되어야할 코드를 포함시킬 목적으로 사용된다.
try-catch문의 끝에 선택적으로 덧붙여 사용할 수 있으며, try-catch-finally 순서로 구성된다
예외 발생시 : try->catch->finally
- 예외 발생 X : try->finally
class FinallyTest {
public static void main(String args[]) {
try {
startInstall(); // 프로그램 설치에 필요한 준비를 한다.
copyFiles(); // 파일들을 복사한다.
deleteTempFiles(); // 프로그램 설치에 사용된 임시파일들을 삭제한다.
} catch (Exception e) {
e.printStackTrace();
deleteTempFiles(); // 프로그램 설치에 사용된 임시파일들을 삭제한다.
} // try-catch의 끝
} // main의 끝
static void startInstall() { /* 프로그램 설치에 필요한 준비를 하는 코드를 적는다. */ }
static void copyFiles() { /* 파일들을 복사하는 코드를 적는다. */ }
static void deleteTempFiles() { /* 임시파일들을 삭제하는 코드를 적는다. */ }
}
deleteTempFiles()메서드는 예외의 발생 여부와 관계없이 실행되어야 하는 것이다.
- 따라서 아래 코드처럼 finally블럭을 사용하는 것이 좋다.
class FinallyTest2 {
public static void main(String args[]) {
try {
startInstall(); // 프로그램 설치에 필요한 준비를 한다.
copyFiles(); // 파일들을 복사한다.
} catch (Exception e) {
e.printStackTrace();
} finally {
deleteTempFiles(); // 프로그램 설치에 사용된 임시파일들을 삭제한다.
} // try-catch의 끝
} // main의 끝
static void startInstall() { /* 프로그램 설치에 필요한 준비를 하는 코드를 적는다. */ }
static void copyFiles() { /* 파일들을 복사하는 코드를 적는다. */ }
static void deleteTempFiles() { /* 임시파일들을 삭제하는 코드를 적는다. */ }
}
Example 8.19
class FinallyTest3 {
public static void main(String args[]) {
// method1()은 static메서드이므로 인스턴스 생성없이 직접 호출 가능하다.
FinallyTest3.method1();
System.out.println("method1()의 수행을 마치고 main메서드로 돌아왔습니다.");
} // main의 끝
static void method1() {
try {
System.out.println("method1()이 호출되었습니다.");
return; // 현재 실행 중인 메서드를 종료한다.
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("method1()의 finally블럭이 실행되었습니다.");
}
} // method1메서드의 끝
}
try 블럭에서 return문이 실행되는 경우에도 finally블럭의 문장들이 먼저 실행된 후에, 현재 실행 중인 메서드를 종료한다.
- 이와 마찬가지로 catch블럭의 문장 수행 중에 return문을 만나도 finally블럭의 문장들은 수행된다.
자동 자원 반환 : try-with-resources문
JDK1.7부터 try-with-resources문이라는 try-catch문의 변형이 새로 추가되었다.
- I/O 관련 클래스를 사용할 때 유용하다.
입출력에 사용되는 클래스 중에서는 사용한 후에 꼭 닫아 줘야 하는 것들이 있다.
- 닫아줘야 자원이 반환되기 때문이다.
try {
fis = new FileInputStream("score.dat");
dis = new DataInputStream(fis);
...
} catch (IOException ie) {
ie.printStackTrace();
} finally {
dis.close(); // 작업 중에 예외가 발생하더라도, dis가 닫히도록 finally블록에 넣는다.
}
- finally블록 안에 close()를 넣었는데, 진짜 문제는 close()가 예외를 발생시킬 수 있다는 것이다.
try {
fis = new FileInputStream("score.dat");
dis = new DataInputStream(fis);
...
} catch (IOException ie) {
ie.printStackTrace();
} finally {
try {
if (dis!=null)
dis.close();
} catch (IOException ie) {
ie.printStackTrace();
}
}
이렇게 예외 발생을 막으려고 finally 안에 try-catch블록을 추가하는 것은 보기에도 안좋은데 더 큰 문제가 있다.
- try블럭과 finally블럭에서 모두 예외가 발생하면 try 블럭의 예외는 무시된다. 결과적으로 try블럭에서 예외가 발생했는지 조차 모를 수 있게 된다
이러한 점을 개선하기 위해 try-with-resources문이 등장했다.
try (fis = new FileInputStream("score.dat");
dis = new DataInputStream(fis)) { // 괄호() 안에 두 문장 이상 넣을 경우 ';'로 구분한다
while(true) {
// 생략
}
} catch (EOFException e) {
// 생략
} catch (IOException ie) {
// 생략
} // try
try-with-resources문의 괄호() 안에 객체를 생성하는 문장을 넣으면 따로 close()를 호출하지 않아도 try블럭을 벗어나는 순간 자동적으로 close()가 호출된다.
그 다음에 catch 또는 finally블럭이 수행된다
참고) try블럭의 괄호() 안에 변수를 선언하는 것도 가능하다. (try 블럭 내에서만 사용 가능)
이처럼 try-with-resources문에 의해 자동으로 객체의 close()가 호출될 수 있으려면, 클래스가 AutoCloseable이라는 인터페이스를 구현한 것이어야만 한다
public interface AutoCloseable {
void close() throws Exception;
}
아까 위에서 try블럭과 finally블럭에서 모두 예외가 발생하면 try 블럭의 예외는 무시된다고 하였다.
- 만약 try-with-resources에서 호출된 close()에서 예외가 발생하면 어떻게 될까?
class TryWithResourceEx {
public static void main(String args[]) {
try (CloseableResource cr = new CloseableResource()) {
cr.exceptionWork(false); // 예외가 발생하지 않는다.
} catch(WorkException e) {
e.printStackTrace();
} catch(CloseException e) {
e.printStackTrace();
}
System.out.println();
try (CloseableResource cr = new CloseableResource()) {
cr.exceptionWork(true); // 예외가 발생한다.
} catch(WorkException e) {
e.printStackTrace();
} catch(CloseException e) {
e.printStackTrace();
}
} // main의 끝
}
class CloseableResource implements AutoCloseable {
public void exceptionWork(boolean exception) throws WorkException {
System.out.println("exceptionWork(" + exception + ")가 호출됨");
if (exception)
throw new WorkException("WorkException발생!!!");
}
public void close() throws CloseException {
System.out.println("close()가 호출됨");
throw new CloseException("CloseException발생!!!");
}
}
class WorkException extends Exception {
WorkException(String msg) { super(msg); }
}
class CloseException extends Exception {
CloseException(String msg) { super(msg); }
}
try-catch 첫 번째에서는 close()에서만 예외를 발생시킨다.
try-catch 두 번째에서는 exceptionWork(), close() 모두 예외를 발생시킨다
두 예외가 동시에 발생할 수 없기 때문에, 실제 발생한 예외를 WorkException으로 하고, CloseException은 억제된 예외로 다룬다.
- 억제된 예외에 대한 정보는 실제로 발생한 예외에 저장된다
사용자 정의 예외 만들기
기존에 정의된 예외 클래스 말고도 필요에 따라 프로그래머가 새로운 예외 클래스를 정의하여 사용할 수 있다.
보통 Exception 클래스로부터 상속받는 클래스를 만들지만, 필요에 따라 알맞는 예외 클래스를 선택할 수 있다.
class MyException extends Exception {
MyException(String msg) { // 문자열을 매개변수로 받는 생성자
super(msg); // 조상인 Exception클래스의 생성자를 호출한다.
}
}
필요하다면 멤버 변수나 메서드를 추가할 수 있다.
Exception클래스는 생성자에서 String값을 받아 메시지를 저장할 수 있다.
기존의 예외 클래스는 Exception을 상속받아서 'checked예외'로 작성하는 경우가 많았지만, 요즘은 예외처리를 선택적으로 할 수 있도록 RuntimeException을 상속받아서 작성하는 쪽으로 바뀌어가고 있다.
- 'check 예외'는 반드시 예외처리를 해줘야 해서 불필요한 경우에도 try-catch를 넣어야 해서 코드가 복잡해지기 때문
참조) Java의 정석 3rd edition(남궁성, 도우출판)
'Programming Language > Java' 카테고리의 다른 글
[Java] 17. Object 클래스 (0) | 2024.10.15 |
---|---|
[Java] 16. 예외 처리(Exception handling)(5) - 예외 되던지기, 연결된 예외 (0) | 2024.07.09 |
[Java] 14. 예외 처리(Exception handling)(3) - 메서드에 예외 선언하기 (0) | 2024.07.09 |
[Java] 13. 예외 처리(Exception handling)(2) - try-catch 문 (0) | 2024.07.09 |
[Java] 12. 예외 처리(Exception handling)(1) - 예외 처리란? (0) | 2024.07.09 |