본문 바로가기
IT 개인 공부/Java

[Java] 예외의 종류

by Libi 2021. 7. 19.
반응형

프로그램 개발을 하다 보면 다양한 오류가 발생할 수 있다. Java에서는 오류가 발생하면 어떤 오류인지를 개발자에게 알려줘서 오류를 처리할 수 있도록 기능을 지원해 준다.

Java에서의 예외는 크게 Error, RuntimeException, OtherException, 3가지의 종류가 존재한다.

이들에 대해서 알아보도록 하자. 먼저 Error이다.

Error는 메모리 부족(OutOfMemoryError), 스택오버플로우(StackOverFlowError)처럼 자바 가상 기계(JVM)나 하드웨어 등 시스템의 문제로 발생하는 것을 의미한다.

즉, 개발자가 처리할 수 있는 영역이 아니기 때문에 Error가 발생하면 프로그램을 종료시키는 것이 보통이다.

다음으로 Exception이다.

Error와 Exception의 차이는 개발자가 프로그램 내에서 처리할 수 있는지에 대한 여부이다. Exception은 Error와 다르게 프로그램 내에서 처리해 줄 수 있다.

즉, 예외가 발생하더라도 프로그램을 비정상 종료시키지 않는다.

Exception은 RuntimeExceptionOtherException으로 나뉜다. 이들을 구분하는 차이는 컴파일러가 예외 처리를 하는지에 대한 여부이다.

RuntimeException은 Error와 함께 컴파일러가 예외를 체크하지 않는다. 이들을 비체크 예외(UnChecked Exceptions)라고 부른다.

반대로 OtherException은 Error와 RuntimeException을 제외한 모든 예외를 의미하며 컴파일러가 예외를 체크해 준다. 이는 체크 예외(Checked Exception)라고 부른다.

 

RuntimeException은 주로 프로그래밍 버그나 논리 오류에서 기인한다. 예를 들어 Null 상태인 객체에 접근할 때 발생하는 NullPointerException 같은 것을 말한다.

 

이러한 예외는 개발자가 처리해 줄 수도 있지만 꼭 처리해 줄 필요가 없다.

예외 처리는 개발자에게 이러한 부분이 잘못되었다는 것을 쉽게 알려주기 위해서 사용하는 것이다. 하지만 이러한 예외는 개발자가 부주의해서 발생하는 경우가 대부분이며 어느 정도 예측이 가능한 범위이기 때문에 반드시 예외 처리를 해줄 필요가 없는 것이다.

반면, OtherException은 무조건 예외 처리를 해줘야 한다. 만약 처리하지 않으면 컴파일러가 컴파일 과정에서 컴파일 오류를 발생시킨다.

즉, 우리는 3가지 종류의 예외 중 Checked Exception인 OtherException을 신경 써야 한다.

그렇다면 어떤 방식으로 예외를 처리할 수 있을까? 예외를 처리하는 방법은 크게 2가지가 있다.

첫 번째 방법은 try-catch-(finally), try-with-resources 등을 사용하여 예외가 발생 시 그 자리에서 처리해 주는 방법이다.

-----------  try-catch  ------------
try {
    //something
} catch (Exception 하위 클래스 e) { 
    //handle Exception
}

-----------  try-catch-finally  ------------
try {
    //something
} catch (Exception 하위 클래스 e) { 
    //handle Exception
} finally {
    //Exception 발생 유무에 상관 없이 무조건 동작
}

-----------  try-with-resources  ------------
try (Exception 처리가 필요한 객체 선언) {
	//something	
} catch (Exception 하위 클래스 e) {
    //handle Exception
}

 

두 번째 방법은 자신이 처리하지 않고 throws를 통해 다른 메서드한테 예외 처리를 넘겨주는 방법이다.

예외가 발생할 수 있는 메서드 뒤에 throws + 예외 종류를 붙여줌으로써 예외가 발생 시 자신을 호출한 상위 메서드로 예외를 넘겨준다.

public void doSomething() throws Exception { 
	//something
}

 

이러한 예외 처리 동작은 호출 스택(Call Stack)을 통해 수행된다. 기본적으로 메서드를 호출하면 메서드들은 호출 스택에 쌓이게 된다. 따라서 마지막으로 호출한 메서드에서부터 역순으로 올라가면서 예외 처리를 구현한 메서드를 찾아가는 것이다.

예를 들어 main() → sub1() → sub2() → doSomething() 순서로 메서드를 호출하였다고 하자.

doSomething(), sub1() 메서드에서는 throws를 통해 예외 처리를 상위 메서드로 넘겨주고 sub1() 메서드에서 try-catch 블록을 통해 예외를 처리해 주도록 구현하였다.

public static void main(String[] args) {
	sub1();
}
	
public static void sub1() {
	try {
		sub2();
	} catch (Exception e) {
		e.printStackTrace();
	}
}
	
public static void sub2() throws Exception {
	doSomething();
}

public static void doSomething() throws Exception {
	throw new Exception();
}

 

doSomething() 메서드에서 예외가 발생 시 다음과 같이 동작한다.

만약, 호출 스택을 계속해서 탐색하면서 예외 처리를 구현한 메서드를 찾는 과정에서 해당 예외 처리를 구현한 메서드가 존재하지 않는다면 런타임 시스템이 그냥 프로그램을 종료시킨다.

throws를 통해 상위 메서드에게 예외 처리를 넘겨주는 방식은 try-catch 블록 등을 통해 예외를 직접 처리하는 것보다 코드가 간결해질 수 있으며 더 효율적일 수도 있다.

하지만 자신에게 발생한 예외를 다른 곳에서 처리하도록 책임을 넘기는 방식이기 때문에 반드시 상위 메서드에서 책임을 지도록 구현해 줘야 한다.

마지막으로 예외를 생성하는 방법이다. 예외를 처리하는 방식을 알았다면 예외를 생성해서 던져줘야 한다. 이는 throw 키워드를 사용하여 예외를 생성한다. throws가 아님을 주의해야 한다.

throw 문장은 하나의 인수만을 요구하는데 바로 Throwable 객체이다. 따라서 적합한 예외 객체를 생성하여 던져주면 된다.

throw new IOException() //입출력에 관한 예외 발생시
throw new NullPointerException() //Null 객체에 참조시

 

예외를 처리해 줌으로써 어떤 부분에서 예외가 발생했는지 개발자가 쉽게 알 수 있는 장점이 있다. 또한, 예외 처리 코드와 정상적인 코드를 분리함으로써 가독성도 높일 수 있게 된다.

하지만 예외를 검사하고 처리하는 비용이 적은 편은 아니다. 따라서 충분히 해결할 수 있는 부분이라면 Exception 처리보다는 return -1, 0, 1 방식처럼 return type 등 다른 간단한 방법을 이용하는 것이 좋다.

반응형

댓글