-[Java] Exception Handling

박솔찬·2021년 11월 23일
0

프로그램이 실행 중 오작동하여 비정상적으로 종료되는 경우가 있다. 이런 경우는 프로그램이 에러가 발생하였기 때문이다.

만약, 중요한 프로그램이 실행 중 어떠한 예외 케이스를 만나 아무런 대응도 하지 못하고 죽어버리면 매우 끔찍한 일이 발생하게 될 것이다.

이러한 상황을 방지하고자 에러가 발생하면 해당 에러를 핸들러하는 것을 에러 처리라고 한다.

에러는 크게 3가지로 표현할 수 있다.

  1. 컴파일 에러 - 컴파일 단계에서 발생하는 에러
  2. 런타임 에러 - 실행 단계에서 발생하는 에러
  3. 논리적 에러 - 실행은 되나, 설계에 맞지 않게 동작하는 경우 발생하는 에러

모든 에러를 예외처리할 수 있는 것은 아니다.
스택오버플로우 혹은 메모리 부족과 같은 에러는 프로그램의 비정상적인 종료를 막을 수 없다.

하지만 위와 같은 경우가 아니라면 예외처리를 통해 프로그램이 정상적으로 수행될 수 있다.

계층구조

예외처리는 예외 처리 클래스로 처리되고, 자바는 실행 시 발생할 수 있는 예외와 에러를 여러 클래스로 정의해두었다.

예외 클래스 또한 Object를 상속받고, 계층구조는 다음과 같다.

Object
|--Throwable
|--|--Exception
|--|--|--RuntimeException
|--|--|--*
|--|--Error
|--|--|--OutOfMemoryError
|--|--|--*

Exception은 에러 처리가 가능한 예외이며, Error은 비정상종료를 막을 수 없는 에러이다.

try - catch

실행도중 발생한 예외는 try - catch 구문을 통해 처리할 수 있다.

try 블럭과 catch 블럭으로 구성되며, try에서 예외가 발생한 경우 catch에서 받아서 정의된 로직으로 처리하고 프로그램은 정상적으로 수행된다.

try {
	throw new RuntimeException();
} catch(Exception e) {
	// 에러에 대한 처리 로직 블록
}

try - catch 구문은 중첩해서 사용 가능하다.
만약, 하나의 로직에 2개 이상의 에러 처리를 해야 한다면 다음과 같이 catch 블록을 이어서 작성할 수 있다.

다중 catch 블록

아래는 Exception보다 자식인 ClassCastException에 대한 catch 블록이 먼저 수행된다. 따라서 Exception 처리 블록에서는 ClassCastException에 대한 처리는 하지 않는다.(앞에서 이미 하기 때문에 못하는 게 맞다.)

try {
	// 수행하는 로직
} catch(ClassCastException ce) {
	// 클래스 형변환 예외에 대한 처리 로직
} catch(Exception e) {
	// Exception에 대한 처리 로직
}

예외 메세지

예외가 발생했을 때, 예외에 대한 메세지를 출력할 수 있다.
1. printStackTrace: 예외발생 당시의 호출스택(Call Stack)에 있었던 메서드의 정보와 예외 메세지를 화면에 출력한다.
2. getMessage: 발생한 예외클래스의 인스턴스에 저장된 메세지를 얻을 수 있다.

try{
    System.out.println(0 / 0);
}catch (ArithmeticException ae) {
    System.out.println("예외 메세지: " +  ae.getMessage());
    ae.printStackTrace();
}

실행 결과는 다음과 같다.

System.out.println("예외 메세지: " +  ae.getMessage()):
예외 메세지: / by zero

ae.printStackTrace():
java.lang.ArithmeticException: / by zero
	at Main.main(Main.java:4)

예외 발생시키기

예외는 로직이 실행되면서 발생하기도 하지만 프로그래머가 특정 케이스에 특정 예외를 발생시킬 수 있다.

예를 들어 함수 요청 인자로 10미만을 받는 경우 RuntimeException을 발생시킨다고 하자.

new 키워드를 통해 예외 인스턴스를 생성하고, throw 키워드를 통해 예외를 발생시킬 수 있다.

public class Main {
    public static void main(String[] args){
        test(5);
    }
    public static void test(int value) {
        try {
            if(value < 10) {
                throw new Exception("10미만은 허용하지 않습니다.");
            }
        }catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

finally

예외 발생 유무에 상관없이 수행해야 하는 로직은 finally를 사용하여 수행할 수 있다.

예로 로직 수행 후 무조건 결과 값을 초기화 해야하는 경우에 finally가 사용될 수 있다.

public class Main {
    public static void main(String[] args) {
        test();
    }

    public static void test() {
        int result = 10;
        System.out.println(result);
        try{
            result = result / 5;
            System.out.println(result);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        } finally {
            result = 0;
        }
        System.out.println(result);
    }
}

마지막 출력 result의 값은 0이다.
위 시나리오는 예외가 발생하지 않은 경우지만, 예외가 발생하여도 마지막 출력 result의 값은 0이다.

예외 커스터마이징

앞에서 설명하였지만 자바는 미리 여러 예외 케이스를 클래스로 정의해 두었다.
하지만 로직을 작성하다보면 로직에 특별한 예외 케이스가 자주 생긴다.
이런 경우에 기존 예외 클래스를 상속받아 커스터마이징 예외 클래스를 정의할 수 있다.

아래 코드는 super()를 호출할 때, 에러 메시지만 보낸다.
하지만 문자열을 받는 생성자만 있는 것이 아니기 때문에 Exception에 직접 들어가서 확인해보면 좋다.

class CustomException extends RuntimeException {
	public CustomException() {
    		super("커스텀 예외 발생");
    	}
}

위 처럼 커스텀 예외를 작성한 후, 예외를 발생시킬 때 Exception이 아닌 CustomException 인스턴스를 생성하여 throw하면 된다.

가능하면 새로운 예외 클래스를 만들기 보다 기존의 예외클래스를 활용하라.
-자바의 정석


참고
Java의 정석(3판)

profile
Why? How? What?

0개의 댓글