Java Exception과 예외처리

squareBird·2022년 6월 16일
0

Java

목록 보기
3/4

에러(Error)와 예외(Exception)

Java에는 에러(Error)와 예외(Exception)이라는 개념이 존재한다.

Java에는 Throwable이라는 클래스가 존재한다.

해당 그림을 보면 크게 ErrorException을 상속하는 클래스들로 구성되어 있다.

여기서 Error를 상속하는 클래스들은 코드의 문제가 아닌 하드웨어 장비나 가상머신, 메모리와 같은 외적인 문제이고 Exception을 상속하고 있는 클래스들이 애플리케이션 코드에서 발생하는 에러이다.

Error가 발생하면 코드레벨에서 뭘 할 수 있을까?

  1. 자동으로 재시작하도록 구성해본다.
  2. 에러 발생시 개발자에게 알려줄 수 있는 체계를 마련한다
  • 에러의 경우 애플리케이션이 자체적으로 해결하기 어렵다
  • 개발자가 문제를 해결하기 위해 개입할 필요가 있는 경우가 많다
  • 로그를 보고 문제를 추적해볼수도 있다
  • 슬렉, 이메일, SNS 등을 통한 알람

확인된 예외와 미확인 예외

위의 그림을 보면RuntimeException을 상속하는 클래스가 있고, 상속하지 않는 클래스가 있다.

RuntimeExcepiton을 상속하는 예외는 UnchekdException이라고 부르고, 상속하지 않는 예외는 CheckedException이라고 부른다.

그렇다면 두 Exception의 차이는 뭘까?

RuntimeException이라는 이름의 클래스를 기준으로 구분을 하기 때문에 예외가 발생하는 시점에 대한 차이로 착각하는 경우가 많다.

실제로 블로그를 찾다보면 발생 시점을 컴파일 시점과 런타임 시점이라고 나누어 설명하는 경우가 많은데, 정확히는 try-catcht를 통한 예외처리의 강제성에 차이가 있다.

두 예외 모두 런타임 시점에 발생하지만, CheckedException의 경우 컴파일러가 예외처리를 확인 하는 과정에서 코드가 실행되지 않기 때문에 컴파일 환경에서 발생하는 것으로 보이는 것이다.

매우 중요하니 반드시 명심해야한다.


UncheckedException

RuntimeException이란 말 그대로 런타임 환경에서 발생하는 Exception이다.

컴파일 과정에서는 해당 Exception을 인지할 수 없고, 코드가 런타임 환경에서 실행되는 과정에서 Exception이 발생한다.

자바 동작 구조상 .java 파일이 자바 컴파일러를 통해 .class 파일로 변환되고, 클래스 로더가 바이트 코드인 .class 파일을 JVM에 올려야 Java InterpreterJIT Compiler에 의해 자바 런타임이 동작한다.

Exception에 대한 체크를 하지 않아도 애플리케이션 코드를 실행시킬 수 있는 Exception이기 때문에 UncheckedException이라고 한다.

public class UncheckedExceptionSample {

    public static void main(String[] args) {

        int[] arr = new int[10];
        arr[10] = 10;

    }

}

예를 들어 위와 같은 코드가 있다고 하자 프로그래밍을 처음 시작하면 자주 접하게 되는 예외중에 IndexOutOfBoundException이라는 예외가 있다.

해당 코드는 컴파일 후 실행까지의 동작이 정상적으로 수행된다.

하지만 배열의 크기가 10으로 0~9까지의 인덱스를 가지게 되는데, 10이라는 범위 밖의 인덱스를 조회하고 있으므로 예외가 발생한다.


CheckedException

RuntimeException이 런타임 환경에서 발생하는 예외라면 Exception은 런타임 시점에 발생하지 않을까?

위에서 말한 것 처럼 Exception 역시 런타임 시점에 발생하는 예외이며, CheckedException과의 차이는 try-catch문을 통한 예외처리가 강제된다는 것이다.

반드시 해당 코드에 대한 예외처리 즉 체크를 해주어야 하기 때문에 CheckedExcption이라고 부르는 것이다.

CheckedException은 주로 자바 설계에 의한 문제인 경우가 많다.

CheckedException의 예를 들어보자.
대표적인 예로 FileNotFoundException이 있다.

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class CheckedExceptionSample {

    public static void main(String[] args) {
        try {
            //파일 객체 생성
            File file = new File("D:/Sample.txt");
            //입력 스트림 생성
            FileReader file_reader = new FileReader(file);
            int cur = 0;
            while ((cur = file_reader.read()) != -1) {
                System.out.print((char) cur);
            }
            file_reader.close();
        } catch (FileNotFoundException e) {
            e.getStackTrace();
        } catch (IOException e) {
            e.getStackTrace();
        }
    }

}

위의 코드를 보면 try-catch문으로 감싸져 있는 코드들이 있다.
CheckedException은 이렇게 try-catch를 통해 예외가 발생할 수 있는 부분을 강제로 예외처리 해 주어야 한다.


예외처리

위에서 설명한 것 처럼 CheckedException에 대해서는 반드시 예외처리가 필요하다.

그렇다면 예외처리 방법에는 어떤 것이 있을까?

위의 FileNotFoundException에 대한 코드를 보면서 설명하겠다.

1. try-catch문을 통한 예외처리

먼저 try-catch문을 통한 예외처리가 가능하다.

try {
	// 비즈니스 로직
} catch (Exception) {
	// 해당 Exception이 발생했을 때 동작 구현
} finally {
	// 반드시 수행할 동작 구현
}

try 내부에 비즈니스 로직을 구현하고, catch에 발생할 수 있는 예외를 정의하고 예외 발생시 어떤 동작을 할지 구현한다.

그리고, 만약 예외가 발생하던 하지 않던 반드시 수행해야 하는 동작들 예를들어 DB 커넥션을 해제하는 등의 동작은 finally에 정의한다.

그런데 위의 try-catch문으로 감싼 코드를 보면 의문이 생긴다.

try-catch문으로 감쌌다는 것은 해당 코드들이 CheckedException을 발생시킨다는 의미인데 어떻게 예외가 발생할것을 알 수 있을까?

그건 try-catch문을 지워보면 알 수 있다.

2. throws를 통한 예외처리

위의 코드에서 try-catch문을 지웠더니 코드에 문제가 있다는 빨간색 밑줄이 생겼다.

그럼 14번째 줄의 FileReader라는 구현체에 어떤 문제가 있는지 확인해 보겠다.

위의 그림은 FileReader의 생성자중 하나이다.

그런데 생성자 선언문의 파라미터{사이에 throws라는 예약어가 있고 그 옆에 FileNotFoundException이라는 단어가 적혀 있다.

Java에서는 이것을 예외를 던진다라고 한다.

이런 CheckedException들은 반드시 어디에선가 해결을 해 주어야 한다.
throws는 이 예외가 발생한 객체가 아닌 자신을 호출한 대상이 예외를 처리하도록 떠넘기는 것과 같다.

위의 코드에서 main에서 FileReader의 생성자를 호출했다. FileReaderthrows 예약어가 붙어 있으니 예외가 발생하면 FileReader의 생성자는 main으로 예외를 던지게된다.

이제 예외 처리에 대한 책임이 main으로 넘어갔기 때문에 main에서 try-catch를 통한 예외처리를 해주어야 한다.

그렇다면 FileNotFoundException의 예외를 처리해보자.

try-catch문을 이용해 FileNotFoundException에 대한 예외를 처리해 주었다.
그런데 FileReader구현체의 read()close() 메소드를 사용하는 부분에도 문제가 있다고 나온다...

이번에는 이 두 메소드의 형태를 살펴보자.
이 글만 보지 말고 반드시 직접 해당 구현체와 메소드를 확인해 보는 것이 좋다.


read()close() 메소드는 FileNotFoundException이 아닌 IOException 예외를 던지고 있기 때문에 문제가 발생한 것이었다.

그럼 IOException에 대한 예외도 처리해 주겠다.

문제가 해결되었다!

그런데 만약 여기서 FileNotFoundExceptioncatch문을 제거하면 어떻게 될까?

문제가 발생하지 않는다.

어째서 문제가 발생하지 않는걸까?

맨 위의 다이어그램을 유심히 봤다면 눈치챘을수도 있지만, FileNotFoundExceptionIOException을 상속한 클래스이기 때문이다.

상속에 대한 개념을 잘 알고 있는 사람이라면, IOException 뿐만 아니라 Exception이나 Throwable로 처리해도 된다는 사실을 깨닫게 될 것이다.

그렇다면 모든 클래스의 최상위 클래스인 Object클래스를 통해서도 될까?

catch의 파라미터 값이 Throwable을 상속한 클래스로 제한되므로 안된다.

이에 대해서는 상속과 업캐스팅 다운캐스팅에 대해서 알아보면 좋을 것 같다.

이제 CheckedExcpetion은 해당 예외가 발생할 수 있는 객체들이 throws를 통해 예외를 던져주기 때문에 반드시 처리해야 된다는 사실을 알게 되었고, try-catch를 통해 예외를 전달받은 main에서 예외처리도 해주었다.

그런데 main마저 예외처리를 하기 싫다면 어떻게 하면 될까?

간단하게 main 메소드 옆에서 throws를 통해 예외를 던져주면 된다.
이렇게하면 프로그램에서 가장 최상단에 위치한 main마저 예외를 처리하지 않는다.

하지만, 이렇게 하면 어떤 에러가 어디서 발생했는지 식별하는데 어려움이 있고 예외가 발생하면 서로 떠밀다가 프로그램이 죽어버리기 때문에 권장하지 않는다...

profile
DevOps...

0개의 댓글