Java/live-study

[Java] 백기선 라이브스터디 9주차 : 예외 처리

jay Joon 2021. 1. 14. 06:56

목표

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

학습할 것 (필수)

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

오류의 종류

일단 프로그램 오류의 종류는 다음과 같이 3가지로 분류가 된다.

 

프로그래밍 오류의 종류

 

1. 컴파일 에러

   -> 구문 에러라고 볼 수 있는데 프로그램이 시작 전에 잡을 수 있는 에러라고 보면 된다.

컴파일 에러 예시

 

2. 런타임 에러

   -> 프로그램 실행 중에 나타나는 에러로 대체로 Null point error , 0으로 나누기를 실행하는 경우가 있다.

 

0 으로 나누기를 하는 경우

 

3. 논리적 에러

   -> 프로그램이 원하는 기댓값이 나오지 않는 경우 즉 프로그래머가 잘못한 경우가 거의 99.9%

 


자바가 제공하는 예외 계층 구조

 

 

자바의 예외 계층 구조는 다음 그림과 같다.

 

예외 계층구조

출처: online.infomatics.info/Course/Core-Java/Types-of-Exception

 

Throwable Class의  서브 클래스 인 Exception과 Error로 나뉩니다. 

 

여기서 좀 더 자세히 알아보자면

 

Throwable 

  -> 자바에서 예외처리를 하기 위한 최상위 클래스이며 이 클래스를 상속받은 서브클래스 만이 thows 키워드를 통해

      전파될 수 있습니다.

 

 

Error 

  -> 동적 연결 실패 또는 다른 심각한 오류가 자바 가상 머신(JVM)에서 발생했을 때 발생하는 오류임으로 대부분 JVM에서 발생하는 오류들이다. (예시 예외는 예외 계층구조 그림에 있습니다.)

 

 

Exception

  -> 런타임과 컴파일 시간에 발생할 수 있는 예외를 잡기 위한 예외 클래스(?) 주로 개발자가 작성한 코드에서 예외를 잡기 위한 클래스  크게 UnChecked exception , Checked exception으로 나뉜다.

 


UnChecked exception , Checked exception 차이점

 

자바에선 Exception 클래스의 서브클래스 중에서 RuntimeException을 상속하지 않은 나머지 예외 클래스들은 체크 예외라고 생각해도 된다. 

 

체크 예외와 언체크 예외의 차이점은 다음과 같다.

UnChecked exception Checked exception
예외처리를 강요하지 않음  예외처리를 반드시 해야함
런타임 단계에서만 확인이 가능함(실행 후 알 수 있다) 컴파일 단계에서 확인이 가능함(실행 전에 알 수 있다)

 

소스코드로 보자.

 

파라미터 값 이 1000이 넘으면 계산하지 못하는 이상한 계산기..

여기 계산기가 있다. 해당 계산기는  x 값 또는 y값이 1000 이상 넘어가면 계산을 못한다고 가정해보자.

 

Exception 을 상속 받고있음

ThousandOverException 은 Exception의 서브클래스 임으로  Checked exception 가 된다.

 

다음과 같이 main 메서드에서 사용하려고 하면 예외처리를 강요한다.

강요 당함..

 

 

하지만 개발자들이 이 이상한 계산기는 1000 이상의 값은 계산 못한다고 암묵적으로 알고 있을 때는

 

예외처리를 강요하는 것이 효율적일까?

 

무언가의 규약을 알고 있다면 강요하지 않는 편이 소스코드를 좀 더 깔끔하게 작성할 수 있을 것이다.

 

따라서 그런 경우 UnChecked exception을 이용하는 것이 좋을 것이다.

RuntimException 으로 변경
더이상 강요받지 않는다 

UnChecked exception 도 원한다면 예외처리를 할 수 있다.  

 


예외처리 방법

 

예외 처리 방법으로는

 

1. 예외 복구

2. 예외처리 회피

3. 예외 전환 

 

이렇게 크게 3가지가 있다. 하나씩 알아보자.

 

 

예외 복구 

 -> 예외 상황을 체크하여 해당 문제를 해결해서 정상 상태로 돌려놓는 방법이다. 

 

예제를 보자.

public class Solution {
    public static void main(String[] args) {
        List<Objec> objectNumberList  =new ArrayList<>();

        for (int i = 0; i < 10; i++) {
            objectNumberList.add(i);
            objectNumberList.add(String.valueOf(i));
        }
    }
}

현재 List(objectNumberList) 안에 String, int 형이 같이 공존하고 있다.

 

이러한 경우 해당 List 안에 있는 값을 int 형으로 변경하여 List 안에 있는 값들을 모두 더하고 싶다고 할 때..

 public static void main(String[] args) {
  		
       // data setting 생략 
        
        int result = 0;
        for (Object o : objectNumberList) {
            int o1 = (int) o;
            result+=o1;
        }
    }

다음과 같은 코드를 작성할 것이다. (instanceof  를이용하여 분류할 수 도 있지만 예제 임으로 이해해주세요

 

하지만 해당 코드는 다음과 같은 오류가 발생한다.

String 형을 int 형으로 형변환을 하지 못하여 발생하는 오류

당연한 오류이다.

String 형을 int 형으로 형 변환을 하지 못하여 발생하는 오류 임으로 해당 오류를 복구하여

 

정상적인 프로그램으로 변경해보자.

public static void main(String[] args) {

      	// data setting 생략 
        
        int result = 0;
        for (Object o : objectNumberList) {
            try {
                int i = (int) o;
                result+=i;
            }catch (ClassCastException e){
                String s  = (String)o;
                int i = Integer.parseInt(s);
                result+=i;
            }
        }
        
    }

(for문안에 try-catch 문을 작성하는 것은 매우 안 좋음으로 실제 프로그래밍을 할 때는 쓰지 않는 것을 권장합니다)

 

 

예외처리 회피

 -> 예외처리를 담당하지 않고 자신 호출한 쪽으로 던져버리는 경우이다.

 

아까 계산기의 코드를 보자.

public class Calculate {

    static int sum(int x, int y) throws ThousandOverException {

        if(x > 1000 || y>1000) throw  new ThousandOverException();

        return x+y;
    }

}

예외가 발생하면 throws 키워드를 통해  해당 method를 호출한 쪽으로 던지고 있다.

 

매우 무책임한 코드 임으로 항상 사용할 때 최선의 방법일 때만 사용하도록 하자.

 

 

예외 전환 

 ->  적절한 예외로 전환해서 넘기는 방법

 

예를 들어보자.

 

체크 예외는 반드시 예외처리를 강요받는다.

 

이 뜻은 체크 예외를 던지는 Method를 사용하는 계층에서 일일이 구현해줘야 하는 번거로운 일이 발생한다.

 

하지만 체크 예외를 던지는 Method 안에서 언체크 예외를 던진다면?

 

다른 계층에서는 일일이 구현해주지 않아도 간편하게 사용할 수 있다.

 

public void add(User user) throws DuplicateUserIdException, SQLException {
    try {
        // ...생략
    } catch(SQLException e) {
        if(e.getErrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY) {
            throw DuplicateUserIdException();
        }
        else throw e;
    }
}

커스텀한 예외 만드는 방법

해당 예외는 앞서 UnChecked exception , Checked exception 차이점 부분에서 자세히 무슨 역할을 하는지 설명하였음으로 넘어가겠습니다.

 

 

 

Checked exception 예외 만들기(Exception 을 상속하여 만든다)

public class ThousandOverException extends Exception {
    @Override
    public String getMessage() {
        return "1000을 넘길수 없습니다";
    }
}

 

UnChecked exception 예외 만들기(RumtimeException 을 상속하여 만든다)

public class ThousandOverException extends RuntimeException {
    @Override
    public String getMessage() {
        return "1000을 넘길수 없습니다";
    }
}

 

 

필요한 기능을 재정의 하여 사용하여 커스텀 예외를 만들 수 있다

 


자바에서 예외 처리 방법 (try, catch, throw, throws, finally)

try{
    //에러가 발생할 수 있는 코드
}catch (Exception e){
    //에러시 수행
     e.printStackTrace(); //오류 출력
     throw e; // 오류를 발생하는 키워드
}finally{
    //무조건 수행
} 

 

throws

  -> 현재 메서드에서 자신을 호출한 상위 메서드로 Exception을 발생시킴

 

throw

  -> 에러를 발생시키고자 할 때 사용(현재 메서드, 혹은 상위 메서드로)

 

 

자바 7 부터 추가된멀티 catch , 자동 리소스 닫기(try-catch-resources)

 

1. 멀티 catch

catch 블록에서 여러 개의 예외를 처리할 수 있도록 멀티 catch 기능이 추가 되었다.

동일하게 처리 하고 싶은 예외를 | 로 연결하면 된다.

   try{
     ....
    }catch(ArrayIndexOutOfBoundsException | NumberFormatException e){ 
    ....
}
    

 

2. 자동 리소스 닫기(try-catch-resources)

finally에서 항상 처리해줘야했던 리소스 객체(입출력 스트림, 소켓 등) 을 자동으로 정리 해준다

 

주의)해당 리소스 객체가 java.lang.AutoCloseable 인터페이스를 구현하고 있어야 한다

 

Scanner 클래스를 한번보자.

보면 Closeable 을 구현 하고 있음으로 명시적으로 호출 하지 않아도 자동으로 리소스를 정리 해준다.

 

Closeable 인터페이스는  다음과같이 AutoCloseable 을 상속받고있다.