자바 기본 복습 9. 예외 처리
9주차 과제: 예외 처리
📌 목표
자바의 예외 처리에 대해 학습하세요.
📌 학습할 것
- Exception과 Error의 차이는?
- 자바가 제공하는 예외 계층 구조
- RuntimeException과 RE가 아닌 것의 차이는?
- 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
- 커스텀한 예외 만드는 방법
📜 시작에 앞서
- 백기선 님의 라이브 스터디(2020년 11월부터 2021년 3월까지) 커리큘럼을 따라 진행한 학습입니다
- 뒤늦게 알게 되어 스터디 참여는 못했지만 남아있는 스터디 깃허브 주소와 유튜브 영상을 참고했습니다
📑 Exception과 Error의 차이는?
프로그램 에러
- 컴파일 에러: 컴파일 시 발생하는 에러로 실행 전 컴파일 단계에서 쉽게 발견
- 런타임 에러: 실행시 발생하는 에러로 프로그램의 정상적인 흐름을 방해
- 논리적 에러: 의도와 다르게 동작하지만 비정상적 종료, 충돌이 없는 에러
런타임 에러
- 자바는 런타임에 발생할 수 있는 프로그램 에러를 예외(Exception)와 에러(Error)로 구분
예외
- 어플리케이션에서 처리할 수 있는 문제
- 발생시 예외 정보 담은 해당 예외 클래스의 인스턴스 생성
- 발생시 생성된 인스턴스는 자신을 처리할 Exception Handler(Catch Code Block)를 찾는다
- Exception Handler를 찾지 못한다면 종료
NullPointerException
,IllegalArgumentException
등
에러
- 어플리케이션에서 처리할 수 없는 심각한 문제
- 내부에서 처리할 수 없으므로 잡을(catch) 필요 없다(상속하거나 잡지 말자)
StackOverflowError
,OutOfMemoryError
등
📑 자바가 제공하는 예외 계층 구조
Throwable
클래스는 자바의 모든 예외와 에러의 부모 클래스- 이때 예외는 크게
RuntimeException
과RE가 아닌 예외
로 구분 - java8 docs: Throwable
📑 RuntimeException과 RE가 아닌 것의 차이는?
- 예외는 크게
RuntimeException
과RE가 아닌 예외
로 구분- RuntimeException: 프로그래머의 실수로 발생하는 예외
- RE 아닌 예외: 클라이언트의 실수와 같이 외적인 요인으로 발생하는 예외
Checked Exception과 Unchecked Exception
Checked Exception | Unchecked Exception | |
---|---|---|
계층 | RE가 아닌 예외 | RuntimeException |
처리 여부 | 반드시 예외 처리 해야함(컴파일 단계에서 확인) | 예외 처리 하지 않아도됨(강제 X) |
예시 | IOException, SQLException, ClassNotFoundException ... | NullPointerException, IllegalArgumentException, IndexOutOfBoundException ... |
-
Checked Exception
-
RE가 아닌 예외의 경우 클라이언트가 자신이 발생시킨 예외로부터 회복할 수 있도록 예외 처리 강제한다
-
코드로 부가적인 작업이 가능한 경우
-
-
Unchecked Exception
-
RuntimeException의 경우 프로그래머의 실수로 발생하며 이 모든 것에 대해 예외처리를 강제하는 것은 코드의 가독성과 생산성을 떨어뜨리기 때문에 예외처리를 강제하지 않는다
-
NullPointerException
이 발생할 수 있는 모든 곳에 예외처리를 한다면 코드 작성은 물론 읽기도 어려울 것이다
-
📑 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
- ❗ 예외 처리는 비용이 들기 때문에 정상적인 흐름에 사용해서는 안 된다
- baeldung: Performance Effects of Exceptions in Java 예외에 관한 여러 상황에 대한 성능 측정 결과에 대한 글
throw
- 고의로 예외를 발생시킬 때 사용
Exceoption e = new Exception("message"); // 발생시킬 예외 인스턴스 생성
throw e; //예외 발생시킨다
throw new Exception("message");
throws
- 예외를 메서드에 선언해 메서드의 클라이언트가 처리하도록 할 떄 사용
void method() throws Exception1, Exception2 {
//구현부
}
- 메서드를 호출하는 쪽에서 다시
throws
를 하든try-catch
를 하든 예외를 처리해야 한다
try-catch
- 예외를 직접 처리하고 싶을 때 사용
try{
//예외가 발생할 수 있는 코드
} catch(Exceoption1 e1) {
//Exceoption1 발생시 실행
} catch(Exceoption2 e2) {
//Exceoption2 발생시 실행 //Multiple catch Blocks try-catch
} catch(Exception3a | Exception3b e3) {
//Exception3a or Exception3b 발생시 실행 //Union catch Blocks
}
- 예외 발생시, 해당 예외 클래스의 인스턴스가 생성
- 인스턴스 통해 각
catch
블럭에 차례로instanceof
연산자로 검사 - 검사 결과
true
인catch
블럭 코드 수행 후 전체try-catch
문을 빠져나간다- 예외 코드 수행 후 전체
try-catch
문을 빠져나오기 때문에, 계층구조상 더 좁은 범위의 예외를 먼저 검사해야하며 그렇지 않을 시 컴파일 에러 - 검사 결과 만족하는
catch
블럭 없다면 예외처리 불가
- 예외 코드 수행 후 전체
- Multiple catch Blocks
try
블럭의 코드가 둘 이상의 예외를 던질 수 있으며 각 예외에 대해 다른 처리를 하고 싶을 때 사용
- Union catch Blocks
- 오류 처리 방식이 동일할 때
|
로 구분해 여러 예외 같은catch
블럭에서 처리 가능 - 자바 7부터 가능
- 오류 처리 방식이 동일할 때
finally
- 예외 발생 여부에 관계없이 실행하고자 하는 코드 있을 때 사용
try{
//예외가 발생할 수 있는 코드
} catch(Exception e) {
//Exception 발생시 실행
} finally {
//예외 발생 여부 관계없이 실행
}
Anti-Patterns
finally
블럭에서return
을 쓰지 말자,try
블럭에서 발생한 예외가 드롭된다 (finally
통해 정상 종료될 수 있다)finally
블럭에서throw
를 쓰지 말자,finailly
블럭에서 던지는 예외가catch
블럭에서 던지는 예외를 삼킨다 (스택 추적 내역에서catch
블럭이 던진 예외에 대한 정보가 사라진다)- 관련 간단한 예제는 baeldung: Exception Handling in Java에서 확인
try-with-resources
AutoCloseable
인터페이스를 구현한 리소스에 대해close()
명시적 호출 없이 간편히 리소스 해제 가능
//java 7
try(Resource1 r1 = new Resource1(); Resource2 r2 = new Resource2()) {
//...
} catch(Exceoption e) {
//예외 발생시
}
//java 9
Resource1 r1 = new Resource1()
try(r1) {
//...
} catch(Exceoption e) {
//예외 발생시
}
- 리소스 여러 개 생성 시
;
으로 구분 - 자바 9부터는 외부에서 선언한 변수 그대로 사용 가능
백기선님 라이브 중 예시
JAVA PUZZLERS: Traps, Pitfalls, and Corner Cases
public class JavaPuzzler40 {
// 단순히 src 쪽에 있는 파일을 dest 쪽에 복사하는 코드
static void copy(String src, String dest) throws IOException {
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(src);
out = new FileOutputStream(dest);
byte[] buf = new byte[1024];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
if (in != null)
in.close();
if (out != null)
out.close();
}
}
}
/*
정답은 finally { if (in != null) in.close(); if (out != null) out.close(); }
에서 앞에 있는 close()에 문제가 생기면 뒤에 자원이 닫히지 않는 것 그래서 처리하려면 각각을 try catch로
*/
public class JavaPuzzler40 {
static void copy(String src, String dest) throws IOException {
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(src);
out = new FileOutputStream(dest);
byte[] buf = new byte[1024];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
if (in != null)
try {
in.close();
} catch (IOException e) {
// 딱히 할 게 없고 로깅이나...
}
if (out != null)
try {
out.close();
} catch (IOException e) {
// 딱히 할 게 없고 로깅이나...
}
}
}
}
/*
위의 코드도 문제가 있다
in.close(); 호출할 때 IOException(IOException은 Checked Exception)이 아니라
런타임 예외가 발생한다면
마찬가지로 아래쪽의 if(out != null) 로 가지 않는다
그래서 어떻게 까지 해야 하냐면
*/
public class JavaPuzzler40 {
static void copy(String src, String dest) throws IOException {
InputStream in = null;
OutputStream out = null;
try { // 요부분
out = new FileOutputStream(dest);
try {
in = new FileInputStream(src);
byte[] buf = new byte[1024];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
if (in != null)
try {
in.close();
} catch (IOException e) {
// 딱히 할 게 없고 로깅이나...
}
}
} finally { //요부분
if (out != null)
try {
out.close();
} catch (IOException e) {
// 딱히 할 게 없고 로깅이나...
}
}
}
}
/*
try-with-resources 사용시
*/
public class JavaPuzzler40 {
static void copy(String src, String dest) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dest)) {
byte[] buf = new byte[1024];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
}
}
Exception Rethrowing
- 메서드에서 발생한 예외를
catch
문에서 되던지는 방법
예외를 메서드 내부와 외부 양쪽에서 처리하는 예시
package week9;
public class Rethrowing {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
System.out.println("main catch블럭에서 예외 처리");
e.printStackTrace();
}
}
static void method1() throws Exception {
try{
throw new Exception("method1에서 발생");
} catch(Exception e) {
System.out.println("method1 catch블럭에서 예외 처리");
throw e;
}
}
}
/*
실행결과
method1 catch블럭에서 예외 처리
main catch블럭에서 예외 처리
java.lang.Exception: method1에서 발생
at week9.Rethrowing.method1(Rethrowing.java:16)
at week9.Rethrowing.main(Rethrowing.java:7)
*/
예외 번역 시 예외 되던지기 예시
try {
...// 저수준 추상화를 이용한다
} catch(LowerLevelException e) {
//추상화 수준에 맞게 번역
throw new HigherLevelException(...);
}
출저: 이펙티브 자바 3/E: 아이템73 추상화 수준에 맞는 예외를 던지라
- 예외 번역: 상위 계층에서 저수준 예외를 잡아 자신의 추상화 수준에 맞는 예외로 바꿔 던지는 것
- 이후 ChainedException에서 예외 번역 예시로 이어진다
ChainedException
- 문제의 원인인 저수준 예외를 고수준 예외에 실어 되던지는 방법
예외 번역 시 연쇄 예외 사용 예시
//예외 연쇄
try {
...// 저수준 추상화를 이용한다
} catch(LowerLevelException cause) {
//저수준 예외를 고수준 예외에 실어 보낸다
throw new HigherLevelException(cause);
}
//예외 연쇄용 생성자
class HigherLevelException extends Exception {
HigherLevelException(Throwable cause){
super(cause);
}
}
출저: 이펙티브 자바 3/E: 아이템73 추상화 수준에 맞는 예외를 던지라
- 대부분의 표준 예외는 예외 연쇄용 생성자가 있다
- 없다면
initCause(Throwable cause)
메서드를 통해 등록 getCause()
메서드를 통해 예외 원인 반환
📑 커스텀한 예외 만드는 방법
- JDK가 제공하는 예외 클래스 외에 직접 예외 클래스를 정의해 사용할 수 있다
Exception
상속 -> checked 예외RuntimeException
상속 -> unchecked 예외
재사용하기 좋은 표준 예외
- JDK가 제공하는 표준 예외에 속하는데도 직접 예외 클래스를 만들어 사용하면 다른 사람의 입장에서 혼란스러울 것
예외 | 사용 상황 |
---|---|
IllegalArgumentException | 허용하지 않는 값이 인수로 건네졌을 때(null은 따로 NullPointerException으로 처리) |
IllegalStateException | 개체가 메서드를 수행하기에 적절하지 않은 상태일 때 |
NullPointerException | null을 허용하지 않는 메서드에 null을 건넸을 때 |
IndexOutOfBoundsException | 인덱스가 범위를 넘어섰을 때 |
ConcurrentModificationException | 허용하지 않는 동시 수정이 발견됐을 때 |
UnsupportedOperationException | 호출한 메서드를 지원하지 않을 때 |
출저: 이펙티브 자바 3/E: 아이템72 표준 예외를 사용하라
- 자바독에 표준 예외와 사용 상황이 잘 정리되있다
관련 읽을 거리
- dzone: Implementing Custom Exceptions in Java by Thorben Janssen 커스텀 예외를 위한 BP를 정리한 글
- sthwin님 블로그: Implementing Custom Exceptions in Java 위 글 번역
📑📌📜✏️
Author And Source
이 문제에 관하여(자바 기본 복습 9. 예외 처리), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@butterf12/자바-기본-복습-9.-예외-처리저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)