반응형
finally블럭
예외의 발생여부 상관없이 실행해야할 코드를 포함시킬 목적으로 사용된다.
- try-catch문의 끝에 선택적으로 덧 붙여 사용할 수 있다.
- try-catch-finally의 순서로 구성된다.
try {
//예외가 발생할 가능성이 있는 문장
} catch (발생할거같은 Exception클래스 e1(예외 인스턴스를 가르키는 참조변수){
//예외처리를 위한 문장
} finally {
//예외의 발생여부에 관계없이 항상 수행되어야하는 문장들을 넣는다.
//finally블럭은 try-catch문의 맨 마직막에 위치해야 한다.
}
- 예외가 발생한 경우 ‘try → catch → finally’의 순으로 실행된다.
- 예외가 발생하지 않은 경우 ‘try → finally’의 순으로 실행된다.
try {
install(); //프로그램 설치에 필요한 준비
copyFiles(); //파일들을 복사한다.
// deleteTempFiles(); //try에서 예외가 발생할 경우 이문장 수행이 되지 않아서 finally 넣기
} catch (Exception e){
e.printStackTrace();
} finally {
deleteTempFiles(); //프로그램 설치에 사용된 임시파일들을 삭제한다.
}
finally블럭은 왜 쓸까?
- 설치를 정상적으로 마쳐도 임시파일을 삭제해야 하고 중간에 예외가 발생해도 임시파일 삭제해야 된다.
- try블럭 안에 return문이 있어서 try블럭을 벗어나갈 때도 finally블럭이 실행된다.
- 이런 느낌? → try블럭 예외발생 ? catch로 이동(instanceof 연산자로 하나씩 찾기) : finally이동
사용자 정의 예외 만들기
기존의 정의된 예외 클래스 외에 필요에 따라서 프로그래머가 새로운 예외 클래스를 정의하여 사용할 수 있다.
- 보통은 Exception클래스 또는 RuntimeException클래스로 부터 상속받는 클래스를 만든다.
- 필요에 따라서 알맞은 예외 클래스를 선택할 수 있다.
- 멤버변수나 메서드를 추가할 수 있다.
public class MyException extends RuntimeException{
// 에러 코드 값을 저장하기 위한 필드 추가
private final int ERR_CODE; //생성자를 통해 초기화
MyException(String msg, int errCode) { // 문자열을 매개변수로 받는 생성자
super(msg); // 부모인 RuntimeException클래스의 생성자를 호출한다.
ERR_CODE = errCode;
}
MyException(String msg) {
this(msg,100); // ERR_CODE를 100(기본값)으로 초기화
}
public int getERR_CODE() {// 에러 코드를 얻을 수 있는 메서드 추가
return ERR_CODE; // 이 메서드는 주로 getMessage()와 함께 사용
}
}
- String을 매개변수로 받는 생성자를 추가해야 생성시 String값을 받아서 내가 만든 예외 클래스도 메시지로 저장할 수 있다.
- ERR_CODE, getERR_CODE()를 통해서 에러코드 값도 저장 가능하다.
- MyException이 발생했을 때, catch블럭에서 getMessage()와 getERR_CODE()를 사용해서 에러코드와 메시지를 모두 얻을 수 있다.
try {
throw new MyException("고의",500);
} catch (MyException re) {
System.out.println("에러 메시지 = " + re.getMessage() +
", 에러코드 = " + re.getERR_CODE());
} finally {
System.out.println("finally block");
}
/*
에러 메시지 = 고의, 에러코드 = 500
finally block
*/
💡 기존의 예외 클래스는 주로 Exception을 상속받아서 ‘checked예외’로 작성하는 경우가 많았지만, 요즘은 예외처리를 선택적으로 할 수 있도록 RuntimeException을 상속받아서 작성하는 쪽으로 바뀌고 있다.
- ‘checked예외’는 반드시 예외처리, 혹은 throws를 동해 선언을 해주어야 하기 때문에 예외처리가 불필요한 경우에도 try-catch문을 넣어서 코드가 복잡해지기 때문이다.
한 메서드 내에서 발생할 수 있는 예외가 여럿인 경우, 몇개는 메서드 내에서 예외처리하고 나머지는 선언부에(throws) 지정하여 호출한 메서드에서 처리하도록 양쪽에서 나눠서 처리할 수 있다.
- 단 하나의 예외에 대해서도 예외가 발생한 메서드와 호출한 메서드, 양쪽에서 처리할 수 있다.
- 예외 되던지기를 통해 가능하다.
예외 되던지기(exception re-throwing)
하나의 예외에 대해서 예외가 발생한 메서드와 이를 호출한 메서드 양쪽 모두에서 처리해줘야 할 작업이 있을 때 사용한다. (예외를 처리한 후에 인위적으로 다시 발생시는 것)
- 예외가 발생할 가능성이 있는 메서드에서 try-catch문을 사용해 예외처리 해준다.
- catch문에서 필요한 작업을 행한 후 throw문을 사용해서 예외를 다시 발생시킨다.
- 다시 발생한 예외는 이 메서드를 호출한 메서드에게 전달된다.
- 호출한 메서드의 try-catch문에서 예외를 또다시 처리한다.
public static void main(String[] args) {
try {
method();
} catch (Exception re) {
System.out.println("main메서드에서 예외처리");
}
}
static void method() throws Exception{ //선언부에 발생할 예외 지정해주기
try {
throw new Exception("고의");
} catch (Exception e){
System.out.println("method에서 예외처리");
throw e; //다시 예외 발생시키기
}
}
/*
method에서 예외처리
main메서드에서 예외처리
method()의 catch 블럭에서 예외를 처리하고도 throw문을 통해 다시 예외를 발생시키고
다시 발생한 예외를 main메서드에서 한 번 더 처리했다. (선언부에 발생할 예외 지정해주기)
*/
💡 예외가 발생할 메서드에서는 try-catch문을 사용해서 예외처리 해줌과 동시에 메서드의 선언부에 발생한 예외를 throws에 지정해줘야 한다.
static int method() throws ArithmeticException{
try {
System.out.println(0/0);
return 0;
} catch (ArithmeticException e){
return 1;
}
}
💡 반환값이 있는 return문의 경우, catch블럭에도 return문이 있어야 한다. 예외가 발생했을 경우에도 값을 반환해야 하기 때문이다.
연결된 예외(chained exception)
한 예외가 다른 예외를 발생시킬 수 있다.
- 예외 A가 예외 B를 발생시켰다면 예외 A를 B의 ‘원인 예외(cause exception)’라고 한다.
try {
int x = 0/0; // ArithmeticException 발생
} catch (ArithmeticException e){
NullPointerException ne = new NullPointerException("x로 인해 예외발생");
//예외 생성
ne.initCause(e);
//NullPointerException의 원인 예외를 ArithmeticException 지정
throw ne;
//NullPointerException을 발생시킨다.
}
initCause()는 Exception클래스의 조상인 Throwable클래스에 정의되어 있기 때문에 모든 예외에서 사용가능
- Throwable intitCause(Throwable cause) 지정한 예외를 원인 예외로 등록
- Throwable getCause() 원인 예외를 반환
// 위 예제 출력내용
Exception in thread "main" java.lang.NullPointerException: x로 인해 예외발생
at Anonymous.main(Anonymous.java:11)
Caused by: java.lang.ArithmeticException: / by zero //원인 예외
at Anonymous.main(Anonymous.java:9)
발생한 예외를 원인 예외로 등록해서 다시 예외를 발생시키는 이유
- 여러가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위해서
예외가 원인 예외를 포함하게 한 이유 (두 예외가 상속관계가 아니어도 상관없다.)
- 예를들어 ArithmeticException을 NullPointerException 부모로 해서 catch블럭을 작성한다면?
- 실제로 발생한 예외가 어떤 것인지 알 수 없다는 문제가 생긴다. p.312
- 묶어서 다루는 예외가 많아질수록 NullPointerException, Exception1, …n의 상속관계를 변경해야 한다는 것도 부담이다.
- checked예외를 unchecked예외로 바꾸기 위해서 사용한다.
- checked예외로 예외처리를 강제한 이유는 프로그래밍 경험이 적은 사람도 보다 견고한 코드 작성을 유도하기 위해서 이다.
- 컴퓨터 환경이 달라지면서 checked예외가 발생해도 예외를 처리할 수 없는 상황이 발생하기 시작했다.
- try-catch문을 추가하지 않고 unchecked예외로 바꿔서 억지로 예외처리를 하지 않게 하기위해 사용
static void test(int x) throws IOException, ClassNotFoundException{
if (x == 1)
throw new IOException();
if (x == 0)
throw new ClassNotFoundException();
}
//checked예외(컴파일x) 선언부에 throws 예외선언 -> IOException, ClassNotFoundException
static void test(int x) throws IOException{
if (x == 1)
throw new IOException();
if (x == 0)
throw new RuntimeException(new ClassNotFoundException());
}
//unchecked예외로 감싸기 (선언부에 throws ClassNotFoundException 안해줘도 된다.)
if (x == 0){
RuntimeException e = new RuntimeException();
ClassNotFoundException e1 = new ClassNotFoundException();
e.initCause(e1);
throw e;
}
//위에 코드는 initCause()대신 RuntimeException의 생성자를 사용했다.
ClassNotFoundException예외를 RuntimeException으로 감싸버렸기 때문에 unchecked예외가 되었다.
💡 RuntimeException(Throwable cause) // 원인 예외를 등록하는 생성자
이전글
반응형
'책 > Java의 정석' 카테고리의 다른 글
[java] Object클래스 (0) | 2023.02.09 |
---|---|
[java] String 클래스와 StringBuffer (0) | 2023.02.09 |
[java] 예외처리(2) (0) | 2023.02.07 |
[java] 예외처리(1) (0) | 2023.02.07 |
[java] instanceof 연산자 (0) | 2023.02.07 |