[08-2] finally, try-with-resources, 사용자정의예외, 예외 던지기
finally블럭
o finally블록은 예외의 발생 여부에 상관없이 반드시 실행 되어야할 코드를 포함시킬 목적으로 사용된다.
o try-catch문에 선택적으로 덧붙여 사용할 수 있으며, try-catch-finally 순으로 구성된다.
public class Test {
public static void main(String[] args) {
try {
// 예외 발생 가능성이 있는 문장을 넣는다.
} catch{
// 예외 처리를 위한 문장을 적는다.
} finally {
// 예외에 발생여부에 관계없이 항상 수행 되어야하는 문장들을 넣는다.
// finally블럭은 try-catch문의 맨 마지막에 위치한다.
}
}
}
|
예외 발생 시 실행 순서 : try -> catch -> finally
예외 발생하지 않을 시 실행 순서 : try -> finally
|
|
- 왼쪽 예제는 예외에 관계없이 deleteTempFiles()를 사용해야 한다고 할 경우 try{ }, catch{ }에 모두 작성해야 한다.
- 그래서 finally 블록을 사용하여 오른쪽 예제와 같이 사용할 수 있다.
public static void main(String[] args) {
method1();
System.out.println("mehtod1()의 수행을 마치고 main메서드로 돌아왔습니다.");
}
static void method1() {
try {
System.out.println("method1()이 호출되었습니다.");
return;
} catch (Exception e) {
e.printStackTrace();
} finally { //try의 return문이 있어도 finally 실행 후 return
System.out.println("method1(0의 finally블럭이 실행되었습니다.");
}
}
|
- try블록에서 return문이 실행되는 경우에도 finally블록이 먼저 실행된 후에 현재 실행중인 메서드를 종료한다.
- 마찬가지로 catch블록이 수행 중에 return을 만나도 finally블록의 문장들은 수행된다.
자동 자원 반환 – try-with-resources
o try-with-resources란 선언된 객체들에 대해서 try가 종료될 때 자동으로 자원을 해제해주는 기능이다.
o try-with-resources는 JDK1.7부터 새로 추가되었으며 ‘입출력(I/O)’과 관련된 클래스를 사용할 때 유용하다.
|
|
![]() |
- 만약 try문에서 예외가 발생하였고 finally에서도 예외가 발생하였다면 try문에서 발생한 예외는 무시된다.
- 이는 메서드가 예외 처리하지 못하고 죽으면 호출한 메서드가 예외 처리를 해야 하는데 예외가 두개면 처리할 수 없기 때문이다. try-catch메커니즘의 한계라고 보면 된다.
- 이러한 점을 개선하기 위해 try-catch-resources문이 추가된 것이다.
|
|
|
- try-with-resources문의 괄호( )안에 객체를 생성하는 문장을 넣으면, 이 객체는 따로 close( )를 호출하지 않아도 try블록을 벗어나는 순간 자동으로 close( )가 호출되기에 finally에서 명시적으로 close( )를 호출해줄 필요가 없다. 그 다음에 catch블럭 또는 finally블록이 수행된다.
- try블록의 괄호 ( )안에 변수를 선언하는 것도 가능하며, 선언된 변수는 try블럭 내에서만 사용할 수 있다.
o 이처럼 try-with-resources문에 의해 자동으로 객체의 close( )가 호출될 수 있으려면, 클래스가 AutoCloseable이라는 터페이스를 구현한 것이어야만 한다. AutoCloseable은 JDK1.7부터 지원한다.
사용자정의 예외 만들기
o 기존에 정의된 예외 클래스 외에 프로그래머가 새로운 예외 클래스를 정의하여 사용할 수도 있다. 보통 Exception클래스 또는 RuntimeException클래스로부터 상속받아 클래스를 만들지만, 필요에 따라 예외 클래스를 선택할 수 있다.
class MyException extends Exception {
MyException(String msg) { //문자열을 매개변수로 받는 생성자
super(msg); //조상인 Exception클래스의 생성자를 호출한다.
}
}
|
- Exception클래스는 생성 시 String값을 받아서 메시지로 저장할 수 있다. String을 매개변수로 추가해주어 사용자 정의 예외 클래스도 메시지를 저장할 수 있도록 했다.
public class Test {
public static void main(String[] args) {
try {
throw new MyException("예외",200); //MyException 예외 발생
} catch(MyException e) {
System.out.println("MyException 예외 발생");
System.out.println(e.getMessage()+" "+e.getErrCode());
e.printStackTrace();
}
}//main 끝
}//클래스 끝
class MyException extends Exception {
private final int ERR_CODE;
MyException(String msg, int errCode) { //생성자
super(msg);
ERR_CODE = errCode;
}
MyException(String msg) { //매개변수 있는 생성자
this(msg, 100); //ERR_CODE를 100(기본값)으로 초기화한다.
}
public int getErrCode() { //에러 코드를 얻을 수 있는 메서드도 추가
return ERR_CODE; // 주로 getMessage() 함께 사용될 것이다.
}
}
|
- 이전의 코드에서 메시지뿐만 아니라 에러 코드 값도 저장할 수 있도록 멤버 변수와 메서드도 멤버로 추가했다.
- catch블록에서 getMessage()와 getErrCode()를 사용해서 에러코드와 메시지를 모두 얻을 수 있다.
public static void main(String[] args) {
try {
startInstall();
secondInstall();
copyFile();
System.out.println("try");
} catch (SpaceException e) {
System.out.println("에러 메시지 : "+e.getMessage());
e.printStackTrace();
System.out.println("설치 공간을 확보 후 다시하시길 바랍니다.");
} catch (MemoryException me) {
System.out.println("에러 메시지 : "+me.getMessage());
me.printStackTrace();
System.out.println("메모리 공간을 확보 후 다시하시길 바랍니다.");
}
}//main 끝
static void startInstall() throws SpaceException, MemoryException {
if(!enoughSpace()) throw new SpaceException("설치 공간이 부족합니다.");
if(!enoughMemory()) throw new MemoryException("설치 메모리가 부족합니다.");
}
static void secondInstall() throws SpaceException, MemoryException {
if(!enoughSpace()) throw new SpaceException("second 설치 공간이 부족합니다.");
if(!enoughMemory()) throw new MemoryException("second 설치 메모리가 부족합니다.");
}
static void copyFile() {/*파일 복사 코드*/}
static void deleteTempFiles() {/*임시파일 삭제 코드*/ }
static boolean enoughSpace() {
//설치하는데 필요한 공간이 있는지 확인하는 코드 작성
return false;
}
static boolean enoughMemory() {
// 설치 시 필요한 메모리 공간이 있는지 확인하는 코드 작성
return false;
}
}//Test클래스 끝
}
//설치 공간이 충분하지 않을 경우 발생하는 예외 클래스
class SpaceException extends Exception {
SpaceException(String msg) {
super(msg);
}
}
//메모리 공간이 충분하지 않을 경우 발생하는 예외 클래스
class MemoryException extends Exception {
MemoryException(String msg) {
super(msg);
}
}
|
- 위 예제는 프로그램 설치 시 예외 발생의 경우를 가정한 예시다. 새롭게 정의한 SpaceException, MemoryException 예외 클래스를 만들어 사용했으며 startInstall( ), secondInstall( ) 메서드에서 파일 설치 시 공간 또는 메모르가 부족한 경우 해당 예외를 발생시키도록 했다.
- startInstall( )메서드 실행 후 예외가 없으면 secondInstall( )을 실행하도록 했으나 startInstall( )에서 예외가 발생하도록 작성되었다(enoughSpace, enoughMemory의 return false).
- 예외 발생 시 적절한 catch(-finally)문을 찾아 실행 후 해당 try-catch문을 빠져나가기에(참고) startInstall( )메서드만 실행 후 try-catch문을 종료하여 다음 secondInstall( )은 실행을 하지 못한다.
- tartInstall( )에서 두 개의 if문은 모두 예외를 발생시키도록 되었으나 예외가 발생하였으나 처리가 되지 않을 경우 해당 메서드는 종료하여 호출한 메서드로 돌아가기에 두 번째 예외는 발생하지 않았다.
static void startInstall() { //예외를 직접 처리한다.
try {
if(!enoughSpace()) throw new SpaceException("설치 공간이 부족합니다.");
if(!enoughMemory()) throw new MemoryException("설치 메모리가 부족합니다.");
} catch (SpaceException e){
System.out.println(e.getMessage());
} catch (MemoryException e){
System.out.println(e.getMessage());
}
}
static void secondInstall() throws SpaceException, MemoryException {
if(!enoughSpace()) throw new SpaceException("second 설치 공간이 부족합니다.");
if(!enoughMemory()) throw new MemoryException("second 설치 메모리가 부족합니다.");
}
|
- 만약 이전의 예외에서 startInstall( )에서 발생한 예외를 직접 처리하도록 코드를 수정해보았다. startInstall( )의 첫번째 if문인 if(!enoughSpace())에서 예외가 발생하여 적절한 catch문을 찾아 ‘설치 공간이 부족합니다’를 출력하였다. 그리고 해당 try-catch문을 빠져나오기에 두번째 if문은 실행되지 않았다.
- startInstall( )에서 예외를 직접 처리하였기에 startInstall( )를 호출한 main메서드는 예외가 발생한 사실을 모른다.
- 따라서 main메서드의 try-catch문 빠져나가지 않고 다음 문장인 secondInstall( )문장을 실행하였다.
예외 던지기(exception re-throwing)
o 한 메서드에서 발생할 수 있는 예외가 여러 개인 경우 try-catch문을 통해 해당 메서드에서 직접 처리하거나 선언부에 지정(throws)하여 호출한 메서드에서 처리하도록 함으로써, 양쪽에서 처리되도록 할 수 있다.
o 심지어 단 하나의 예외에 대해서도 양쪽에서 처리되도록 할 수 있는데 이를 ‘예외 되던지기(exception re-throwing)’이라고 한다.
o 먼저 예외가 발생할 가능성이 있는 문장을 try-catch문에서 처리해준다. catch문에서 예외를 처리한 후에 throw문을 통해 예외를 다시 발생시킨다. 다시 발생한 예외는 호출한 메서드에서 처리할 수 있도록 메서드 선언부에 throws를 작성하여야 한다.
o 호출한 메서드에서도 전달받은 예외를 try-catch문으로 처리함으로써 양쪽에서 예외를 처리하게 할 수 있으며 하나의 예외에 대해 예외가 발생한 메서드, 호출한 메서드 양쪽에서 처리해줘야 할 작업이 있을 때 사용된다.
public class Test {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
System.out.println("main메서드에서 예외 처리");
}
}//main 끝
static void method1() throws Exception { //다시 예외를 넘긴다.
try {
throw new Exception();
} catch (Exception e) {
System.out.println("method1에서 예외 처리");
throw e; //다시 예외를 발생시킨다.
}
}
}//Test클래스 끝
|
- method1()과 main메서드 양쪽에 catch블록이 수행되었음을 알 수 있다. methdo1()의 catch블록에서 예외를 처리하고 throw를 통해 예외를 다시 발생시켰다.
- 그리고 이 예외를 호출한 main메서드로 넘겨줌으로써 main메서드의 try-catch문에서도 처리되었다.
public class Test {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
System.out.println("main메서드에서 예외 처리");
}
}//main 끝
static int method1() throws Exception {
try {
System.out.println("method1()이 호출되었습니다.");
System.out.println(0/0); //예외 발생 가능성 구문 작성
return 0;
} catch (Exception e) {
e.printStackTrace();
return 1;
} finally {
System.out.println("method1()의 finally블럭이 실행되었습니다.");
}
}
}//Test클래스 끝
|
- 반환 값이 있는 return의 경우, catch블록에도 return문이 있어야한다. 예외가 발생했을 경우에도 값을 반환해야 하기 때문이다.
public class Test {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
System.out.println("main메서드에서 예외 처리");
}
}//main 끝
static int method1() throws Exception {
try {
System.out.println("method1()이 호출되었습니다.");
System.out.println(0/0); //예외 발생 가능성 구문 작성
return 0;
} catch (Exception e) {
e.printStackTrace();
throw new Exception(); //return문 대신 예외를 호출한 메서드로 전달
} finally {
System.out.println("method1()의 finally블럭이 실행되었습니다.");
}
}
}//Test클래스 끝
|
- catch블록에서 예외 되던지기를 해서 호출한 메서드로 예외를 전달하면, return문이 없어도 된다.
※ finally블럭 내에도 return문을 사용할 수 있으며, try블록이나 catch블록의 return문 다음에 수행된다. 최종적으로 finally블록 내의 return문의 값이 반환된다.
연결된 예외(chained exception)
o 원인 예외(cause exception)란 한 예외가 다른 예외를 발생시키는 것을 말하는데 예를 들어 예외 A가 예외 B를 발생시켰다면 A를 B의 원인 예외(cause exception)라고 한다.
o catch문에서 발생시킬 다른 예외를 생성 후 catch조건의 예외를 원인 예외로 지정(initCause)한다. throw를 통해 새로 생성한 예외를 발생시킨다. 새로운 예외를 발생시켜 호출한 메서드로 전달하기에 메서드 선언부에 ‘throws [예외]’가 등록이 되어있어야 한다.
|
|
![]() |
|
Throwable initCause(Throwable cause) : 지정한 예외를 원인 예외로 등록 Throwable getCause() : 원인 예외를 반환 |
- initCause을 통해 예외의 원인을 지정된 값(참조변수 e)으로 초기화 함으로써 SpaceException인스턴스의 정보가 저장되었다.
o 발생한 예외를 처리하지 않고 원인 예외로 등록해서 처리하는 이유는 여러가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위해서이다.
o 만약 InstallException을 SpaceException과 MemoryException의 조상으로 해서 catch블록을 작성하면, 실제로 발생한 예외가 어떤 것 인지 알 수 없는 문제가 있으며 InstallExceptio과 SpaceException의 상속관계를 변경해야 하는 부담도 있다.
o 그러나 예외가 원인 예외를 포함할 수 있게 하면 두 예외는 상속관계가 아니어도 상관없다.
|
|
![]() |
o 원인 예외를 사용하는 또 다른 이유는 checked예외를 unchecked예외로 바꿀 수 있도록 하기 위해서다. 자바가 처음 개발되던 때와 컴퓨터 환경이 많이 달라져서 checked예외가 발생해도 처리할 수 없는 상황들이 하나씩 발생하기 시작했다. 이럴 때 의미 없는 try-catch문을 추가하는 것뿐이다. 그러나 check예외를 uncheck예외로 바꾸면 예외 처리가 선택적이 되기에 억지로 예외처리를 하지 않아도 된다.
checked 예외 |
|
||
unchecked 예외 |
|
- MemoryException은 Exception의 자손이므로 반드시 예외 처리를 해야 하는데 RuntimeException으로 감싸버렸기에 unchecked예외가 되었다.
- 그래서 startInstall( ) 매서드 선언부의 throws에 선언하지 않아도 된다. initCause( )대신 RuntimeException의 생성자를 사용했다. [RuntimeException (Throwable cause) //원인 예외를 등록하는 생성자]
package codingTest;
public class Test {
public static void main(String[] args) throws InstallException{
try {
install(); //InstallException 전달 받음
copyFiles();
} catch (InstallException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}//main 끝
static void install() throws InstallException {
try {
startInstall(); //프로그램 설치 준비
copyFiles(); //파일 복사
} catch (SpaceException se) {
InstallException ie = new InstallException("설치 중 예외발생");
ie.initCause(se);
throw ie;
} catch (MemoryException me) {
InstallException ie = new InstallException("설치 중 예외발생");
ie.initCause(me);
throw ie;
} finally {
deleteTempFiles(); //프로그램 설치에 사용된 임시파일 삭제
}
}
static void startInstall() throws SpaceException, MemoryException {
if(!enoughSpace()) throw new SpaceException("설치 공간이 부족합니다.");
if(!enoughMemory()) throw new MemoryException("설치 메모리가 부족합니다.");
// if(!enoughMemory()) throw new RuntimeException(new MemoryException("메모리가 부족합니다."));
}
static void copyFiles() {/*파일 복사 코드*/}
static void deleteTempFiles() {/*임시파일 삭제 코드*/ }
static boolean enoughSpace() {
//설치하는데 필요한 공간이 있는지 확인하는 코드 작성
return false;
}
static boolean enoughMemory() {
// 설치 시 필요한 메모리 공간이 있는지 확인하는 코드 작성
return false;
}
}//Test클래스 끝
//설치 공간이 충분하지 않을 경우 발생하는 예외 클래스
class SpaceException extends Exception {
SpaceException(String msg) {
super(msg);
}
}
//메모리 공간이 충분하지 않을 경우 발생하는 예외 클래스
class MemoryException extends Exception {
MemoryException(String msg) {
super(msg);
}
}
class InstallException extends Exception {
InstallException(String msg) {
super(msg);
}
}
|