예외 처리는 왜 필요한가?
오류(error)는 시스템 레벨에서 프로그램에 심각한 문제를 야기하여 실행 중인 프로그램을 종료시킨다
이러한 오류는 개발자가 미리 예측하여 처리할 수 없는 것이 대부분이므로, 오류에 대한 처리는 할 수 없다
하지만 예외(exception)는 오류와 마찬가지로 실행 중인 프로그램을 비정상적으로 종료시키지만, 발생할 수 있는 상황을 미리 예측하여 처리할 수 있다
따라서 개발자는 예외 처리(exception handling)를 통해 예외 상황을 처리할 수 있도록 코드의 흐름을 바꿀 수 있어야 한다
try {
예외를 처리하길 원하는 실행 코드;
} catch (e1) {
e1 예외가 발생할 경우에 실행될 코드;
} catch (e2) {
e2 예외가 발생할 경우에 실행될 코드;
}
...
finally {
예외 발생 여부와 상관없이 무조건 실행될 코드;
}
try 블록 : 맨 먼저 실행되는 코드로 여기에서 발생한 예외는 catch 블록에서 처리됨
catch 블록 : try 블록에서 발생한 예외 코드나 예외 객체를 인수로 전달받아 그 처리를 담당함
finally 블록 : try 블록에서 예외가 발생하건 안 하건 맨 마지막에 무조건 실행됨
catch 블록과 finally 블록은 선택적인 옵션으로 필수가 아니다
모든 catch 블록에서 예외를 처리할 수 없으면 예외를 처리하지 못한 채로 프로그램이 종료된다
Exception 클래스
모든 예외의 조상 클래스가 되는 Exception 클래스
구분:
1. RuntimeException 클래스
2. 그 외의 Exception 클래스의 자식 클래스
RuntimeException 클래스를 상속받는 자식 클래스들은 주로 치명적인 예외 상황을 발생시키지 않는 예외들로 구성되기 때문에 프로그램 작성 시 예외가 발생하지 않도록 주의를 기울이면 된다
하지만 그 외의 Exception 클래스에 속하는 자식 클래스들은 치명적인 예외 상황을 발생시키므로, 반드시 try / catch 문을 사용하여 예외를 처리해야만 하고 자바 컴파일러는 이런 예외들이 발생할 가능성이 있는 구문에는 반드시 예외를 처리하도록 강제한다
(안하면 컴파일 오류를 발생시킴)
PrintStream 클래스의 write() 메소드를 사용하여 byte 타입 배열의 모든 요소를 출력하는 예제
public class Exception01 {
public static void main(String[] args) {
byte[] list = {'a', 'b', 'c'};
try {
System.out.write(list);
} catch (IOException e) {
e.printStackTrace();
}
}
}
abc
위와 같이 예외 처리를 해주지 않는다면 write() 메소드에서 발생할 수 있는 IOException에 대한 예외를 처리하지 않았으므로, 컴파일 시 오류가 발생한다
Throwable 클래스
모든 예외의 조상이 되는 Exception 클래스와 모든 오류의 조상이 되는 Error 클래스의 부모 클래스
Throwable 타입과 이 클래스를 상속받은 서브 타입만이 자바 가상 머신(JVM)이나 throw 키워드에 의해 던져질 수 있다
Throwable에 포함된 예외나 오류에 관한 다양한 정보를 확인할 수 있는 메소드들
String getMessage()
: 해당 throwable 객체에 대한 자세한 내용을 문자열로 반환함.
void printStackTrace()
: 해당 throwable 객체와 표준 오류 스트림에서 해당 객체의 스택 트레이스를 출력함.
String toString()
: 해당 throwable 객체에 대한 간략한 내용을 문자열로 반환함.
일부러 숫자를 0으로 나눠 ArithmeticException 오류를 발생시키는 예제입니다.
try {
System.out.println(5 / 0);
} catch (ArithmeticException e) {
System.out.println("현재 발생한 예외 정보 : " + e.getMessage());
}
현재 발생한 예외 정보 : / by zero
자바에서 자주 사용되는 예외 클래스
1. ClassCastException 수행할 수 없는 타입 변환이 진행될 경우
2. ArrayIndexOutOfBoundsException 배열에 잘못된 인덱스를 사용하여 접근할 경우
3. NullPointerException null 객체의 인스턴스 메소드를 호출하는 등의 경우
4. ArithmeticException 산술 연산에서 정수를 0으로 나누는 등 연산을 수행할 수 없는 경우
예외 발생 시키기
Exception e = new Exception("오류메시지");
...
throw e;
throw를 통해 강제로 예외를 발생시키고 생성자에 전달된 문자열은 getMessage() 메소드를 사용하여 오류 메시지로 출력할 수 있다
public class Exception03 {
static void handlingException() {
try {
throw new Exception();
} catch (Exception e) {
System.out.println("호출된 메소드에서 예외가 처리됨!");
}
}
public static void main(String[] args) {
try {
handlingException();
} catch (Exception e) {
System.out.println("main() 메소드에서 예외가 처리됨!");
}
}
}
호출된 메소드에서 예외가 처리됨!
위와 같이 main 메소드에서는 호출한 메소드에서 예외가 발생했는지 알 수 없다
그래서 아래와 같이 throws 키워드를 사용하여 해당 메서드에서 처리하지 않는 예외를 호출한 쪽으로 위임 할 수 있다
public class Exception04 {
static void handlingException() throws Exception { throw new Exception(); }
public static void main(String[] args) {
try {
handlingException();
} catch (Exception e) {
System.out.println("main() 메소드에서 예외가 처리됨!");
}
}
}
main() 메소드에서 예외가 처리됨!
사용자 정의 예외 클래스
자바에서는 Exception 클래스를 상속받아 자신만의 새로운 예외 클래스를 정의하여 사용할 수 있다
사용자 정의 예외 클래스에는 생성자뿐만 아니라 필드 및 메소드도 원하는 만큼 추가할 수 있음
class MyException extends RuntimeException {
MyException(String errMsg) {
super(errMsg);
}
}
요즘에는 위와 같이 Exception 클래스가 아닌 예외 처리를 강제하지 않는 RuntimeException 클래스를 상속받아 작성하는 경우가 많다
try-with-resources 문
Java SE7 부터 도입된 사용한 자원을 자동으로 해제해주는 구문
static String readFile(String filePath) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(filePath));
try {
return br.readLine();
} finally {
if (br != null)
br.close();
}
}
Java SE 7 이전에는 finally 블록을 사용하여 사용한 파일을 닫아줘야 했다.
(BufferedReader에 대한 자세한 내용은 java stream에서 다룸)
하지만 아래와 같이 try문 옆에 괄호를 추가하여 파일을 열거나 자원을 할당하는 명령문을 명시하면, 해당 try 블록이 끝나자마자 자동으로 파일을 닫거나 할당된 자원을 해제해 준다
static String readFile(String filePath) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
return br.readLine();
}
}