예외 처리

2025. 7. 19. 15:06JAVA

자바를 공부하거나 프로그래밍을 하다 보면 예외(Exception)와 관련된 오류를 마주치는 경우가 많습니다. 이번 글에서는 자바의 예외 처리에 대해 정리해보려고 합니다.

● Object : 예외도 객체입니다.

자바에서 모든 클래스의 최상위 부모는 Object이며 예외 클래스도 예외가 아닙니다. 따라서 예외도 객체입니다.

● Throwable : 모든 예외의 최상위 클래스입니다.

Throwable은 자바에서 발생할 수 있는 모든 예외와 오류의 부모 클래스입니다. 하위에는 Exception과 Error가 있습니다.

● Error : 시스템 수준의 치명적인 예외입니다.

Error는 메모리 부족, 스택 오버플로우 등 시스템적인 문제로 인해 발생하는 예외로, 애플리케이션에서 복구할 수 없습니다.
따라서 애플리케이션 개발자는 이를 try-catch로 처리하려 해서는 안 됩니다.

  • 상위 타입을 catch로 잡으면 하위 예외까지 모두 함께 잡히게 됩니다.
    예를 들어 Throwable을 catch하면 Error도 포함되므로, 애플리케이션 로직에서는 Throwable을 catch하지 말아야 합니다.
    필요한 경우 Exception부터 필요한 예외만 선택적으로 처리하는 것이 바람직합니다.
  • Error는 언체크 예외(Unchecked Exception) 입니다.

● Exception : 일반적인 예외 (체크 예외)

Exception은 애플리케이션 로직에서 사용할 수 있는 최상위 예외입니다.

  • Exception과 그 하위 클래스는 컴파일러가 체크하는 체크 예외(Checked Exception) 입니다.
    예외가 발생할 수 있는 코드는 반드시 try-catch로 처리하거나 throws로 명시해야 합니다.
  • 단, RuntimeException과 그 하위 클래스는 예외입니다. (즉, 언체크 예외입니다)

● RuntimeException : 런타임 예외 (언체크 예외)

RuntimeException과 그 자식 클래스는 컴파일러가 체크하지 않는 언체크 예외(Unchecked Exception) 입니다.

  • 대표적인 예로는 NullPointerException, IndexOutOfBoundsException, IllegalArgumentException 등이 있습니다.
  • 대부분 프로그래머의 실수로 인해 발생하는 예외이며, 컴파일 시점이 아닌 런타임 시점에 발견됩니다.
  • 이러한 예외는 try-catch로 명시적으로 처리할 수도 있지만, 일반적으로는 예외가 발생하지 않도록 코드 자체를 안전하게 작성하는 것이 더 중요합니다.

이처럼 자바의 예외는 크게 Error, Exception, 그리고 그 하위인 RuntimeException으로 나뉘며,
체크 예외와 언체크 예외로 구분됩니다. 예외 처리의 목적은 프로그램이 갑자기 종료되지 않도록 안정적으로 흐름을 제어하는 데 있으며,
필요한 예외만 적절하게 처리하고, 불필요하게 너무 포괄적인 예외는 catch하지 않도록 주의하는 것이 좋습니다.

 

  • 이러한 방식으로 Service단에서 예외를 처리하면 정상적으로 로직이 흘러갈 수 있습니다. 

  • 예외를 처리하지 못한다면 계속 밖으로 던지게 되므로 로직을 수행할 수 없습니다.

 

예외에 대해서는 2가지 기본 규칙을 기억하자!

1.예외는 잡아서 처리하거나 던져야 합니다.

2.예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리됩니다.

   ● 예를 들어 Exception을 catch로 잡으면 그 하위 예외들도 모두 잡을 수 있습니다.

    ● Exception을 throws를 통해 밖으로 던지면 하위 예외들도 모두 던질 수 있습니다.

 

package com.example.Car.basic;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

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

    @Test
    public void check_throw() {
        Service service = new Service();
        Assertions.assertThatThrownBy(() -> service.callThrow()).isInstanceOf(MyCheckdException.class);
    }
        /**
         * Exception을 상속받은 예외는 체크 예외가 된다.
         */
        static class MyCheckdException extends Exception {
            public MyCheckdException(String message) {
                super(message);
            }
        }

        /**
         * Checkd예외는
         * 예외를 잡아서 처리하거나 던지거나 선택.
         */
        static class Service {
            Repository repository = new Repository();

            /**
             * 예외를 잡고 처리하는 코드
             */
            public void callCatch() {
                try {
                    repository.call();
                } catch (MyCheckdException e) {
                    throw new RuntimeException(e);
                }
            }

            public void callThrow() throws MyCheckdException {
                repository.call();
            }

        }


        /**
         * 예외를
         */
        static class Repository {
            public void call() throws MyCheckdException {
                throw new MyCheckdException("EX");
            }
        }
    }

 

체크 예외의 장단점

1.체크 예외는 예외를 잡아서 처리할 수 없을 때, 예외를 밖으로 던지는 throws 예외를 필수로 선언해야 합니다. 그렇지 않으면 오류가 발생합니다.

장점: 실수로 예외를 누락하지 않도록 컴파일러를 통해 문제를 잡아줍니다.

단점: 모든 체크 예외를 반드시 잡거나 던지도록 해야하기 때문에 번거롭습니다.

 

● 언체크 예외 기본 이해

1.RuntimeException과 그 하위 예외는 언체크 예외로 분류합니다.

2.언체크 예외는 말 그대로 컴파일러가 예외를 체크하지 않는다는 뜻입니다.

3.언체크 예외는 체크 예외와 기본적으로 동일하다. 차이가 있다면 예외를 던지는 throws를 선언하지 않고 생략할 수 있다. 이 경우에는 자동으로 예외를 던집니다.

 

체크 예외 vs 언체크 예외

● 체크 예외: 예외를 잡아서 처리하지 않으면 항상 throws에 던지는 예외를 선언해야 합니다.

● 언체크 예외: 예외를 잡아서 처리하지 않아도 throws를 생략할 수 있다.

 

package com.example.Car.basic;

import org.junit.jupiter.api.Test;

public class UnCheckedTest {

    @Test
    public void Catch() {
        Service service = new Service();
        service.callCatch();
    }

    /**
     * RuntimeException을 상속받은 예외는 언체크 예외가 됩니다.
     */
    static class MyUncheckedException extends RuntimeException {
        public MyUncheckedException(String message) {
            super(message);
        }
    }

    /**
     * 언체크 예외는 예외를 잡거나, 던지지 않아도 됩니다.
     * 예외를 잡지 않으면 자동으로 밖으로 던집니다.
     */
    static class Service {
        Repository repository = new Repository();

        public void callCatch() {
            try {
                repository.call();
            } catch (MyUncheckedException e) {
                System.out.println("MyUncheckedException caught: " + e.getMessage());
            }
        }
    }

    static class Repository {
        public void call() {
            throw new MyUncheckedException("EX"); // 수정됨
        }
    }
}

 

정리: 체크 예외와 언체크 예외의 차이는 사실 예외를 처리할 수 없을 때 예외를 밖으로 던지는 부분에 있다. 이 부분을 필수로 선언해야 하는지 생략할 수 있는지 차이가 있습니다.

 

그렇다면 언제 체크 예외를 사용하고 언체크(런타임) 예외를 사용하면 좋을까???

기본 원칙은 2가지를 기억하자!!!

1.기본적으로는 언체크 예외를 사용하자.

2.체크 예외는 비즈니스 로직상 의도적으로 던지는 예외에만 사용하자.

'JAVA' 카테고리의 다른 글

자바 문법 및 필요 함수  (1) 2025.06.15