
자바에서는 발생할 수 있는 오류(예외, 에러)에 대해서
기본적으로 다음 두 개의 클래스를 통해 정의하였다.
public class Exception extends Throwable { ... }
public class Error extends Throwable { ... }
에러(Error) : 프로그램 코드로 수습될 수 없는 심각한 오류
OutOfMemoryError StackOverFlowError NoClassDefFoundError
예외(Exception) : 프로그램 코드로 수습될 수 있는 미약한 오류
ArithmeticException NullPointerException FileNotFoundException
이때 두 오류 클래스의 선언부를 보면
Throwable이라는 클래스를 상속 받는 것이 확인 가능한데
public class Throwable implements Serializable { ... }
이를 통해 Exception뿐만 아니라 Error 역시
throw 키워드를 통해 개발자가 에러 객체를 생성할 수 있음을 알 수 있다.
Error객체를 throw 하는 경우?
자바에서의 Error는 JVM 수준의 심각한 문제이다.
따라서 일반적으로 개발자가 Error를 직접 throw하는 것은 권장되지 않는다.
굳이 사용한다면 다음과같은 사항에 넣을 수 있다.// 절대 발생해서는 안 되는 코드 경로임을 명시 throw new AssertionError("이 코드는 절대 실행되면 안 됩니다.");// switch의 모든 case를 처리했음에도 도달했을 때 switch (direction) { case NORTH: ... case SOUTH: ... default: throw new AssertionError("알 수 없는 방향: " + direction); }// 테스트코드에서의 상황 throw new Error("테스트 환경에서 강제 실패");
Throwable 클래스는 왜 모든 오류(예외 + 에러) 객체의 조상 클래스일까?
이는 throw 키워드와 catch 블록 내에서
Throwable클래스 타입만 다룰 수 있도록 JVM이 설계되어 있기에
Throwable을 상속받는 클래스만 예외나 에러를,
던지고(throw) 받을(catch) 수 있도록 하였다.
또한 프레임워크나 테스트 도구에서 모든 종류의 오류를 한 번에
처리해야하는 코드의 경우 다음처럼
catch(Throwable t) { ... }
단일 표현이 가능하여 코드의 중복을 막아 복잡도를 개선할 수 있고
상속을 통한 자바의 다형성 측면이 드러남을 알 수 있다.
+프로그램의 오류는 크게 다음과 같은 분류로 나눌 수 있다.
- 컴파일 에러 : 컴파일 시 발생하는 오류
문법 오류타입 오류checked exception 미처리- 런타임 에러 : 런타임 시 발생하는 오류
NullPointerExceptionOutOfMemoryError- 논리적 에러 : 실행은 되지만, 의도와 다른 동작
Throwable
<- Error
<- Exception
<- RuntimeException (checked exception)
(checked exception)
<- IOException
<- ReflectiveOperationException
<- ClassNotFoundException
<- ...
이 중 개발자가 제어가 불가능한 Error 클래스를 제외하고
Exception 클래스의 상속 클래스들을 살펴보면 크게 다음 두 가지로 나눌 수 있다.
RuntimeException의 경우,
unchecked exception으로 컴파일 시점에
try-catch와 같은 예외 처리를 강제하지 않지만,
올바르게 예외처리가 되어있지 않다면 발생 시 예상치 못한 문제를 야기할 수 있다.// 사용자 id 길이 check해야하는데 런타임에 null값이 할당된 경우 String username = null; if (username.length() < 5) System.out.println("id 길이 5자 이상");// NullPointerException 발생 Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "username" is null
RuntimeException 외 Exception 상속 클래스의 경우
checked exception이며
소스코드에서try-catch와 같은 예외처리를 강제한다.
예외처리가 되지 않은 checked exception은,
컴파일을 진행할 경우 컴파일 에러가 발생한다.public static void main(String[] args) { Thread t = new Thread(() -> System.out.println("test Thread1")); t.start(); t.join(); }// InterruptedException java: unreported exception java.lang.InterruptedException; must be caught or declared to be thrown
정리하자면 컴파일러가 강제하는 checked exception의 예외처리와,
런타임 상황에서 발생할 수 있는 uncheked exception으로 인한 문제를 예방하기 위해
예외 처리가 필요하다.
예외처리의 근본적인 목적은 예외의 발생으로 인한
실행 중인 프로그램의 비정상 종료를 막으며 정상 실행상태를 유지하는 것이다.
Error와 Exception은 모두 실행 시 (runtime) 발생할 수 있는 오류이다.
→ 따라서 실행 중 발생하는 에러는 다음 문제이고,
=> 예외는 코드레벨에서 미리 처리해야 한다.
try {
// 예외가 발생할 가능성이 있는 문장들
} catch (Exception1 e1) {
// Exception1이 발생할 경우, 이를 처리하기 위한 문장
} catch (Exception2 e2) {
// Exception2가 발생할 경우, 이를 처리하기 위한 문장
} catch (ExceptionN eN){
// ExceptionN이 발생할 경우, 이를 처리하기 위한 문장
}
위 코드는 기본적인 try-catch의 문법 구조이다.
try 코드 블럭 내에서 예외가 발생하면, 발생한 예외에 해당하는 인스턴스가 만들어진다.
이 인스턴스는 발생한 예외에 대한 정보가 담겨있으며,
catch 문의 괄호 () 를 통해 참조변수로 정보를 얻을 수 있다.
예외 발생 시
try-catch동작 과정
try블록 내에서 예외가 발생했다면, 코드 순서대로 catch의 괄호 내 선언된
참조변수의 타입과 발생한 예외를,
instanceof 연산자를 통해 검사하는 과정을 거친다.try { ... } catch (Exception e) { ... } catch (IllegalArgumentException e) { ... } // Exception의 경우 모든 예외의 조상이기에 // 하단 IllegalArgumentException에 해당하더라도 // catch (Exception e) catch 블럭 내부만 실행된다.
개발자는 throw 키워드를 통해 고의적으로 예외(Exception)를 발생시킬 수 있다.
class ExceptionEx {
public static void main(String[] args) {
try {
Exception e = new Exception("고의 발생 예외");
throw e; // 예외를 발생시킨다.
// Exception객체를 만들 때 String을 인자로 넣어주면
// 해당 문자열이 Exception 객체의 메시지로 저장된다.
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
System.out.println("정상 종료");
}
}
고의 발생 예외 // by e.getMessage()
정상 종료 // catch 이후 로직 진행으로 인해
java.lang.Exception: 고의 발생 예외
at ExceptionEx.main(Main.java:4)
이러한 고의적 예외 발생 방식을 통해
checked exception과 unchecked exception의 차이에 대해 알 수 있는데
class ExceptionEx {
public static void main(String[] args) {
// throw new ArrayIndexOutOfBoundsException();
// throw new IllegalArgumentException();
// throw new IOException();
// throw new ClassNotFoundException();
}
}
throw를 통해 고의적으로 발생 시킨 각 예외 객체를,
실제로 주석을 풀어 main 메서드에서 실행시켜보면 각각 아래와 같은 결과를 얻는다.
ArrayIndexOutOfBoundsException IllegalArgumentExceptionException in thread "main" java.lang.ArrayIndexOutOfBoundsException
at ExceptionEx.main(Main.java:5)
Exception in thread "main" java.lang.IllegalArgumentException
at ExceptionEx.main(Main.java:6)
IOException ClassNotFoundExceptionjava: unreported exception java.io.IOException;
must be caught or declared to be thrown
java: unreported exception java.lang.ClassNotFoundException;
must be caught or declared to be thrown
checked exception인 ArrayIndexOutOfBoundsException 와 IllegalArgumentException의 경우
컴파일이 정상 진행되나 이후 런타임에서 예외가 발생하여 프로그램이 종료된다.
반대로 checked exception인 IOException과 ClassNotFoundException은
예외처리를 하지 않아 컴파일 조차 진행되지 않음을 알 수 있다.
그렇다면
throw를 통한 예외 객체는 왜 쓸까?
throw로 예외 객체를 만드는 이유 중 하나는
단순return,if와 같은 분기를 통해 표현하기 어려운
비정상 상황을 명시적으로 하여 알리기 위해서이다.
- 예외 발생 시
return사용 분기
호출자가 반환된 값을 제대로 처리하지 못하면, 예외가 묻힘- 예외 발생 시 if 사용 분기
메서드 호출 스택이 깊게 쌓여 있다면, 오류 전달하기 번거러움
throw의 경우 발생한 예외를 강제로 위로 전달하고 무시할 수 없게 한다.
throw를 통해 발생한 예외 객체는 try-catch와 같은
예외 처리를 담당하는 부분까지 Call Stack을 타고 올라가며
예외처리 담당 부분을 만나면 catch의 괄호 내에서
instanceof 연산을 통해 확인하고 이를 처리하는 것이다.
메서드의 선언부에 붙은 throws 키워드는 실제로 예외를 처리한다가 아닌,
예외 처리의 책임을 해당 메서드 호출부에 넘긴다는 의미이다.
void method() throws Exception1, Exception2, ... ExceptionN {
...
}
한 throws 로 여러 예외 클래스 타입을 선언부에 쓸 수 있는데
이를 통해 해당 메서드를 사용할 때 어떠한 예외가 발생할 수 있는 지 알 수 있다.
checked exception의 경우try-catch로 감싸거나,throws를 통해 다시 상위 호출 메서드로 책임을 넘길 수 있다.unchecked exception의 경우main 메서드에 throws 가 붙으면 어떻게 될까?
class ExceptionEx { public static void main(String[] args) throws ClassNotFoundException { throw new ClassNotFoundException(); } }위 예제를 보면
ClassNotFoundException은
checked exception이기에 원래는 예외처리가 없으면 컴파일되지 않는 코드임에도
main 메서드 선언부에 붙은 throws를 통해 컴파일이 진행되어 프로그램이 실행된다.
이후 main을 호출한 JVM에서 해당 예외를 받아 예외에 대해 출력하며,
프로그램이 비정상 종료된다.Exception in thread "main" java.lang.ClassNotFoundException at ExceptionEx.main(Main.java:5)