관리 메뉴

개발그래머

[자바스터디 9주차] 예외 처리 본문

Java

[자바스터디 9주차] 예외 처리

임요환 2023. 6. 22. 11:17

목표

  • 자바의 예외 처리에 대해 학습하세요

학습할 것 (필수)

  • 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
  • 자바가 제공하는 예외 계층 구조
  • Exception과 Error의 차이는?
  • RuntimeException과 RE가 아닌 것의 차이는?
  • 커스텀한 예외 만드는 방법

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

자바의 예외처리

  • 프로그램 실행 중에 발생할 수 있는 예상치 못한 상황을 나타내는 객체
  • 이러한 예외상황을 적절히 처리하고 프로그램의 안정성을 높이기 위해 중요함
try {
    // 예외가 발생할 수 있는 코드
} catch (ExceptionType1 e1) {
    // 예외 처리 코드
} catch (ExceptionType2 e2) {
    // 예외 처리 코드
} finally {
    // 선택적인 finally 블록 (필요한 경우 사용)
}
  • try 문 = 예외가 발생할 수 있는 코드를 작성
  • catch 문 = 해당 예외를 처리하는 코드를 작성
  • finally 문 = 선택적으로 작성하나 주로 자원의 반납에 사용
public void throwException() throws Exception {
    throw new Exception("error");
}
  • throw
    • 예외를 명시적으로 발생시킬 때 사용
    • 예외가 발생하는 상황을 감지하고 예외 객체를 생성하여 해당 예외를 처리할 코드 블록으로 전달
    • throw 키워드가 try-catch 문 내부에서 사용되면 예외 발생 시 catch문으로 제어를 이동시킴
  • throws
    • 메서드 선언부에 사용되며 메서드가 호출될 때 발생할 수 있는 예외를 명시적으로 선언함
    • 메서드에서 발생할 수 있는 예외를 호출자에게 알려줌으로써 예외 처리를 해당 메서드를 호출한 곳에서 처리할 수 있도록 함
    • 메서드 시그니처 뒤에 예외 클래스들을 선언하고 여러 개의 예외를 선언할 경우 쉼표로 구분
    • throws로 선언된 예외는 직접 try-catch 문을 사용하여 처리하거나 상위 호출자에게 예외를 전달할 수 있음

catch 문 여러개일 시 순서의 중요성

try {
    // do something
} catch (RuntimeException e){

} catch (IllegalArgumentException e) { // 무의미한 코드가 되어버림

}
  • 여러 줄 catch 할 때 주의해야 할 사항
    • 예외 계층 구조의 상속관계 때문에 순서가 중요함
    • IllegalArgumentException은 RuntimeException을 상속받은 관계이므로 IllegalArgumentException에 도달하기 전에 이미 RuntimeException에서 예외가 잡혀버림
try {
    // do something
} catch (IllegalArgumentException e){

} catch (RuntimeException e) {

}
  • 좁은 범위의 예외를 위에서 catch 해야 함
  • try 구문에서 호출하는 메서드가 어떤 예외를 던지느냐에 따라 catch 하는 예외와 순서가 달라짐

multi-catch

try {
    // do something
} catch (IllegalArgumentException e){

} catch (IllegalStateException e) {

}

try {
    // do something
    throw new IllegalArgumentException();
} catch (IllegalArgumentException | IllegalStateException e){
    System.out.println(e.getClass()); // class java.lang.IllegalArgumentException
}
  • 자바 1.7 이상부터 두 개의 예외의 처리가 같다면 하나로 합칠 수 있음
  • catch 한 e에 들어오는 클래스는 try 구문에서 어떤 예외를 던지느냐에 따라 결정됨
try {
    // do something
} catch (RuntimeException | IllegalStateException e){ // 순서 바꿔도 컴파일 에러

}
  • multi-catch 하는 예외가 상속관계면 multi-catch 불가능

예외 처리 비용

  • 예외 처리는 StackTrace에 내용이 계속 담겨 메모리를 사용하기 때문에 비용이 비쌈

try-with-resources

FileReader reader = null;
try {
    reader = new FileReader("file.txt");
    // 파일 읽기 작업 수행
    // 예외가 발생할 수 있는 코드
} catch (IOException e) {
    // 예외 처리 코드
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException e) {
            // 자원 해제 실패에 대한 예외 처리
        }
    }
}

try (FileReader reader = new FileReader("file.txt")) {
    // 파일 읽기 작업 수행
    System.out.println("do");
    // 예외가 발생할 수 있는 코드
} catch (IOException e) {
    // 예외 처리 코드
} finally {
    // finally 구문도 추가적으로 사용 가능 -> try with resources는 finally 블록에서 close 로직을 생성하는게 아니라 catch 블록에서 close하는 로직을 생성해주고 또 맨 밑에 close하는 로직을 추가로 생성해줌
}

// try-with-resources를 컴파일한 class파일
try{
  FileReader reader = new FileReader("file.txt");
  try {
      System.out.println("do");
  } catch (Throwable var5) {
      try {
          reader.close();
      } catch (Throwable var4) {
          var5.addSuppressed(var4);
      }

      throw var5;
  }

  reader.close();
} finally {
    //finally 추가시 try로 한번더 감싸져서 finally가 추가됨
}
  • 예외처리 메커니즘을 활용하여 자원관리를 간편하게 해주는 기능(finally 블록을 줄여줌)
  • try 안에 생성하는 자원은 AutoCloseable 인터페이스를 구현한 클래스이어야만 함
  • AutoCloseable의 close메서드를 명시적으로 호출하므로 자동으로 자원을 정리함
  • 코드의 가독성과 안정성을 향상하고, 자원 누수를 방지함
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) {
                in.close(); // in.close하는 부분도 try catch로 잡아주어야 다음 out.close하는 부분으로 내려가 자원을 종료할 수 있음, 만약 에러가 던져지면 out의 자원은 종료되지 못하고 자원 누수가 발생됨
            }

            if(out != null) {
                out.close();
            }
        }
    }
}

finally 안에 return문(안티패턴)

public class FinallyReturnTest {
    public static void main(String[] args) {
        System.out.println(new FinallyReturnTest().returnTest()); // 무조건 finally에서 리턴된 값이 나옴
    }

    public String returnTest() {
        try {
            System.out.println("try");
//            throw new IllegalStateException("error"); // 에러발생시에는 catch 값까지 print한 후 finally 값이 리턴됨
            return "try";
        } catch (Exception e) {
            System.out.println("catch");
            return "catch";
        } finally {
            System.out.println("finally");
//            throw new IllegalStateException("error"); // 이런식으로도 사용 x
            return "finally"; // 이런식으로도 사용 x
        }
    }
}
  • try 문 return = finally 블록을 거쳐 정상 실행
  • catch 문 return = finally 블록을 거쳐 정상 실행
  • finally 문 return = try 블록 안에서 발생한 예외 무시되고 finally 거쳐 정상 종료(예외를 알 수 없음)
  • finally에서는 코드의 흐름을 제어하는 로직은 작성하지 않는 게 좋음
  • finally에서는 자원 반납 혹은 로깅정도만 하는 형식으로 사용하는 것을 권장함

자바가 제공하는 예외 계층 구조

  1. java.lang.Throwable
  • 모든 예외 클래스의 최상위 클래스
  • 예외와 에러의 기본 클래스 = Exception과 Error 클래스의 부모 클래스
  1. java.lang.Exception
  • 일반적인 예외 상황을 나타내는 클래스
  • 다양한 예외 하위 클래스를 가짐
  • Exception 클래스를 상속받은 예외들은 예외처리가 필요한 Checked Exception임
  1. java.lang.RuntimeException
  • 프로그램 오류가 아닌 주로 개발자의 잘못된 사용으로 발생하는 예외를 나타냄
  • RuntimeException을 상속받은 예외들은 Unchecked Exception으로 예외처리가 선택적임
  • Exception 클래스의 자식 클래스임
  1. java.lang.Error
  • 시스템 레벨에서 발생하는 심각한 예외 상황을 나타내는 클래스
  • Error 클래스를 상속받은 예외들은 프로그램에서 복구가 불가능하며 주로 가상 머신에서 발생하는 문제를 나타냄

Exception과 Error의 차이는?

Exception Error
프로그램 실행 중에 예상할 수 있는 예외 처리 시스템 레벨에서 발생하는 심각한 예외 상황을 나타냄, 가상머신 또는 런타임 환경에서 발생하는 문제
Exception 클래스를 상속받은 예외는 예외처리가 필요한 Checked Exception이므로 반드시 예외처리 코드를 작성해야함 Error를 상속받은 예외들은 프로그램에서 복구가 불가능함
잘못된 입력, 파일을 찾을수 없음, 네트워크 연결 오류 등과 같이 예상할 수 있는 예외 상황에 대응하기 위해 사용 예외처리 코드를 작성하여 예외를 처리하는 것은 권장되지 않으며 시스템 레벨에서 처리되야함
RuntimeException, IOException, SQLException OutOfMemoryError, StackOverflowError, NoClassDefFoundError, AssertionError

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

  • RuntimeException(UncheckedException)
    • Exception 클래스의 하위클래스로 주로 개발자의 잘못된 사용으로 발생하는 예외
    • 예외 처리가 선택적이므로 컴파일러가 예외처리 코드를 강제하지 않음
    • 예외를 코드로 복구하기 힘든 경우
    • 컴파일러가 확인할 수 없는 예외
    • 프로그램의 코드 논리나 인수 유효성 검사와 관련된 문제를 나타내며 개발자의 코드 수정으로 예방 가능한 예외 상황들임
  • CheckedExcpetion
    • 예외처리가 필수적이며 컴파일러가 예외처리 코드를 요구함
    • 예외가 났을 때 코드로 컨트롤이 가능할 경우(재시도, 값 수정 등)
    • 컴파일러가 확인할 수 있는 예외
    • 예외 처리 코드 필요
    • 외부 리소스와의 상호작용(파일, 네트워크, 데이터베이스 등)이나 예외적인 상황에 대응하기 위한 예외 상황
    • 예외처리 코드를 통해 예외 상황에 대한 안정성과 오류 복구를 제공함

자바가 제공하는 RuntimeException 기본 예외

예외 설명
NullPointerException null 참조에 대한 연산이 수행될 때 발생
IllegalArgumentException 잘못된 인수가 메서드에 전달될 때 발생
IndexOutOfBoundsException 배열 또는 컬렉션의 인덱스가 범위를 벗어날 때 발생
ArithmeticException 산술 연산 중에 발생하는 예외를 처리하기 위한 기본 클래스
ClassCastException 잘못된 형 변환을 시도할 때 발생
UnsupportedOperationException 지원되지 않는 작업을 수행하려고 할 때 발생
IllegalStateException 객체의 상태가 메서드의 호출에 적합하지 않을 때 발생
NumberFormatException 숫자로 변환할 수 없는 문자열을 숫자로 변환하려고 할 때 발생

커스텀한 예외 만드는 방법

public class CustomException extends RuntimeException {// or Exception 상속
    public CustomException(String message, Throwable cause) {
        super(message, cause);
    }
}

try {
    // do something
} catch (Exception e) {
    throw new CustomException("message", e); // root cause(= e) 보내기
}
  1. 예외 클래스 작성
  • 커스텀한 예외 클래스를 작성하기 위한 새로운 클래스 생성
  • Exception 또는 RuntimeException을 상속받음
  • 예외 클래스의 이름은 보통 Exception으로 끝나는 것이 관례임
  1. 생성자 작성
  • 예외 클래스에는 일반적으로 기본 생성자와 메시지를 받는 생성자를 구현
  • 메시지를 받는 생성자는 예외에 대한 추가 정보를 전달할 수 있도록 해야 함
  1. 추가적인 메서드 및 속성 작성
  • 필요에 따라 예외 클래스에 추가적인 메서드와 속성을 작성할 수 있음
  • 커스텀한 예외에 특정한 동작이나 데이터를 추가하고자 할 때 유용함
  1. 예외 발생
  • 예외를 발생시키기 위해 throw 키워드를 사용하여 해당 예외 객체를 생성하고 던짐
  • 예외 상황이 발생했을 때 예외 객체를 생성하여 예외 처리 코드로 전달

커스텀 예외를 만들 때 참고해야 할 사항

  1. Always Provide a Benefit
    자바의 표준 예외들은 다양한 장점을 가지는 기능들이 있으므로 최대한 활용하고 어떠한 장점도 가지지 못하는 커스텀 예외는 만들지 않는 것이 좋음
  2. Follow the Naming Convention
    자바의 예외 클래스들은 모두 Exception으로 끝나므로 커스텀 예외를 만들 시 규칙을 따르는 것이 좋음
  3. Provide javadoc Comments for Your Exception Class
    기본적으로 API의 모든 클래스, 멤버변수, 생성자들에 대해 문서화하는 것이 좋음, JavaDoc과 문서화를 하는 게 다른 개발자들이 목적을 이해하기 좋음
  4. Provide a Constructor That Sets the Cause
    생성자에 Catch 한 예외를 넘겨줄 수 있게 만들어라, root cause(근본적인 에러 원인)를 꼭 보내라