자바 기본 복습 9. 예외 처리

38339 단어 JavaJava

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클래스는 자바의 모든 예외와 에러의 부모 클래스
  • 이때 예외는 크게 RuntimeExceptionRE가 아닌 예외로 구분
  • java8 docs: Throwable

📑 RuntimeException과 RE가 아닌 것의 차이는?

  • 예외는 크게 RuntimeExceptionRE가 아닌 예외로 구분
    • RuntimeException: 프로그래머의 실수로 발생하는 예외
    • RE 아닌 예외: 클라이언트의 실수와 같이 외적인 요인으로 발생하는 예외

Checked Exception과 Unchecked Exception

Checked ExceptionUnchecked Exception
계층RE가 아닌 예외RuntimeException
처리 여부반드시 예외 처리 해야함(컴파일 단계에서 확인)예외 처리 하지 않아도됨(강제 X)
예시IOException, SQLException, ClassNotFoundException ...NullPointerException, IllegalArgumentException, IndexOutOfBoundException ...
  • Checked Exception

    • RE가 아닌 예외의 경우 클라이언트가 자신이 발생시킨 예외로부터 회복할 수 있도록 예외 처리 강제한다

    • 코드로 부가적인 작업이 가능한 경우

  • Unchecked Exception

    • RuntimeException의 경우 프로그래머의 실수로 발생하며 이 모든 것에 대해 예외처리를 강제하는 것은 코드의 가독성과 생산성을 떨어뜨리기 때문에 예외처리를 강제하지 않는다

    • NullPointerException이 발생할 수 있는 모든 곳에 예외처리를 한다면 코드 작성은 물론 읽기도 어려울 것이다


📑 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)


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 연산자로 검사
  • 검사 결과 truecatch블럭 코드 수행 후 전체 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블럭이 던진 예외에 대한 정보가 사라진다)

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개체가 메서드를 수행하기에 적절하지 않은 상태일 때
NullPointerExceptionnull을 허용하지 않는 메서드에 null을 건넸을 때
IndexOutOfBoundsException인덱스가 범위를 넘어섰을 때
ConcurrentModificationException허용하지 않는 동시 수정이 발견됐을 때
UnsupportedOperationException호출한 메서드를 지원하지 않을 때

출저: 이펙티브 자바 3/E: 아이템72 표준 예외를 사용하라

  • 자바독에 표준 예외와 사용 상황이 잘 정리되있다

관련 읽을 거리


📑📌📜✏️

좋은 웹페이지 즐겨찾기