<Java> Check 예외 vs Uncheck 예외

해당 포스팅을 진행하기 전에 자바 기본 문법 예외에 대한 참고를 하시고 싶으시면 해당 포스트를 참고 하시길 바랍니다.

Java 예외 계층

본격적으로 check, uncheck 예외를 알아보기전에 큰 틀에서의 자바 예외 계층에 대해 알아봅시다.다들 아시다시피 아래의 계층으로 자바 예외는 구성되었습니다.하나 하나씩 살펴보겠습니다.

김영한 - 스프링 DB 1편
  • Object : 예외 또한 클래스이기에 너무나 당연하게 Object 클래스의 자식입니다.즉, 모든 예외의 최상위 부모는 Object라고 할 수 있습니다.
  • Throwable : Exception과 Error를 포함하는 클래스입니다.최상위 예외라고 볼 수 있습니다.
  • Error : 메모리 부족이나 심각한 시스템의 오류로부터 발생하는 예외입니다.개발자가 로직으로 컨트롤 할 수 없는 예외라고 생각하시면 됩니다.곰곰히 생각해보시면 Error 예외를 예외 처리해보신 적은 없으실 것입니다.이러한 이유에서 Throwable 예외도 예외처리해서는 안됩니다.
  • Exception : 개발자가 로직에서 예외처리할 수 있는 실질적인 최상위 예외입니다.이 RuntimeException을 제외하면 체크예외입니다.
  • RuntimeException : 흔히 런타임 에러로 불리고 uncheck 예외로도 불리는 예외입니다.대표적으로 NPE가 있습니다.

Java에서 예외를 다루는 방식

본격적으로 예외를 배우기전에 Java에서는 어떤식으로 예외를 처리하는지 알아봅시다.다들 폭탄 돌리기 게임을 알고 계시나요?자바 또한 이와 같은 방식이라고 생각하시면 됩니다.어떠한 메서드에서 예외가 발생할 경우 해당 예외를 잡아서 처리하거나 해당 메소드를 호출한 곳으로 던지거나 둘중에 하나를 선택해서 예외를 처리합니다.즉,catch 구문 안에서는 예외를 잡을 수 있고, throw를 사용하면 예외를 밖으로 던질 수 있습니다.

간단하게 복습을 했으니 check예외와 uncheck예외에 대해 알아봅시다.

Check 예외

위의 용어를 이번 포스팅을 준비하면서 처음 접했습니다.하지만 알고보니 이미 알고 있는 개념이지만 용어만 달라진 것이였습니다.통상적으로 Compile Exception이라고 불리는 예외들이 모두 체크 예외입니다.RuntimeException을 제외한 Exception 예외와 그 하위 예외들은 모두 check예외(컴파일 예외)입니다.이들은 컴파일러가 모두 체크를 해주기 때문에 개발자 입장에서는 파악하기 수월합니다.그렇기 때문에 체크 예외는 잡아서 처리하지 않으면 항상 throws를 선언하고 해당 예외를 던져야 합니다.

지금부터는 테스트 코드를 통해 Check 예외를 알아봅시다.

@Slf4j
public class CheckedTest {
    @Test
    void checked_catch() {
        Service service = new Service();
        service.callCatch();
    }

    @Test
    void checked_throw() {
        Service service = new Service();
        assertThatThrownBy(() -> service.callThrow()).isInstanceOf(MyCheckedException.class);
    }

    /*
     * Exception을 상속 받은 예외는 Check 예외 (컴파일러가 확인)
     * */
    static class MyCheckedException extends Exception {
        public MyCheckedException(String message) {
            super(message);
        }
    }

    /*
    * Check 예외는 잡거나 던지거나 둘 중 하나가 필수
    * */
    static class Service {
        Repository repository = new Repository();

        /*
         * check 예외를 잡아서 처리하는 메서드
         * */
        public void callCatch() {
            try {
                repository.call();
            } catch (MyCheckedException e) {
                //예외처리로직
                log.info("Exception message = {}",e.getMessage(),e);
            }
        }

        /*
        * check 예외를 잡아서 던지는 메서드
        * */
        public void callThrow() throws MyCheckedException {
            repository.call();
        }

    }

    static class Repository {
        //check 예외는 던질 때, 던진다고 선언해줘야함 => 선언안되있을 경우 체크해줘서 체크 예외
        public void call() throws MyCheckedException {
            throw new MyCheckedException("ex");
        }
    }

}

코드에 대해 간략히 설명을 하자면 Repository에서 예외를 발생 시키고 이를 Service단에서 잡아주거나 던지는 메서드를 호출하고 있습니다.

서비스단에서 예외를 잡아주는 테스트의 경우 아래와 같이 예외가 발생해도 정상적으로 로그에 해당 예외가 나오는 것을 확인 할 수 있습니다.

반면 서비스단에서 예외를 던지는 테스트의 경우 assertj를 통해 예외가 발생함을 검증해줘서 테스트 코드를 통과시키고 있습니다.

Uncheck 예외

Uncheck 예외의 경우 런타임 에러라고도 불립니다.즉,컴파일러가 해당 예외를 잡아주지 않는다는 것입니다.언체크 예외 또한 기본적인 예외의 틀은 체크 예외와 동일합니다.하지만 예외를 던질 경우 throws를 선언하지 않고 생략할 수 있습니다.

@Slf4j
public class UncheckedTest {

    @Test
    void unchecked_catch() {
        Service service = new Service();
        service.callCatch();
    }
    @Test
    void unchecked_throw() {
        Service service = new Service();
        Assertions.assertThatThrownBy(() -> service.callThrow())
                .isInstanceOf(MyUncheckedException.class);
    }
    /*
    * RuntimeException을 상속받은 예외는 Unchecked 예외이다
    * */
    static class MyUncheckedException extends RuntimeException {
        public MyUncheckedException(String message) {
            super(message);
        }
    }

    /*
    * Unchecked 예외를 잡거나 던지지 않아도 됨
    * 예외를 잡지 않으면 자동으로 밖으로 던짐
    * */
    static class Service {
        Repository repository = new Repository();

        /*
        * 필요한 경우 예외를 잡아서 처리하면 된다
        * */
        public void callCatch() {
            try {
                repository.call();
            } catch (MyUncheckedException e) {
                log.info("Unchecked Exception = {}", e.getMessage(), e);
            }

        }

        /*
        * checkd 예외와 다르게 throws 예외 선언을 하지 않아도 된다.
        * */
        public void callThrow() {
            repository.call();
        }
    }
    static class Repository {
        public void call() {
            throw new MyUncheckedException("ex");
        }
    }
}

Uncheck 예외 또한 check 예외와 마찬가지로 예외를 catch로 잡으면 잡을 수 있습니다.또한 throw을 통해 던지는 것도 가능합니다.하지만 차이점이 있다면 throws 예외 선언을 하지 않아도 예외를 던질 수 있다는 것입니다.

실제로 예외 선언을 하지 않은 테스트 코드를 돌려보면 위와 같이 성공적으로 돌아가는것을 볼 수 있습니다.

마지막으로 정리를 해보겠습니다.checked 예외는 컴파일러가 throws가 선언이 되어 있지 않을 경우 에러를 잡아주는 예외입니다.반면에 unchecked 예외는 컴파일러가 잡아주지 않기에 throws를 선언하지 않고도 예외를 던질 수 있는 예외입니다.

지금까지 간단하게 테스트 코드를 통해 checked 예외와 unchecked 예외에 대해 알아보았습니다.다음 포스팅에서는 이러한 예외를 실제로 어떻게 활용할 수 있을지에 대해 알아보겠습니다.