예외처리
예외 클래스
오류란 무엇인가?
- 프로그램에서 오류가 발생하는 상황은 Compile error와 runtime error 두가지다
- Compile error : 프로그램 코드 작성 실수로 발생하는 오류. 개발 환경에서 대부분의 원인을 알 수 있다.
- Runtime error : 실행 중인 프로그램이 의도하지 않은 동작을 하거나 프로그램이 중지되는 실행 오류. 프로그램을 잘못 구현해 의도한 바와 다르게 실행됙어 생기는 오류를 버그라고 한다.
- 실제 서비스 제공중인 프로그램이 오류로 인해 종료되면 서비스가 중지되므로 문제가 심각해진다. 실행 중 오류가 발생하면 상황을 재현해야 하는데 실제 시스템이나 서비스가 운영중인 상황에서는 쉽지 않다. 그래서 로그를 남기는것이 중요하다.
- 자바에서는 비정상 종료를 최대한 줄이기 위해 다양한 예외 처리 방법을 가지고 있다.
- 예외 처리를 하응 목적은 일단 프로그램이 비정상 종료되는 것을 방지하기 위한것이다. 그리고 예외가 발생했을 때 로그를 남겨 두면 예외 상황을 파악하고 버그를 수정하는데 도움을 받을 수 있다.
오류와 예외
- Error : JVM에서 발생하는 시스템 오류. 예로는 할당가능한 동적 메모리가 없거나 스택 메모리의 오버플로우가 발생했을 때 등. 제어가 불가능하다
- Exception : 프로그램에서 제어가 가능하다. 예를 들어 프로그램에서 파일을 읽어들이는데 파일이 없는 경우, 배열 값을 출력하는데 배열 요소가 없는 경우 등.
- 오류 클래스는 모두 Throwable 클래스에서 상속받는다. 프로그램에서 제어하는 부분은 Exception 클래스와 그 하위에 있는 클래스들이다.
checked exception / unchecked exception
- 검사 예외 / 비검사 예외 라고도 한다.
- 검사예외 : 명시적으로 try-catch로 감싸줘야 하는 코드. Exception을 상속받은 throws가 선언된 경우에는 반드시 try-catch를 사용해서 감시해야된다. (Exception, IOException..)
- 비검사예외 : try-catch는 선택사항이다. (RuntimeException, NPE..)
- 최근에는 검사예외를 지양하고 있는데 그 이유는 다음과 같다
1) 반드시 해당 오류를 처리해야함을 강제함
2) 메서드 시그니처다보니 한번 정해지면 수정이 어려움 - 추가적으로 읽어볼만한 글
예외 클래스의 종류
- RuntimeException은 프로그램 실행 도중 발생할 수 있는 오류에 대한 예외를 처리한다. 또한 try-catch를 사용하지 않아도 컴파일 오류가 나지 않는다.
- 예를들어 ArithmeticException은 산술 연산 중에 0으로 나누기 같은 경우 발생하는 예외다. 이 경우는 컴파일러에서 체그가 되지 않는다.
예외처리하기
try-catch
try {
// 예외가 발생할 수 있는 코드 부분
} catch(처리할 예외 타입 e){
//try 블록 안에서 예외가 발생했을때 예외를 처리하는 부분
}
public class ArrayExceptionHandling {
public static void main(String[] args) {
int[] arr = new int[5];
try {
for (int i = 0; i <= 5; i++) {
arr[i] = i;
System.out.println(arr[i]);
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(e);
System.out.println("예외 처리 부분");
}
System.out.println("프로그램 종료");
}
}
- 에러가 나는 클래스를 예외로 잡아서 가지고 온다.
- 예외처리를 하지 않는다면 마지막 “프로그램 종료” 부분까지 실행되지 않는다.
컴파일러에 의해 예외가 체크되는 경우
파일 입출력에서 발생하는 예외 처리하기
- FileInputStream을 이용해 예외처리를 하는 코드를 간단하게 작성해보자
- 클래스만 이용해 코드를 작성하게 되면 컴파일러가 위와 같이 알려준다.
public class StreamExceptionHandling {
public static void main(String[] args) {
try {
FileInputStream fileInputStream = new FileInputStream("context.txt");
} catch (FileNotFoundException e) {
// e.printStackTrace();
System.out.println(e);
}
System.out.println("Execution.");
}
}
- 아래는 실행결과로 예외가 처리된것을 알 수 있다.
java.io.FileNotFoundException: context.txt (No such file or directory)
Execution.
try-catch-finally
- 프로그램에서 사용한 리소스는 프로그램이 종료되면 자동으로 해제된다. 그러나 끝나지 않고 계속 수행되는 서비스에 리소스를 계속 열기만 하면 문제가 발생한다. 사용한 리소스는
close()
로 닫아줘야 한다.
try {
FileInputStream fileInputStream = new FileInputStream("context.txt");
if (fileInputStream != null) {
try { // <<<<<<<<<<<<<<<<<<
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (FileNotFoundException e) {
// e.printStackTrace();
System.out.println(e);
}
- 만약 발생할 수 있는 상황이 여러개라면 예외 상황 수만큼 구현해야 한다. 모든 try-catch문을 작성하는것은 번거롭다.
- 이때 사용할 수 있는 블록이 finally다.
public class StreamExceptionFinallyHandling {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("context.txt");
} catch (FileNotFoundException e) {
System.out.println(e);
return; // <<<<<<<<<<<<<<
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("Always execution."); // <<<<<<<<<<<<<<
}
System.out.println("Execution.");
}
}
java.io.FileNotFoundException: context.txt (No such file or directory)
Always execution.
- return으로 탈출과 동시에 finally의; “Always execution.”만 실행되는 것을 보면 예외와 상관없이 finally가 실행되는 것을 알 수 있다.
try-with-resources
- close()를 명시적으로 호출하지 않아도 try블록 내에서 열린 리소스를 자동으로 닫을 수 있다 (since Java7)
try-with-resources
를 사용하려면 해당 리소스가 AutoCloseable
을 구현해야 한다. AutoCloseable
에는 close()가 있고 자동으로 호출 된다.
AutoCloseable Interface
public class AutoCloseableObject implements AutoCloseable {
@Override
public void close() throws Exception {
System.out.println("Resource closed.");
}
}
public class AurtoCloseableTest {
public static void main(String[] args) {
try (AutoCloseableObject obj = new AutoCloseableObject()) {
throw new Exception(); // 강제로 예외 발생
} catch (Exception e) {
System.out.println(e);
System.out.println("Exception.");
}
}
}
Resource closed.
java.lang.Exception
Exception.
향상된 try-with-resources (since Java9)
- 자바9 부터는 try괄호 안에 외부에서 선언한 변수를 사용할 수 있다.
//...
AutoCloseableObject obj = new AutoCloseableObject();
try (obj) {
throw new Exception();
} catch (Exception e) {
System.out.println(e);
System.out.println("Exception.");
}
//...
예외 처리 미루기
throws
- try-catch로 감싸는것 외에도 throws 선언을 추가하는 방법으로 예외를 처리할 수 있다.
throws를 활용하여 예외 처리 미루기
public class ThrowsException {
public Class loadClass(String fileName, String className)
throws FileNotFoundException, ClassNotFoundException {
FileInputStream fileInputStream = new FileInputStream(fileName);
Class c = Class.forName(className);
return c;
}
public static void main(String[] args) {
ThrowsException test = new ThrowsException();
try {
test.loadClass("context.txt", "java.lang.String");
} catch (FileNotFoundException | ClassNotFoundException e) {
System.out.println(e);
}
System.out.println("Execution.");
}
}
- loadClass()에서 선언된 thorws는 두 예외를 메소드가 호출 될때 처리하도록 미룬다는 의미 입니다.
- 미뤄진 예외처리는 실제로 메소드가 호출 되는 main()에서 발생합니다.
- 예외를 처리할때 or 연산으로 처리가 가능합니다.
- 예기치 못한 그외의 예외를 처리하려면 아래와 같이 추가하면 됩니다.
try {
test.loadClass("context.txt", "java.lang.String");
} catch (FileNotFoundException | ClassNotFoundException e) {
System.out.println(e);
} catch (Exception e){ // <<<<<<<<<<<<<<<
System.out.println(e);
}
다중 예외 처리에서 주의 사항
- 예외 처리를 할 때 Exception을 맨 위로 가지고 오면 아래에 있는 예외가 호출 되지 않습니다. (주의 요망)
사용자 지정 예외
- 예외 클래스를 직접 만들어 예외를 발생시키고 예외 처리 코드를 구현할 수 있다.
- RuntimeException만으로는 너무 두루뭉술 하므로 상황을 잘 설명할 수 있는 예외클래스를 작성하는것이 중요하다.
사용자 지정 예외 클래스 구현하기
public class CustomFormatException extends Exception {
public CustomFormatException(String message) {
super(message);
}
}
public class CustomFormatTest {
private String userId;
public String getUserId() {
return userId;
}
public void setUserId(String userId) throws CustomFormatException {
if (userId == null) {
throw new CustomFormatException("Must have ID String.");
} else if (userId.length() < 8 || userId.length() > 20) {
throw new CustomFormatException(
"ID must be at least 8 characters and less than 20 characters.");
}
this.userId = userId;
}
public static void main(String[] args) {
CustomFormatTest test = new CustomFormatTest();
String userId = null;
try {
test.setUserId(userId);
} catch (CustomFormatException e) {
System.out.println(e.getMessage());
}
userId = "aaaaaa";
try {
test.setUserId(userId);
} catch (CustomFormatException e) {
System.out.println(e.getMessage());
}
}
}
Must have ID String.
ID must be at least 8 characters and less than 20 characters.