Java

[Java] Throwable vs Error vs Exception 그리고 예외 처리 전략

태크민 2025. 1. 14. 16:23

1. 프로그램 오류

프로그램은 어떤 원인에 의해 오작동 또는 비정상적 종료가 일어나는 경우가 있다.

이러한 결과를 초래하는 원인을 프로그램 오류 또는 에러라고하며 발생 시점에 따라 3가지로 나뉜다.

  • 컴파일 에러 : 컴파일 시에 발생하는 오류
  • 런타임 에러 : 실행 시 발생하는 오류
  • 논리적 에러 : 실행은 되지만, 의도와 다르게 동작하는 것

컴파일러는 소스코드(.java) 오타, 잘못된 구문, 자료형 체크 등 기본적인 검사를 수행하여 오류가 있는지 알려준다. 이때 발생하는 것이 컴파일 에러이다.

그리고 이 컴파일 과정이 끝나면 클래스 파일(.class)이 생성되고 생성된 클래스 파일을 실행할 수 있게 된다.
하지만, 컴파일이 잘되었어도 실행 도중 발생할 수 있는 잠재적 오류까지 검사할 수 없기 때문에 실행 도중 오류가 발생할 수 있다.

이때 발생하는 것이 런타임 에러이며 자바에서는 이를 에러(Error)와 예외(Exception)로 구분한다.

 

2. 에러 vs 예외

  • 에러(Error) : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
  • 예외(Exception) : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류

에러는 메모리 부족(OutOfMemoryError)나 스택오버플로우(StackOverflowError)와 같이 일단 발생하면 복구할 수 없는 심각한 오류이고,

예외는 NullPointerException, illegalArgumentException과 같이, 발생하더라도 적절한 코드(try-catch)를 미리 작성해 놓음으로써 프로그램의 비정상적 종료를 막을 수 있는 비교적 덜 심각한 오류이다.

 

3. 에러와 예외 클래스 계층 구조

오류(Error)와 예외(Exception) 모두 Throwable를 상속받는다.

모든 Error와 Exception 클래스는 Throwable 클래스를 상속받고있으며, getMessage()와 printStackTrace() 메서드를 통해 현재 Error와 Exception 여부를 확인할 수 있다.

사용자는 Error의 상황을 미리 미연에 방지하기 위해서 Exception 상황을 만들 수 있으며, java에서는 try-catch문으로 Exception handling을 할 수 있다.


Checked Exception, Unchecked Exception

Exception클래스는 두 그룹으로 나눌 수 있다.

  1. Exception클래스와 자손들 (RuntimeException클래스와 그 자손들 제외) ➡️ Checked Exception
  2. RuntimeException클래스와 자손들 ➡️ Unchecked Exception

쉽게 말하면, RuntimeException을 상속하지 않은 클래스는 Checked Exception,

반대로 상속한 클래스는 Unchecked Exception으로 분류할 수 있다.

 

Checked Exception

  • 컴파일러가 예외처리를 강제(확인)하는 예외
  • 사용자(외부)의 동작에 의해서 발생될 수 있는 예외
  • ex) 존재하지 않는 파일의 입력(FileNotFoundException), 클래스 이름을 잘못 입력(ClassNotFoundException) 등
  • 트랜잭션 도중 예외가 발생하면 롤백(rollback)하지 않음

명시적인 예외 처리를 강제하기 때문에 Checked Exception이라 한다. 반드시 try ~ catch로 예외를 잡거나 throw로 호출한 메소드에게 예외를 던져야 한다.

 만일 컴파일 시점에 예외에 대해 처리(try/catch) 하지 않는다면 컴파일 에러가 발생하게 된다. 또한 트랜잭션 Rollback이 안된다는 특징도 있다.


Checked Exception 예시는 아래와 같다.

public sendFile(String fileName)() {
        File file;
        try {
                file = FileFindService.find(fileName);
        } catch (FileNotFoundException e){ 
                // 기본 파일을 찾아서 전송한다.
                file = FileFindService.find("default.png");
        }

        send(file);
        }
}

 

Unchecked Exception

  • 컴파일러가 예외처리를 강제(확인)하지 않는 예외
  • ex) null인 참조변수의 멤버호출(NullPointException), 배열의 범위를 벗어난 동작(ArrayIndexOutOfBoundException) 등
  • 트랜잭션 도중 예외가 발생하면 자동 롤백(rollback)

💡 중요 : 가장 큰 차이점은 예외에 대한 처리를 컴파일 시점에 강제하냐 안하냐의 차이입니다. (예외를 언제 던지냐의 차이가 아닙니다.) 코드에 Checked Exception에 대한 예외 처리가 안되어 있을 때 발생하는 건 컴파일 에러입니다.

 

Uncheckced Exception은 왜 예외처리를 강제하지 않을까?

RuntimeException클래스와 자손들은 예외처리 강제하지 않는다.
만일 RuntimeException클래스들에 속하는 예외에도 예외처리를 필수로해야한다면? 아래와 같이 참조변수와 배열이 사용되는 모든 곳에 예외처리를 해주어야할 것이다.

try {
	int[] arr = new int[10];
    System.out.println(arr[0]);
} catch(ArrayIndexOutOfBoundException ae) {
	...
} catch(NullPointerException ne) {
	...
}

이외에도 예외처리가 불필요한 경우 try-catch문을 무조건 넣어야 하므로 코드가 복잡해지게 된다.

 

왜 Checked Exception은 롤백해주지 않는걸까?

CheckedException은 예외처리가 컴파일러에 의해 강제되어있기 때문에 롤백 혹은 다른 처리를 개발자가 진행할 수 있는 기회가 있다. 하지만 Unchecked Exception은 예외 처리가 되어있지 않은 경우가 훨씬 많다. 이 경우 commit은 치명적일 수 있기때문에 롤백을 수행해준다라고 한다.

 

Exception Handling

Java에서 모든 예외가 발생하면 (XXX)Exception 객체를 생성한다.
예외를 처리하는 방법에는 크게 2가지가 있다.

 

1. try-catch

직접 try-catch를 이용해서 예외에 대한 최종적인 책임을 지고 처리하는 방식

try {
	// 예외가 발생할 가능성이 있는 코드
} catch(Exception1 e1) {
	// 예외1이 발생했을 경우, 처리하기 위한 코드
} catch(Exception2 e1) {
	// 예외2이 발생했을 경우, 처리하기 위한 코드
} catch(Exception3 e1) {
	// 예외3이 발생했을 경우, 처리하기 위한 코드
} finally {
	// 예외 발생여부에 관계없이 항상 수행되어야하는 문장을 넣는다. (선택 사항)
}

 

2. 예외 처리 회피

throws Exception을 이용해서 발생한 예외의 책임을 호출하는 쪽이 책임지도록 하는 방식 (주로 호출하는 쪽에 예외를 보고할 때 사용함) 
하지만, 무책임하게 상위 메서드에 throw로 예외를 던지는 것은 상위 메서드의 책임이 증가하기 때문에 좋지 않은 방법이다.

void method() throws Exception1, Exception2, Exception3 {
	...
}

 

3. 예외 전환
예외 처리 회피와 비슷하게 메소드 밖으로 예외를 던지지만, 그냥 던지지 않고 적절한 예외로 전환해서 넘기는 방법이다. 명확한 의미로 전달되기 위해 적합한 의미를 가진 예외로 변경해야 한다.

public Object method() {
        try {
                ...
        } catch (IOException e) {
                throw new CustomException ("IOException 발생");
        }
}

public class CustomException extends RuntimeException{
    public CustomException(String message) {
        super(message);
    }
}

 


참고자료

https://steady-coding.tistory.com/583

https://toneyparky.tistory.com/40

https://velog.io/@gjwjdghk123/Checked-Exception-Unchecked-Exception-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC

https://seungjjun.tistory.com/250