728x90

# 자바의 예외 처리에 대해 학습하세요.


# 학습할 것

  • 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
  • 자바가 제공하는 예외 계층 구조
  • Exception과 Error의 차이는?
  • RuntimeException과 RE가 아닌 것의 차이는?
  • 커스텀한 예외 만드는 방법

 

자바에서 예외 처리 방법 (try, catch, throw, throws, finally), 커스텀한 예외 만드는 방법


자바에서 예외를 처리하는 방법은 다양하다.

 

try ~ catch 를 사용해서 예외를 직접 처리하는 방법.

try ~ catch ~ finally 를 사용해서 예외를 직접 처리하고, 예외가 발생해도 꼭 해야하는 후속처리까지 하는 방법.

throw를 사용해서 강제로 예외를 발생하는 방법.

에라 모르겠다. throws를 사용해서 나를 호출한 녀석에게 예외를 던지는 방법 등이 있다.

 

package me.xxxelppa.study.week09;
 
public class Exam_001 {
    public static void main(String[] args) {
        
        try {
            
            // 예외가 발생할 수 있는 코드를 try 블록 안에 작성
            
        } catch(Exception e) {
            
            // 발생한 예외를 처리할 수 있는 코드를 catch 블록 안에 작성
            // 예외가 발생하지 않으면 이 블록의 내용을 실행하지 않는다.
            
        }
    }
}

 

사실 처음 예외를 보면 finally 가 그다지 쓸모 없어 보이기도 한다.

finally 를 사용하는 다양한 경우가 있겠지만, 데이터베이스를 사용할 때 많이 사용한다.

 

하지만 데이터베이스에 대해 설명하려면 너무 복잡해지기 때문에 상황을 예로 들어보자.

 

냉장고 문을 열었으면 문을 다시 닫고, 음식을 먹었으면 설거지를 해놓는게 보통이다.

 

package me.xxxelppa.study.week09;
 
public class Exam_002 {
    public static void main(String[] args) {
        
        boolean isSnack = true;
        boolean isMilk = true;
        boolean isFood = true;
        
        try {
            
            // 냉장고 문을 연다.
            
            // 간식을 찾는다.
            
            // 간식이 없으면 예외 발생
            if(!isSnack) throw new SnackNotFoundException();
            
            // 우유가 없으면 예외 발생
            if(!isMilk) throw new MilkNotFoundException();
            
            // 먹을게 없으면 예외 발생
            if(!isFood) throw new FoodNotFoundException();
            
            // 간식을 꺼낸다.
            
            // 냉장고 문을 닫는다.
            
        } catch (SnackNotFoundException snfe) {
            
            // 간식이 없으면 실행하는 블록
            
            // 냉장고 문을 닫는다.
            
        } catch (MilkNotFoundException mnfe) {
            
            // 우유가 없으면 실행하는 블록
    
            // 냉장고 문을 닫는다.
            
        } catch (FoodNotFoundException mnfe) {
            
            // 먹을게 없으면 실행하는 블록
    
            // 냉장고 문을 닫는다.
            
        }
    }
}
 
class SnackNotFoundException extends Exception { }
class MilkNotFoundException extends Exception { }
class FoodNotFoundException extends Exception { }

 

try 블록 내에서 코드를 실행하는 도중 예외가 발생하면, try 블록 마지막에 실행하는 냉장고 문을 닫는 일을 할 수 없다.

먹고 싶은게 없다고 냉장고 문을 계속 열어두면, 등짝맞기 딱 좋다.

 

그래서 문을 닫아야 하는데, 모든 예외 상황에 문을 닫는다고 하려니 같은 내용이 반복 된다.

만약 냉장고 문을 그냥 닫지 않고, 이것좀 와서 보라고 한 다음에 냉장고 문을 닫고 싶으면 네곳을 고쳐야 한다.

슬픈일이 아닐 수 없다.

 

또 어쩌면 실수로 특정 예외 상황에서 냉장고 문을 닫는것을 깜빡하고 빠뜨릴 수도 있다.

등짝이 남아나지 않을거다.

 

 

728x90

 

 

 

예외가 발생해도 하지 않아도 무조건 냉장고 문을 닫아야 등짝이 터지지 않을 수 있기 때문에 finally 블록을 사용한다.

 

package me.xxxelppa.study.week09;
 
public class Exam_003 {
    public static void main(String[] args) {
        
        boolean isSnack = true;
        boolean isMilk = true;
        boolean isFood = true;
        
        try {
            
            // 냉장고 문을 연다.
            
            // 간식을 찾는다.
            
            // 간식이 없으면 예외 발생
            if(!isSnack) throw new SnackNotFoundException();
            
            // 우유가 없으면 예외 발생
            if(!isMilk) throw new MilkNotFoundException();
            
            // 먹을게 없으면 예외 발생
            if(!isFood) throw new FoodNotFoundException();
            
            // 간식을 꺼낸다.
            
        } catch (SnackNotFoundException snfe) {
            
            // 간식이 없으면 실행하는 블록
            
        } catch (MilkNotFoundException mnfe) {
            
            // 우유가 없으면 실행하는 블록
            
        } catch (FoodNotFoundException mnfe) {
            
            // 먹을게 없으면 실행하는 블록
            
        } finally {
            
            // 냉장고 문을 닫는다.
        }
    }
}
 
class SnackNotFoundException extends Exception { }
class MilkNotFoundException extends Exception { }
class FoodNotFoundException extends Exception { }

 

그리고 자연스럽게 넘어갔는데, 특정 예외를 강제로 발생하고 싶을 때 throw 를 사용할 수 있다.

이 throw 를 사용할 경우 크게 두 가지 방법으로 처리할 수 있다.

 

하나는 위에서 예시로 만든 것처럼 catch 블록을 사용해서 직접 후처리를 하는 것이고

다른 하나는 메소드에 throws 키워드를 사용해서 나를 호출한 녀석에게 예외를 처리 해달라고 던져버리는 것이다.

 

이 두번째 방법을 예외 전파라고도 한다.

 

package me.xxxelppa.study.week09;
 
public class Exam_004 {
    public static void main(String[] args) {
        try {
            myMethod_1();
        } catch (Exception e) {
            
            // 예외가 전파되어온 스택을 출력한다.
            e.printStackTrace();
        }
    }
    
    public static void myMethod_1() throws Exception {
        myMethod_2();
    }
    
    public static void myMethod_2() throws Exception {
        myMethod_3();
    }
    
    public static void myMethod_3() throws Exception {
        int a = 10;
        int b = 0;
        
        // 0으로 나누면 예외가 발생한다.
        a = a / b;
    }
}

 

java.lang.ArithmeticException: / by zero

at me.xxxelppa.study.week09.Exam_004.myMethod_3(Exam_004.java:25)
at me.xxxelppa.study.week09.Exam_004.myMethod_2(Exam_004.java:17)
at me.xxxelppa.study.week09.Exam_004.myMethod_1(Exam_004.java:13)
at me.xxxelppa.study.week09.Exam_004.main(Exam_004.java:6)

 

실행 결과를 보면 최초로 예외가 무엇 때문에 어디서 발생 했는지 call stack 을 출력해서 보여주고 있다.

 

 

개인적인 생각이지만 일반적으로 사람들은 똑같은 일을 반복하는걸 별로 좋아하지 않는다.

특히 개발자들은 더 그런것 같다.

 

예외를 처리할 때 try ~ with ~ resource 라는게 있다.

자바 7부터 등장했는데, 다음과 같이 생겼다.

 

package me.xxxelppa.study.week09;
 
public class Exam_005 {
    public static void main(String[] args) {
        
        boolean isSnack = true;
        boolean isMilk = true;
        boolean isFood = true;
        
        try (Refrigerator refrigerator = new Refrigerator()){
            
            // 냉장고 문을 연다.
            
            // 간식을 찾는다.
            
            // 간식이 없으면 예외 발생
            if(!isSnack) throw new SnackNotFoundException();
            
            // 우유가 없으면 예외 발생
            if(!isMilk) throw new MilkNotFoundException();
            
            // 먹을게 없으면 예외 발생
            if(!isFood) throw new FoodNotFoundException();
            
            // 간식을 꺼낸다.
            
        } catch (Exception e) {
            if(e instanceof SnackNotFoundException) {
                // 간식이 없으면 실행하는 블록
            } else if(e instanceof MilkNotFoundException) {
                // 우유가 없으면 실행하는 블록
            } else if(e instanceof FoodNotFoundException) {
                // 먹을게 없으면 실행하는 블록
            }
            
            e.printStackTrace();
        } 
    }
}
 
class Refrigerator implements AutoCloseable {
    @Override
    public void close() throws Exception {
        // 냉장고 문을 닫는다.
    }
}
 
class SnackNotFoundException extends Exception { }
class MilkNotFoundException extends Exception { }
class FoodNotFoundException extends Exception { }

 

10라인에 try 블록에 소괄호 블록이 생겼다.

이 소괄호 블록 안에서 생성한 객체는 finally 에서 후처리를 해주지 않아도 후처리를 해준다.

대신 조건이 있다.

 

41라인에 해당 클래스가 정의되어 있는 것을 보면, AutoCloseable 라는 인터페이스를 구현하고 있는것을 볼 수 있다.

이 인터페이스를 정의하고 있는 클래스라면 위와같이 조금은 더 간결하게 코드를 작성할 수 있다.

 

 

마지막으로 커스텀한 예외는 위에서 예제를 코드를 만들어 보면서 작성 했다.

 

바로 다음 절에서 Checked 와 Unchecked 예외에 대해 설명 하지만,

Checked 예외 상황을 만들 경우 Exception을 포함한 자식 타입 클래스를, Unchecked 예외 상황을 만들 경우 RuntimeException을 포함한 자식 타입 클래스를 상속 받으면 된다.

 

package me.xxxelppa.study.week09;
 
public class Exam_006 {
    public static void main(String[] args) throws MyException {
        
        throw new MyException("내가 만든 커스텀한 예외");
        
    }
}
 
class MyException extends Exception {
    
    // super 생성자를 사용하면
    // Exception 클래스의 부모인 Throwable 클래스의 생성자를 호출한다.
    
    public MyException() {
        super();
    }
    
    public MyException(String message) {
        super(message);
    }
}

 

Exception in thread "main" me.xxxelppa.study.week09.MyException: 내가 만든 커스텀한 예외

at me.xxxelppa.study.week09.Exam_006.main(Exam_006.java:6)

 

 

자바가 제공하는 예외 계층 구조, Exception과 Error의 차이는?, RuntimeException과 RE가 아닌 것의 차이는?


자바 api 문서를 참고해보면 모든 클래스의 최상위 부모인 Object 를 상속 받은 Throwable 을 상속 받은 Exception 과 Error 가 있다.

 

Error는 메모리가 부족하다던가 등의 JVM에 문제가 생겼을 경우에 발생하고,

Exception은 프로그램 코드 레벨에서 문제가 생겼을 경우에 발생하는 편이다.

 

Exception 은 또 크게 두가지로 구분해서 생각할 수 있는데, Checked 와 Unchecked 이다.

 

검색해보니 잘 정리된 좋은 그림이 있어서 가져와보았다.

 

** 출처 : https://madplay.github.io/post/java-checked-unchecked-exceptions

 

 

Unchecked 예외는 RuntimeException (런타임 예외) 라고도 하는데, 보통 프로그램 실행중에 발생하며, 발생할 것을 미리 알 수 없다는 특징이 있다.

그리고 이 예외는 Exception 을 상속 받은 RuntimeException 을 상속 받은 클래스이다.

 

 

반면 Checked 예외는 RuntimeException이 아닌 예외를 말한다.

 

이러한 예외는 프로그램 실행중에 갑자기 발생하는 것이 아니기 때문에 미리 대처해둘 수 있다는 특징이 있다.

그렇기 때문에 RE가 아닌 (Unchecked Exception이 아닌) 예외에 대해서는 코드에서 명시적으로 예외 처리를 해주어야 한다.

 

 

 

 

728x90

'Archive > Java online live study S01' 카테고리의 다른 글

11주차 : Enum  (0) 2021.05.01
10주차 : 멀티쓰레드 프로그래밍  (0) 2021.05.01
8주차 : 인터페이스  (0) 2021.05.01
7주차 : 패키지  (0) 2021.05.01
6주차 : 상속  (0) 2021.05.01

+ Recent posts