0. 학습할 것
- 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
- 자바가 제공하는 예외 계층 구조
- Exception과 Error의 차이는?
- RuntimeException과 RE가 아닌 것의 차이는?
- 커스텀한 예외 만드는 방법
1. Exception과 Error의 차이
ERROR (에러)
- 오류(Error)은 시스템의 비정상적인 상황이 생겼을 때 발생한다. (JVM 실행에 문제가 생김)
- 시스템 레벨에서 발생하기 때문에 심각한 수준의 오류이다.
- 개발자가 미리 예측하여 처리할 수 없기 때문에, 애플리케이션에서 오류에 대한 처리를 신경쓰지 않아도 된다.
- 프로세스에 영향을 준다.
public class ErrorExample {
public static void main(String[] args) {
recursive(5);
}
public static void recursive(int i){
while (i != 0){
i+=1;
recursive(i);
}
}
}
/* 출력
Exception in thread "main" java.lang.StackOverflowError
at ErrorAndExceptionTest.ErrorExample.recursive(ErrorExample.java:11)
at ErrorAndExceptionTest.ErrorExample.recursive(ErrorExample.java:11)
at ErrorAndExceptionTest.ErrorExample.recursive(ErrorExample.java:11)
...
*/
EXCEPTION (예외)
- 오류(Error)가 시스템 레벨에서 발생한다면 예외(Exception)는 사용자의 잘못된 조작 또는 개발자의 잘못된 코딩으로 인해 발생하는 프로그램 오류이다.
- 예외는 발생 상황을 미리 예측하여 처리할 수 있다.
- 예외는 개발자가 처리할 수 있기 때문에 예외를 구분하고 그에 따른 처리 방법을 명확히 알고 적용하는 것이 중요하다
- Exception에는 Checked Exception 과 Unchecked Exception 이 존재한다.
- 쓰레드에 영향을 준다.
public class ExceptionExample {
public static void main(String[] args) {
int x = 10;
int y = 0;
int z = x / y;
System.out.println(z);
}
}
/* 출력
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ErrorAndExceptionTest.ExceptionExample.main(ExceptionExample.java:7)
*/
Error VS Exception
Error | Exception | |
타입 | 확인되지 않은 유형으로 분류 | Checked 와 Unchecked 로 분류 |
패키지 | java.lang.error | java.lang.Exception |
복구 | 불가 | 가능 |
발생 | 시스템 레벨에서 발생 | 런타임 & 컴파일 시간에 발생 |
예 | OutOfMemoryError, IOError | NullPointerExceoption, SqlException |
2. RuntimeException과 RE가 아닌 것의 차이 (Unchecked VS Checked)
CheckedException | UncheckedException |
반드시 예외 처리를 해야한다. | 명시적인 처리를 강제하지 않음 |
컴파일단계에서 확인 | 실행단계에서 확인 (컴파일 단계에서 확인되지 않음) |
Exception의 하위 클래스 중 Runtime Exception을 제외한 모든 예외 IOException FileNotFoundException ClassNotFoundException DataFormatException ... |
Runtime Exception 하위 예외 RuntimeException ㄴ ArithmeticException ㄴ NullPointerException |
외부적인 영향으로 발생할 수 있는 예외 | 실행중 발생되는 예외. |
Checked Exception이 발생할 가능성이 있는 메소드라면 반드시 try- catch로 감싸거나 throw로 던져서 처리해야 한다.
즉, 꼭 처리해야 하는 Exception이다.
반면에, Unchecked Exception은 명시적인 예외처리를 하지 않아도 된다.
이 예외는 피할 수 있지만 개발자가 부주의해서 발생하는 경우가 대부분이고, 미리 예측하지 못했던 상황에서 발생하는 예외가 아니기 때문에 굳이 로직으로 처리를 할 필요가 없도록 만들어져 있다
인용: https://www.notion.so/3565a9689f714638af34125cbb8abbe8
3. 자바가 제공하는 예외 계층 구조
예외 클래스
모든 예외 클래스는 Throwable 클래스를 상속 받고 있다. Throwable은 최상위 클래스 Object의 자식 클래스이다.
4. 자바에서 예외 처리 방법
자바의 예외 처리는 일반적으로 3가지 방법을 통해 처리한다.
- 예외가 발생하면 다른 작업 흐름으로 유도하는 예외 복구
- 처리하지 않고 호출한 쪽으로 던져버리는 예외처리 회피
- 호출한 쪽으로 던질 때 명확한 의미를 전달하기 위해 다른 예외로 전환하여 던지는 예외 전환
4-1. 예외 복구
- 예외가 발생해도 어플리케이션이 정상 동작한다.
- 예외 발생시 이를 예측해 다른 로직으로 흐름을 유도하거나 예외가 발생하지 않는 상황으로 복구를 시도하는 로직을 추가한다.
private void mayThrowExceptionLogic() {
int maxTry = 20;
while(maxTry --> 0) {
try {
// ???Exception 이 Throw 될 수 가능성이 있는 로직
// 성공 시 return, 해당 메소드 종료
return ;
} catch(???Exception e) {
// Error 로그 출력
// 실패 로직 존재 시 원상 복구
// 일정 시간 동안 대기
} finally {
// 작업에 사용한 Resource 반환 및 정리
}
}
// 최대 횟수 실패시 예외 Throw
throw new MaxTryFailedException();
}
위의 코드는 어떤 요인에 따라 실패할 때 복구하는 로직이다.
maxTry만큼 시도하고 maxTry가 초과된 경우 maxTryFailedException으로 전환하여 Throw한다.
재시도를 통해 정상적인 흐름을 타게 하거나, 예외가 발생하면 이를 예측하여 다른 흐름으로 유도시키도록 구현하면
예외가 발생하였을 때 정상적으로 작업을 종료할 수 있을 것이다.
4-2. 예외처리 회피
- 예외 발생시 throws를 통해 호출된 부분으로 해당 예외를 던지고 예외처리를 회피
private void mayThrowExceptionLogic() throws ???Exception {
// 비즈니스 로직
}
위의 코드는 신중하게 사용해야한다.
호출한 쪽에서 다시 예외를 받아서 처리하거나 해당 메소드에서 이 예외를 던지는 것이 최선의 방법이라고 생각할 때만 사용해야 한다.
4-3. 예외 전환
- 예외 발생시 예외를 잡아서 다른 예외로 던지는 것이다.
try {
// Exception 발생 가능 로직
}
catch(???Exception e) {
throw 새로운Exception();
}
호출한 쪽에서 예외를 받아서 처리할 때 조금더 명확하게 인지할 수 있도록 돕기 위한 방법이다.
또한 Checked Exception이 발생했을 경우 이를 Unchecked Exception으로 전환하여 호출한 메서드에서 예외처리를 일일이 선언하지 않아도 되도록 처리할 수 도 있다.
try - catch - finally
- 예외처리를 사용하기 위해서 try-catch구문을 이용한다.
- 예외가 발생한 메소드 내에서 직접 처리하고자 할 때 작성되는 코드이다.
- try-catch 블럭내에 포함된 문장이 하나여도 {}를 생략할 수 없다.
- catch내에 또다른 try-catch블럭을 사용할 수 있다. (단, 상위catch문과 동일한 변수 사용불가)
- finally는 try블럭이 끝날때 ( exception 발생여부, return, continue, break등과 상관없이) 반드시 수행된다.
- finally블럭은 반드시 작성할 필요는 없다.
try {
// 예외가 발생할 가능성이 있는 코드
} catch (Exception1 e1) {
// Exception1이 발생했을 때, 이를 처리하기 위한 코드
} catch (Exception2 e2) {
/// Exception2가 발생했을 때, 이를 처리하기 위한 코드
} catch (ExceptionN eN) {
// ExceptionN이 발생했을 때, 이를 처리하기 위한 코드
} finally {
// 항상 실행되는 코드
}
정리하면 다음과 같다.
- try
- try에 속한 블록은 예외가 발생할 가능성이 있는 범위를 지정하는 블록이다.
- try블록은 최소한 한개 이상의 catch 블록이 있어야 하며, catch블록은 try블록 다음에 위치한다.
- catch
- catch블록은 매개변수의 예외 객체가 발생했을 때 참조하는 변수명으로 반드시 java.lang.Throwable 클래스의 하위 클래스 타입으로 선언되어야 한다.
- catch 문은 여러개의 블록으로 구성할 수 있다.
- catch블록 작성시 부모 클래스가 자식 클래스보다 먼저 catch에서 쓰이게 되면 컴파일에러가 발생하게 된다. (ex. Exception 클래스가 ArrayIndexOutOfBoundsException 보다 먼저 선언되면 에러 발생)
- finally
- try블록에서 예외가 발생 유무에 상관없이 무조건 실행된다.
multi-catch
여러개의 예외를 묶어서 같은 catch 블럭에 묶을 수 있다.
그러나 연결되어 있는 예외 클래스가 부모/자식 관계에 있다면, 컴파일 에러가 발생함.
try{
.. do something
}catch(IllegalStateException | IllegalArugmentException e){
// catch !!
}
throws
- 예외가 발생한 메서드를 호출한 곳으로 예외 객체를 넘기는 방법이다.
- 예외를 선언함으로서 메서드를 사용할때 발생할수 있는 에러를 명시적으로 알수있다.
- 이때 예외는 해당메서드를 호출하는 어느곳에서든 한번은 try-catch로 처리해주어야 한다.
- 예외 객체를 넘기기 위해서 throws 라는 키워드를 사용한다.
public class ExceptionTest {
static void handlingException() throws Exception { throw new Exception(); }
public static void main(String[] args) {
try {
handlingException();
} catch (Exception e) {
System.out.println("main() 메소드에서 예외 처리됨");
}
}
}
/* 출력
main() 메소드에서 예외 처리됨
*/
// 출처: http://www.tcpschool.com/java/java_exception_throw
throw
- 예외를 인위적으로 발생시킬 때 사용한다.
- 개발자가 의도한 케이스에 대해 임의로 예외를 발생시킬 때 사용되며, 특정 예외의 케이스에서 더욱 구체적인 예외로 처리하고 자 할 때에도 사용된다.
public void exceptionThrow() throws Exception{
Exception e = new Exception("고의로 발생시킨 예외");
throw e;
// throw new Exception("위의 코드와 동일");
ex
public class Person {
private String userName;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void throwExam() {
Person person = jpaRepository.findById("userName");
if (person == null) {
throw new NullPointerException();
} else {
System.out.println(person.getName());
}
}
}
// 출처:https://leegicheol.github.io/whiteship-live-study/whiteship-live-study-09-exception-handling/
Person클래스에서 db를 조회하여 name을 찾고자 한다.
이때 name의 값이 있을 수도 없을 수도 있다. 만약 값이 없는 경우 if문안에 throw 키워드를 사용해 임의로 예외를 발생시킬 수 있다.
try - with - resources
Java7에 도입된 기능으로 try블록에서 사용할 resource를 선언하는 방법으로 사용된다.
finally block 대신 resource를 자동으로 반환하기 위해 사용한다.
여기서 말하는 리소스는 java.lang.AutoClosable이나 java.io.Closable을 구현한 오브젝트를 말하며 반드시 close되야하는 리소스 이다.
AutoClosable 인터페이스
public interface AutoCloseable{
void close() throws Exception;
}
ex)
// Java 7 이후 try-with-resources
static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
// Java 7 이전
static String readFirstLineFromFileWithFinallyBlock(String path)
throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
if (br != null) br.close();
}
}
// 출처: https://velog.io/@youngerjesus/%EC%9E%90%EB%B0%94-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC#the-try-with-resources-statement
Chained Exception
Chained Exceptions은 예외에 대한 응답으로 다른 예외를 발생시키는 걸 말한다.
한 예외로 인해 다른 예외가 발생함을 아는게 때로는 유용한 정보가 될 수 있다.
예를들어 0으로 나눴을 때 발생하는 ArithmeticException이 일어났다고 가정하다.
이때 근본적인 원인은 I/O error때문에 0으로 나눈 상황이다.
하지만 실제로 예외의 원인을 알 수없고 ArithmeticException 예외가 발생했음을 알 수 있다.
이런 문제를 chained Exception으로 해결할 수 있다.
public class ExceptionHandling
{
public static void main(String[] args)
{
try
{
// Creating an exception
NumberFormatException ex =
new NumberFormatException("Exception");
// Setting a cause of the exception
ex.initCause(new NullPointerException(
"This is actual cause of the exception"));
// Throwing an exception with cause.
throw ex;
}
catch(NumberFormatException ex)
{
// displaying the exception
System.out.println(ex);
// Getting the actual cause of the exception
System.out.println(ex.getCause());
}
}
}
출력
java.lang.NumberFormatException: Exception
java.lang.NullPointerException: This is actual cause of the exception
원인 예외를 등록해서 예외를 발생시키면 여러 가지 예외를 하나의 큰 분류의 예외로 묶어서 다루는 것이 가능하다.
서로 연결되는 예외는 상속 관계가 아니어도 상관없다.
Constructors Of Throwable class Which support chained exceptions in java :
- Throwable(Throwable cause) :- Where cause is the exception that causes the current exception.
- Throwable(String msg, Throwable cause) :- Where msg is the exception message and cause is the exception that causes the current exception.
initCause() method를 통해 현재 발생한 예외에 원인 에외를 넣을 수 있다.
getCause() method를 통해 실제 예외의 원인을 볼 수 있다.
initCause()는 Throwable 클래스에 정의되어 있기 때문에 모든 예외 클래스에서 사용할 수 있다.
5. 커스텀 예외
커스텀 예외를 만들 때 참고해야 할 4가지 Best Practice
원문은 여기를 참고함.
1. Always Provide a Benefit
자바 표준 예외들에는 포함되어 있는 다양한 장점을 가지는 기능들이 있다.
이미 JDK가 제공하고 있는 방대한 수의 예외들과 비교했을 때 만들고자 하는 커스텀 예외는 어떠한 장점도 제공하지 못한다면? 커스텀 예외를 만드는 이유를 다시 생각해볼 필요가 있다.
어떠한 장점을 제공할 수 없는 예외를 만드는 것 보다 오히려 UnsupportedOperationException 이나, IllegalArugmentException 과 같은 표준 예외 중 하나를 사용하는 것이 낫다.
2. Follow the Naming Convention
JDK가 제공하는 예외 클래스들을 보면 클래스의 이름이 모두 "Exception" 으로 끝나는 것을 알 수 있다.
이러한 네이밍 규칙은 자바 생태계 전체에 사용되는 규칙이다.
즉, 만들고자 하는 커스텀 예외 클래스도 이 네이밍 규칙을 따르는 것이 좋다.
3. Provide javadoc Comments for Your Exception Class
많은 커스텀 예외들이 어떠한 javadoc 코멘트도 없이 만들어진 경우들이 있다.
API의 모든 클래스, 멤버변수, 생성자들에 대해서는 문서화 하는 것이 일반적인 Best Practices 이다.
문서화되지 않은 API들은 사용하기 매우 어렵다.
예외 클래스들은 API에 크게 드러나지 않는 부분일 수 있으나 사실상 그렇지 않다.
클라이언트와 직접 관련된 메소드들 중 하나가 에외를 던지면 그 예외는 바로 예외의 일부가 된다.
그렇다는 것은 잘 만들어진 JavaDoc와 문서화가 필요하다는 뜻이다.
JavaDoc은 예외가 발생할 수도 있는 상황과 예외의 일반적인 의미를 기술한다.
목적은 다른 개발자들이 API를 이해하고 일반적인 에러상황들을 피하도록 돕는 것이다.
/**
* The MyBusinessException wraps all checked standard Java exception and enriches them with a custom error code.
* You can use this code to retrieve localized error messages and to link to our online documentation.
*
* @author TJanssen
*/
public class MyBusinessException extends Exception { ... }
4. Provide a Constructor That Sets the Cause
커스텀 예외를 던지기 전에 표준 예외를 Catch 하는 케이스가 많다.
이 사실을 관과하지 말자.
보통 캐치된 예외에는 제품에 발생한 오류를 분석하는데 필요한 중요한 정보가 포함되어 있다.
아래 예제를 보면 NumberFormatException은 에러에 대한 상세 정보를 제공한다.
MyBusinessException의 cause 처럼 cause 정보를 설정하지 않으면 중요한 정보를 잃을 것이다.
public void wrapException(String input) throws MyBusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new MyBusinessException("A message that describes the error.", e, ErrorCode.INVALID_PORT_CONFIGURATION);
}
}
커스텀 예외 만들기
- Checked Exception을 구현할 때는 Exception 을 확장함
- Unchecked Exception을 구현할 때는 RuntimeException 을 확장함
- 커스텀 예외를 만들 때 위의 4가지 원칙을 따르는 것이 좋음
종종 코드를 작성하다보면 직접 예외를 만들고 싶은 경우가 생긴다.
예를 들어 어떤 서비스 홈페이지에서 회원가입에 자연수만 입력받게 하고 싶다.
예외처리를 하고 싶지만 built-in exception으로 마땅하게 할 수 있는 방법이 없다.
이런 경우 커스텀 예외를 만들어 예외를 던질 수 있다.
먼저 예외를 사용자 정의하기 위해서 Exception을 상속받아야 한다.
이때 기본 생성자를 만들어 출력할 메세지를 받아 출력할 수 있으며 메세지 없이 어떤 예외인지 볼 수 있다.
(코드와 설명은 여기를 참고)
public class SignUp {
public static void main(String[] args) throws AgeException {
SignUpClass signUp = new SignUpClass();
System.out.println("회원가입을 시작합니다~");
signUp.setAge(-5);
}
}
class SignUpClass {
public void setAge(int age) throws AgeException {
if (age < 0) {
throw new AgeException("나이에 음수값을 입력할 수 없습니다");
}
}
}
class AgeException extends Exception {
AgeException() {}
AgeException(String message) {
super(message);
}
}
출력
기본 생성자로 예외 처리한 경우(메세지 전달X)
회원가입을 시작합니다~
Exception in thread "main" customExceptionTest.AgeException
at customExceptionTest.SignUpClass.setAge(SignUp.java:17)
at customExceptionTest.SignUp.main(SignUp.java:9)
메시지를 받는 생성자로 예외 처리를 한 경우이다.
회원가입을 시작합니다~
Exception in thread "main" customExceptionTest.AgeException: 나이에 음수값을 입력할 수 없습니다
at customExceptionTest.SignUpClass.setAge(SignUp.java:17)
at customExceptionTest.SignUp.main(SignUp.java:9)
Exception 이름이 AgeException인 것과 내가 작성한 예외 메시지를 볼 수 있다.
예외 클래스의 생성자에서 super(message); 를 사용한다.
따라 올라가보면 Throwable 클래스의 생성자를 볼 수 있다. Throwable 클래스에서 예외 메시지 처리를 하고 있다.
인용 및 참고
https://wisdom-and-record.tistory.com/46
https://catch-me-java.tistory.com/46
https://www.notion.so/3565a9689f714638af34125cbb8abbe8
https://leegicheol.github.io/whiteship-live-study/whiteship-live-study-09-exception-handling/
https://i-am-clap.tistory.com/12
https://velog.io/@youngerjesus/%EC%9E%90%EB%B0%94-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC
https://www.notion.so/cce3fc21976f4400aa4ed8d3fb26497b
https://www.geeksforgeeks.org/errors-v-s-exceptions-in-java/?ref=gcse
https://www.nextree.co.kr/p3239/
https://www.geeksforgeeks.org/chained-exceptions-java/?ref=gcse
'Language > Java-Weekly-study' 카테고리의 다른 글
[Java] W11: 자바 Enum (0) | 2022.08.13 |
---|---|
[Java] W10: 자바 멀티쓰레드 프로그래밍 (0) | 2022.07.31 |
[Java] W08: 자바 인터페이스 (0) | 2022.07.12 |
[Java] W07: 자바 패키지 (0) | 2022.07.10 |
[Java] W06: 자바 상속 (0) | 2022.06.26 |