예외 처리(Exception Handling)
프로그램 실행도중에 발생하는 에러
는 어쩔 수 없지만, 예외(Exception)
은 프로그래머가 미리 대처를 할 수 있다.
사용자의 원인으로 발생하는 예외는 개발자가 미리 대처를 해줄 수 있다.
예외 처리(exception handling)란, 프로그래머가 예기치못한 예외의 발생에 미리 대처하는 코드를 작성하는 것으로, 실행중인 프로그램의 비정상적인 종료를 막고, 상태를 정상상태로 유지하는 것이 목적이다.
예외처리의 목적
정의 - 프로그램 실행 시 발생할 수 있는 발생할 수 있는 예외에 대비한 코드를 작성하는것
목적 - 프로그램의 비정상 종료를 막고 정상적인 실행상태를 유지하는것
- Error(에러클래스) 와 예외(Exception)은 모두 runtime 시에 발생하는 오류이다
발생한 예외를 처리하지 못하면 프로그램은 비정상적으로 종료되며 처리되지 못한 예외(uncatght exception)는 JVM의 예외처리기(UncaughtExceptionHandler) 가 받아서 예외의 원인을 화면에 출력한다.
자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
예외를 처리하기 하기위해서는 try - catch 문을 사용하며 그 구조는 다음과 같다.
- finally 구문로 코드가 실행된 후에 무조건 실행할 코드도 작성할 수 있다.
public static void exceptionHandling(){
try {
// do something - 예외가 발생할 수 있는 코드
} catch (Exception e) {
// Exception 예외시 처리할 코드
} catch (OtherException e2) {
// 위에서 캐치한 Exception과 다른 OtherException 예외시 처리할 코드
} catch (AnotherException e3 | AnotherException2 e4) {
// AnotherException 과 AnotherException2 예외를 같은 동작으로 처리할 코드
}
finally {
// 예외와 상관없이 항상 실행할 코드
}
// try-catch가 종료되면 메소드를 나온다.
}
- try 블럭 ({}) 안에는 예외가 발생할 수 있는 코드를 작성한다.
- try 블럭 다음 catch 블럭({}) (예외이름, 객체명) 1개 이상의 catch 블럭 이 올 수 있다.
- 여러 블럭이 올 수 있지만 이중 발생한 예외의 종류와 일치하는 단 한개의 catch블럭만 수행된다.
- 멀티 catch :
catch (AnotherException e3 | AnotherException2 e4) {
- JDK 1.7부터 추가된 기능.
- " | " 기호를 사양해 여러 catch블럭을 하나로 합칠 수 있으며 합칠 수 있는 예외 클래스의 개수는 제한이 없다.
- 그러나 같이 catch 되어 있는 예외 클래스가 부모/자식 관계, 즉 상속받은 예외클래스라면 컴파일 에러가 발생
- 왜냐하면, 자식 클래스로 잡아낼 수 있는 예외는 부모 클래스로도 잡아낼 수 있기 때문에 코드가 중복된 것이나 마찬가지이기 때문이다.
- 또한 멀티캐치는 하나의 블록으로 여러 예외를 처리하는 것이기 때문에 멀티 캐치 블록 내에서는 발생한 예외가 정확이 어디에 속한 것인지 알 수 없다. 그래서 참조 변수 e에는 ‘|’로 연결된 예외들의 공통 조상 클래스에 대한 정보가 담긴다.
- try 블럭만 존재할 수 없으며, catch블럭도 세트로 존재한다.
- if문과 달리 try 블럭이나 catch 블럭 내에 포함된 문장이 하나뿐이여도 괄호{} 를 생략할 수 없다.
- catch 블럭 다음에는 finally가 올 수 있으며, finally 블럭은 예외가 발생하던 발생하지 않던 상관없이 항상 실행할 코드를 작성한다.
- 만약 try문에서 예외가 발생하게 된다면 발생한 라인 밑부분의 코드는 실행이 되지않고 catch문으로 넘어가게 된다. 이와 상관없이 항상 실행할 코드를 finally문에 작성한다
멀티 catch 문을 작성할때의 주의점
상위 예외클래스 블럭이 하위 예외 클래스블럭보다 아래에 위치해야 한다.
하위 예외 클래스가 이미 상위 예외 클래스에 속해있기 때문에, 상위 예외 클래스 블럭이 더 앞에있으면 하위 예외 클래스 캐치블럭을 처리하지 않고 상위 예외클래스 캐치블럭이 먼저 처리한다.
catch 블럭에 있는 Exception Class는 자바에 있는 Exception과 Error의 superClass인 Throwable을 상속한 클래스 중 하나여야만 한다.
finally block
finally block은 try block이 끝난 후 또는 예상치 못한 에러가 발생 했을때도 실행을 보장한다.
- 하지만 만약에 JVM이 try catch block을 실행 중에 종료된다면 finally block은 실행되지 않는다.
마찬가지로 Thread가 try catch block을 실행 중에 interrupt 당하거나 kill 당한다면 finally block은 실행되지 않는다
- finally block은 cleanup code를 넣어서 resource leak(리소스 오버 등)을 막을 용도로 사용하는게 좋다.
printStackTrace(), getMessage()
예외처리를 할 때 발생한 예외의 대한 정보가 담긴 메서드이다.
catch 블럭의 참조변수를 통해 인스턴스에 접근할 수 있다. (Exception e
) = e가 참조변수이다
- printStackTrace()
- 예외 발생 당시에 호출스택(CallStack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력한다.
- getMessage()
- 발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
printStaticTrace(PrintStream s) 또는 printStatckTrace(PrintWriter s)를 사용하면 발생한 예외에 대한 정보를 파일에 저장도 가능하다
try-catch문에서의 흐름
- try 블럭 내에서 예외가 발생한 경우
- 발생한 예외와 일치하는 catch블럭이 있는지 확인
- 일치하는 catch 블럭을 찾게되면 그 catch블럭을 수행하고 try-catch문을 빠져나간다.
- 만약 일치하는 catch블럭을 차지 못하면 예외는 처리되지 못하며 메소드 밖으로 던져지게 된다
- try 블럭 내에서 예외가 발생하지 않은 경우
- catch블럭을 거치지 않고 finally문으로 넘어가거나, finally 문이 없다면 전체 try-catch문을 빠져나가서 다음 코드를 수행하거나 메소드는 종료된다.
try-with-resources
- exception시 resources를 자동으로 close() 해준다.
- 사용 로직을 작성할 때 객체는
AutoCloseable 인터페이스를 구현한 객체
여야 한다. - java 7 부터 추가됨.
AutoCloseable 인터페이스
public interface AutoCloseable{
void close() throws Exception;
}
- 이 인터페이스가 구현되어 있다면 try-with-resources 사용 시 자동으로 open한 리소스를 close() 해준다.
try-catch-finally
FileOutputStream out = null;
try{
out = new FIleOutputStream("file.txt");
// do something
}catch(FileNotFoundException e){
e.printStackTrace();
}finally{
if(out != null){
try{
out.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
try-with-resources
try(FileOutputSTream out = new FileOutpuStream("file.txt")){
// do something
}catch(IOException e){
e.printStackTrace()l;
}
- 기존의 try-catch-finally 블럭 보다 훨씬 간결하게 처리할 수 있다.
예외 발생시키기
throw
키워드를 이용해서 개발자가 고의로 직접 예외를 발생시킬 수 있다.
- 연산자 new를 이용해서 발생시키려는 예외클래스의 객체를 만든다음에 키워드 throw 를 통해서 예외를 발생시킨다
Exception e = new Exception("고의 예외");
throw e;
// 또는
throw new Exception("고의 예외");
- RuntimeException과 UncheckedException 은 처리 하지 않아도 실행은 되지만, CheckedException은 처리하지 않으면 컴파일이 되지 않는다.
메서드에 예외 선언해서 try-catch문 사용하지 않고 예외 던지기
throws 키워드를 통해 메서드에 예외를 선언할 수 있다. 여러 개의 메서드를 쉼표로 구분해서 선언할 수 있다. 형태는 다음과 같다.
void method() throws Exception1, Exception2, … ExceptionN {
// 메서드 내용/
}
throws는 메서드 선언부에 예외를 선언해둠으로써, 해당 메서드를 사용하는 사람들이 어떤 예외를 처리해야 하는 지를 알려주는 역할을 한다.
만일, 아래와 같이 모든 예외의 최고 조상인 Exception 클래스를 메서드에 throws로 선언하면 이 메서드는 모든 종류의 예외가 발생할 가능성이 있다는 뜻이다.
void someMethod() throws Exception {
// 메서드 내용
}
- 이렇게 예외를 선언하면 이 예외뿐만 아니라 그 자손타입의 예외까지도 발생할 수 있다는 점에 주의해야한다.
throws 자체는 예외의 처리와는 관계가 없다.
throws는 해당 메서드에서 예외를 처리하지 않고, 해당 메서드를 사용하는 쪽이 예외를 처리하도록 책임을 전가하는 역할을 한다.
- 이 메서드를 사용하는 쪽에서는 이에 대한 처리를 강제함으로써 견고한 프로그램 코드를 작성할 수 있도록 도와준다.
public class Test {
public void someMethod() throws Exception {
// 메서드 내용
}
public void OtherMethod() {
try {
someMethod(); // 이 메소드 호출 시 예외처리를 하지 않는다면 컴파일 에러.
} catch(Exception e) {
// 예외 처리
}
}
}
- 만약 OtherMethod에서도 예외 처리를 하지 않고 밖으로 던진다면 다음과 같이 메소드에 throws를 추가해주면 된다
public void OtherMethod() throws Exception {
someMethod();
}
throws를 사용하는 것은 주의하고 한번 더 생각해보자.
물론 메서드안에서 try/catch문을 사용하면 예외처리를 함으로써 메서드가 복잡해져 읽기 어렵고, 추가적인 예외가 발생할 수 있다.
이런 상황에서 throws를 사용하면 다른 팀원들이 메서드를 사용하는 상황에 맞게 예외처리를 할 수 있어진다.
또한 메서드 이름 옆에 발생할 수 있는 예외가 명시적으로 작성이 되므로 코드작성에 주의를 할 수 도 있다.하지만 throws를 계속해서 사용하다보면 main 메서드까지 가게 되는데,
main 메서드 또한 예외를 throws 하게 된다면 JVM이 대신 처리하게 된다.
결국 예외를 처리하지 않은 것과 다름없다..때문에 예외처리가 귀찮아서 throws를 사용하는 것은 금물이다.
예외 전파
아래와 같은 코드에서 doSomething() 메서드 수행 시 Exception이 발생하면 어떻게 될까?
try 의 별도 catch 문이 존재하지 않아서 Exception은 상위로 throw 가 될까? 아니면 skip 될까?
try{
doSomething();
} finally{
System.out.println("finally");
}
Call Stack
JVM에서 메소드가 실행되면 메모리의 stack 영역에 Stack Frame이 쌓이게 된다.
( Execution Stack , Call Stack)
- 메서드의 작업에 필요한 메모리 공간을 제공
- 메서드 호출시 호출 스택에 호출된 메서드를 위한 메모리가 할당되며, 메서드가 작업을 수행하는 동안 지역변수(매개변수 포함) 들과 연산의 중간결과등을 저장하는데 사용됨
- 메소드 작업이 끝나면 할당되었던 메모리공간은 반환되어 비워짐
- 특징
- 메서드가 호출되면 수행에 필요한 만큼의 메모리를 스택에 할당
- 메서드가 수행을 마치고나면 사용했던 메모리를 반환하고 스택에서 제거
- 호출스택의 제일 위에 있는 메서드가 현재 실행중인 메서드
- 아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드
즉, stack 영역은 Stack Frame 을 저장하는 Stack 이며, JVM은 오직 Stack Frame을 Push 하고 Pop 하는 작업만 하게 된다.
- Java에서는 Exception 이 발생했을 경우 Exception Handler 가 처리한다.
- 메서드에서 Exception이 발생하면 해당 메서드는 Exception Object(exception의 이름과 설명, 프로그램의 현재 상태, exception 발생 위치의 메서드 리스트[call stack]를 포함)를 생성하고 JVM에 넘긴다.
- 이러한 과정을
Exception을 던진다(throwing an Exception)
라고 표현한다.
- Exception이 발생했을 때, Exception Handler는 Exception Handler 를 call stack 을 이용하여 역방향으로 Exception Handler를 탐색한다.
- 즉 예외가 발생한 메서드를 실행시키기 위해 실행했던 모든 메서드를 반대 순서대로 찾는다는 것이다.
- 탐색하면서 Exception Handler 가 처리할 수 있는 Exception 타입인지 체크 한 뒤 맞다면 처리하고 아니라면 상위로 전달된다.
- 즉, 발생한 Exception 에 대해서 처리가 가능한 Handler 를 찾을 때까지 상위로 전달되면서 찾아가게 된다.
- 적절한 Handler 를 찾지 못한다면 JVM 까지 전달되며, 최종적으로 JVM
default exception handler
가 Exception 을 처리하게 되는데, 모든 call stack을 찾아도 적절한 handler를 찾지 못했다면 default exception handler가 실행된다. - default exception handler는 exception 정보를 노출하며 해당 thread를 중지시킨다.
예외처리 비용
- 예외를 사용한다는 것 자체가 비용이 비싸다.
- try-catch를 동작 하면서 발생하는 검사들도 하나의 원인이겠지만,
- Throwable 생성자의 fillInStackTrace() 메서드가 주 원인이다.
- 이 메서드는 예외가 발생한 메서드의 Call Stack의 Stack Trace 를 모두 출력해주기 때문이다.
- 예외가 발생되면 예외의 내용을 보여주는 Stack Trace의 각 라인은 하나의 Stack Frame을 표현하는 것이며, 메모리 영역의 Stack 에 쌓여있는 Stack Frame 들을 pop 하여 출력한다.
- 충분히 로직으로 사용할 수 있는 것이라면 Exception보다 Return Type이나 입력 값 등을 통해 작업하는 것이 좋다.
반복문 내에서는 Checked Exception에 대한 처리는 지양하자.
for (String item : items) {
try {
insert(item);
}catch (SQLException e) {
e.printStackTrace();
}
}
- 반복문 내에서 Checked Exception에 대한 예외처리 구문이 들어가게 되면 성능은 몇배로 더 떨어지게 된다.
- 이러한 경우에는 insert에서 예외 발생 시, RuntimeException으로 한번 Wrapping하여 Exception이 발생 되도록 하고 반복문 내에서는 최대한 예외처리에 대한 코드를 제거하는 것이 성능 상 유리하다.
참조
- 자바의 정석
'Java > Java' 카테고리의 다른 글
Java UUID (+ IN MySQL ) (0) | 2022.12.07 |
---|---|
예외 처리 방법(Exception Handling) - 복구, 회피, 전환 (0) | 2022.12.07 |
자바가 제공하는 기본 예외들과 자주 사용하는 예외들 (0) | 2022.11.27 |
객체지향 관점에서의 is-a, has-a (0) | 2022.11.25 |
Java - 객체 비교에는 Objects.equals()를 사용하자. (0) | 2022.11.19 |