[Java] Exception 1편: Throwable 클래스
들어가며
이번 글부터 총 3편에 걸쳐 면접에서 가장 많이 받았던 질문인 Exception에 대해 차근차근 정리해 보려 한다. 1편에서는 Exception의 기초인 Throwable 클래스, 2편에서는 Exception 중에서도 개발할 때 가장 많이 만나는 Runtime Exception, 3편에서는 Checked Exception과 Unchecked Exception을 비교하며 나머지 주요 Exception에 대해 작성할 예정이다.
Java에 존재하는 여러 예외 중에 자주 만나는 예외가 어떤 것인지는 알았지만, 예외들 사이의 관계와 정확한 의미에 대해 깊게 공부한 적은 없었기 때문에 이번 기회에 정리해 보려 한다!
Java에서 Exception 클래스의 위치는 어딜까?
우선 Error 클래스와 함께, Java에서 Exception 클래스의 위치에 대해 정리했다. Java의 모든 자료형의 조상은 Object인데, 따라서 Error와 Exception도 결국 Object 클래스를 상속받는다. 다만, Error와 Exception 클래스는 Object 클래스를 직접 상속받지 않고, Throwable이라는 클래스를 통해 상속받는다.
Throwable 클래스란?
Throwable 클래스란 무엇일까? 우선 위 다이어그램을 보면, getMessage()와 printStackTrace()를 포함하는 것을 볼 수 있다.
이 두 메서드는 모두 개발자가 프로그램 실행 중에 발생한 예외를 처리하는 데 도움을 주며, Exception 클래스가 바로 이 Throwable 클래스를 상속받기 때문에, 개발할 때 예외의 정보를 콘솔에서 확인할 수 있는 것이다.
다만, 이 둘은 약간의 차이가 있는데, 정확한 역할은 아래와 같다.
이 두 메서드의 차이를 좀 더 확실하게 알기 위해 아래와 같은 코드를 작성했다.
public class Main {
// ArithmeticException 유발 함수
private static int divide(int a, int b) {
return a / b;
}
public static void main(String[] args) {
try {
int result = divide(10, 0);
System.out.println("결과: " + result);
} catch (Exception e) {
// 1. getMessage()
System.out.println("1. getMessage() 출력:");
System.out.println(e.getMessage());
System.out.println();
// 2. printStackTrace()
System.out.println("2. printStackTrace() 출력:");
e.printStackTrace();
}
}
}
Runtime Exception 중 하나인 Arithmetic Excpetion을 일부러 발생시키기 위해 divide라는 함수를 만들고, 코드를 실행한 결과는 아래와 같았다.
즉, getMessage()의 실행 결과는 예외 메시지 문자열, '/ by zero'이며, printStackTrace()의 실행 결과는 예외의 종류, 예외 메지, 클래스와 메서드 이름을 포함한 예외 발생 위치이다.
여기서 의문점이 생겼다. getMessage()의 반환형은 String인데 그렇다면 위 예제에서 toString()의 실행 결과는 뭘까? 이 질문의 답을 알아보기 위해 위 예제의 코드를 아래와 같이 수정했다.
public class Main {
// ArithmeticException 유발 함수
private static int divide(int a, int b) {
return a / b;
}
public static void main(String[] args) {
try {
int result = divide(10, 0);
System.out.println("결과: " + result);
} catch (Exception e) {
// 1. getMessage()
System.out.println("1. getMessage() 출력:");
System.out.println(e.getMessage());
System.out.println();
// 2. toString()
System.out.println("2. toString() 출력:");
System.out.println(e.toString());
}
}
}
코드를 실행한 결과는 아래와 같았다.
앞선 두 메서드와는 다른 결과가 출력됐다. toString()의 실행 결과는 예외의 종류와 예외 메시지였다.
결론
앞선 예제들을 통해, 서로 다른 세 메서드를 실행하면 어떤 결과가 출력되는지 확인할 수 있었다. 만약 셋 중에 어느 방법을 사용할 것인지를 묻는다면, 나는 printStackTrace()를 사용할 것 같다.
그 이유는 개인적으로 개발하면서 예외가 발생하면, 그 예외가 발생한 클래스의 이름과 위치를 확인하는 것이 정말 많은 도움이 되기 때문이다! 다만 별도의 로깅 라이브러리를 사용하지 않거나 프로젝트의 규모가 점점 커질 경우, 점점 다양한 상황에서 예외가 발생할 수 있고 빠른 속도로 쌓이는 로그 속에서 예외 메시지를 찾는 것도 복잡해지기 때문에 예외 메시지를 적절히 커스텀화해서 필요한 정보만 출력되도록 수정하는 것이 도움이 될 것 같다!