1. 자바 예외처리(Exception Handling)
자바 프로그램이 동작하면서 다양한 문제들을 만나게 된다. 개발자의 로직에 헛점이 생겨서 발생하는 문제도 있고, 자바 프로그램이 실행되는 JVM에서 문제가 생기는 경우, 사용자가 잘 못된 입력을 하는 경우도 있다. 자바의 예외처리(Exception Handling)은 프로그램의 사소한 문제가 시스템 전체를 망가트리지 않도록하는 안전장치다.
견고한(Robust) 소프트웨어를 작성하기 위해서는 애플리케이션이 실행되면서 만날 수 있는 이런 문제들에 대해서 적당하게 처리하는 것이 필요하다.
1.1 Throwable 클래스
자바는 Throwable 객체에 문제의 상황에 대한 설명과 문제가 발생했을 때의 상황을 저장해서 throw
구문을 통해 상위 메소드로 전달할 수 있게 한다. 이렇게 메소드 안쪽에서 전달된 Throwable 객체는 자바의 try-catch 구문을 이용해서 예외처리를 하거나 다시 상위 메소드로 throw 된다.
Throwable 클래스를 상속해서 구현한 클래스에는 예외를 다루는 Exception 클래스와 에러를 다루는 Error 클래스가 있다.
1.2 Error(에러) 클래스
우선 에러에 대해서 알아보자. 에러는 자바 프로그램이 실행되는 JVM 환경에서 발생하는 문제를 다룬다. 시스템 레벨에서 발생한 에러들은 개발자가 처리하기 어려운 경우가 많다.
예를 들어 OutOfMemoryError가 대표적인데, JVM에서 사용할 수 있는 메모리 공간이 모두 소진되었다는 의미다. 당장 JVM의 메모리가 부족한 상황에서 애플리케이션 로직이 할 수 있는 것은 거의 없다. 이 경우 애플리케이션의 실행을 중단하고, JVM의 힙 메모리 사이즈를 늘리거나 불필요하게 객체들을 생성하는 곳이 없는지 소스코드 레벨에서 다시 로직을 확인해야한다.
시스템 레벨에서 발생하는 문제인 에러는 대부분 애플리케이션을 실행할 수 없을 정도의 심각한 상황을 의미한다. 따라서 Effective Java 같은 책에서는 에러로 던져진 상황은 try-catch로 잡아서 해결하려 하지 말라고 조언한다.
1.3 Exception(예외) 클래스
자바에서 예외처리를 위해 Exception 클래스를 사용한다. 아마도 소스코드레벨에서는 에러보다 Exception을 더 많이 보고 다뤘을 것이다.
예외는 사용자의 잘못된 입력이나 개발자의 잘못된 로직으로 발생하는 프로그램 오류를 의미한다. 대부분 애플리케이션 프로그램의 실행을 중단할 정도로 심각한 문제는 아니며, 국지적으로 예외상황에 대한 처리코드를 추가해서 전체적인 실행 흐름에는 문제가 없도록 만들 수 있다.
자바에서 사용할 수 있는 예외는 두 가지가 있다.
Checked Exception
Checked Exception은 반드시 코드에서 처리해야하는 예외다. Checked Exception은 try-catch 구문을 이용해 메소드에서 예외상황에 대한 처리를 해주던가 throws 구문을 메소드 시그니처에 붙여 해당 메소드를 실행할 경우 어떤 예외가 발생할 수 있는지를 호출자에게 알려줘야한다. 메소드의 호출자는 역시 동일하게 try-catch 구문으로 예외처리를 하던가 throws 구문으로 상위 메소드에게 알려줘야한다.
Checked Exception을 처리하지 않으면 자바 컴파일러는 컴파일 에러를 발생시키며 예외에 대한 처리를 해달라고 요청하게 된다.
자바에서는 RuntimeException 클래스와 이 클래스를 상속한 클래스들을 제외한 나머지 Exception들은 Checked Exception으로 간주된다.
Unchecked Exception
반면 Unchecked Exception은 컴파일 타임에 체크되지 않는다. try-catch 구문으로 잡아서 처리하거나 throws로 상위 메소드에 전달하지 않더라도 컴파일에는 문제가 없다. (다만 발생하지 않는다는 뜻은 아니다.)
대표적으로 RuntimeException을 상속한 NullPointerException, IndexOutOfBoundException 등이 있다. 위에서 봤던 에러 객체 역시 catch 처리를 하지 않아도 되므로 Unchecked라고 볼 수 있다.
2. 자주 사용되는 예외
NullPointerException
자바 실행에서 가장 빈번하게 발생하는 예외인 NullPointerException이다. 줄여서 NPE라고 부르기도 한다. 변수에 null 값을 할당해놓고, 참조를 따라가려할 때 발생한다.
Object obj = null;
obj.toString();
이 코드처럼 obj 객체를 참조하는 변수 obj가 null 값을 가지고 있는데 도트(.) 연산을 이용해서 참조를 따라가려고 했을 때 발생한다.
NullPointerException은 RuntimeException을 상속한 클래스로 Unchecked Exception이다. 이 예외의 발생을 줄이기 위해서 소스코드에 @Nullable, @Notnull 등의 애노테이션을 사용하거나 IDE 등이 NPE가 발생할 가능성이 있다고 안내해주기도 한다.
IllegalArgumentException
라이브러리 등을 사용할 때 잘 못된 인자를 입력했을 경우 발생할 수 있다.
ArrayIndexOutOfBoundsException
배열의 범위를 벗어난 인덱스를 사용했을 경우에 발생한다.
String[] str = new String[8];
str[8] = 'C'; // ArrayIndexOutOfBoundsException
예를 들어 8개의 엘리먼트를 저장할 수 있는 배열을 만들고 사용을 하는 코드에서 배열 접근 인덱스를 8로 주면 이 에러가 발생할 수 있다. 엘리먼트의 개수가 8개짜리 배열은 0~7의 인덱스를 사용해야하는데 8을 줬기 때문이다. (C였다면 메모리를 깰 수도 있는데.. 자바는 참 편하다)
IOException
네트워크 전송이나 디스크 사용같은 IO를 실행하다가 발생하는 예외다.
ClassCastException
변환 할 수 없는 클래스로 타입 캐스팅을 시도했을 때 발생한다. 타입 캐스팅은 객체와 같은 클래스 혹은 객체의 조상클래스로만 할 수 있다. 서로 관련이 없거나 자손 클래스로 타입 캐스팅을 시도했을 경우 이런 에러가 발생할 수 있다.
3. Java의 예외처리
자바에서 예외를 처리하는 방법에는 크게 두 가지가 있다.
try-catch 구문을 이용해서 발생할 수 있는 예외 상황에 대한 코드를 직접 작성하는 방법과 throws Exception 구문을 메소드 시그니처에 붙여서 메소드에서 발생할 수 있는 예외에 대한 처리를 호출하는 쪽에서 처리하도록 알려주는 방법이다.
try-catch 구문
자바는 예외 상황에 대한 코드 작성을 위해서 try-catch 구문을 제공한다.
try {
// 일반적인 애플리케이션 로직
} catch (Exception e) {
// Exception이 발생했을 때 실행되어야 하는 코드
} finally {
// 예외가 발생하던 발생하지 않던 수행되어야하는 코드
}
try 블럭에는 예외가 발생할 수 있는 로직이 들어간다.
catch 블럭에는 특정 예외가 발생한 상황에서 실행되어야 하는 코드들이 들어간다. try 블럭에서 예외가 발생하면 바로 catch 블럭으로 넘어가며, try 블럭에서 예외가 발생한 코드의 다음 부분들은 실행되지 않는다.
try {
// 일반적인 애플리케이션 로직
} catch (IOException e) {
// IOException이 발생했을 때 실행되어야 하는 코드
} catch (IllegalArgumentException e) {
// IllegalArgumentException이 발생했을 때 실행되어야 하는 코드
}finally {
// 예외가 발생하던 발생하지 않던 수행되어야하는 코드
}
catch 절은 if-else 구문처럼 여러개를 사용할 수 있다. 발생할 수 있는 예외가 여러가지일 경우 예외마다 catch 블럭을 사용할 수 있다.
마지막으로 finally 블럭에는 예외가 발생하거나 발생하지 않았을 때 모두 실행되어야 하는 코드가 들어간다. close() 메소드 등으로 잡고 있었던 리소스를 해제하는 동작이 대표적이다.
throws 구문
현재 작성중인 메소드에서 예외를 직접처리하지 않고, 메소드의 호출자에게 예외가 발생할 수 있음을 알리고 처리를 위임하는 방법도 있다.
현재 작성하고 있는 메소드의 호출자는 throws 구문을 통해 발생할 수 있는 예외에 대해서 인지할 수 있고, try-catch 구문을 이용해 처리하거나 상위로 다시 throws 구문을 통해 위임할 수 있다.
public class Example {
public void methodA() throws Exception {
methodB(null);
}
public void methodB(Object obj) throws Exception {
if (obj == null)
throw new Exception("obj is null");
System.out.println(obj.toString());
}
public static void main(String[] args) throws Exception {
Example example = new Example();
example.methodA();
}
}
이 코드를 실행한 결과는 다음과 같다.
Exception in thread "main" java.lang.Exception: obj is null
at Example.methodB(Example.java:8)
at Example.methodA(Example.java:3)
at Example.main(Example.java:15)
methodB()에서 발생한 예외가 메소드 콜스택(호출된 순서)을 따라서 상위로 전달되다가 결국 main() 메소드에서 시스템으로 전달된 후 애플리케이션의 수행이 종료되었다.
댓글