자바의 예외 처리에 대해 학습하세요.
예외를 처리하는 가장 기본적인 구조는 try-catch-finally 구조를 가진다.
코드가 실행되는 부분으로, 예외를 잡아내기 위한 부분이다. 예외가 발생한다면, catch 블록으로 예외가 발생하지 않는다면 catch 블록을 실행하지 않고 finally 블록으로 이동해 코드를 실행한다.
try {
// 코드 (예외 발생 코드 경우 -> catch 블록)
// 코드 (예외 발생 코드 아닌 경우 -> try 블록 끝난 후 finally 블록)
}
try 블록에서 예외가 발생하면 발생한 코드의 다음 부분은 실행하지 않고 바로 catch 블록으로 넘어와 catch 블록의 예외타입이 catch 블록을 실행한다.
catch (예외타입 변수명) {
// 예외 발생시 실행하는 코드 블록
}
catch 블록은 반드시 한 개만 사용해야하는 것은 아니다. 따라서 아래와 같은 형태를 가질 수도 있다.
try {
// 코드
} catch (예외타입 변수명1) {
// 1과 관련된 예외 시 처리 구문
} catch (예외타입 변수명2) {
// 2와 관련된 예외 시 처리 구문
}
여러 개의 catch 문으로 구성된 형식을 하나의 catch 블록으로 묶는 것 또한 가능하다.
try {
// 코드
} catch (예외타입 | 예외타입 변수명) {
// 코드
}
다중 catch 블록을 사용할 때 주의해야 할 점은 반드시 앞의 예외 객체 타입이 뒤의 예외 객체 타입 보다 작아야한다. 즉, 앞에 나오는 예외 객체 타입이 뒤에 나오는 예외 객체 타입의 서브 클래스인 경우에 가능하다. 앞에 나오는 예외 객체 타입이 상위 클래스라면 뒤의 예외를 하나의 예외 클래스로 처리할 수 있기 때문에 컴파일 에러를 발생한다.
//// 컴파일 에러
try {
// 코드
} catch (Exception e) {
} catch (IOException e) {
}
//// 컴파일 에러
try {
// 코드
} catch (Exeption | IOException e) {
// 코드
}
예외 처리 구문에서 필수적으로 존재해야 하는 부분은 아니다. finally 블록은 try-catch 구문에서 예외가 발생 유무에 상관없이 항상 해당 블록을 실행한다.
finally {
// 반드시 실행하는 코드
}
인위적으로 예외를 발생시킬 때 사용하는 키워드이다. 프로그램 동작 중에 개발자가 원하는 조건을 만족하지 않을 때 더 이상 코드가 진행하지 못하게 예외를 발생시킬 때 사용한다. 대표적으로 라이브러리를 만들 때 사용한다고 한다.
아래의 예제는 N까지의 양수의 합을 구하는 메소드이다. 해당 메소드에서 파라미터 N은 음수가 들어오면 안되기 때문에 예외를 발생시켜서 넘겨주었다.
// N까지의 양수의 합
private static int sumPositiveNum(int N) throws Exception {
if( N < 0)
throw new Exception("N은 양의 정수이어야 합니다");
int sum = 0;
for(int i =1;i<=N;i++){
sum += i;
}
return sum;
}
만약 sumPositiveNum() 메소드의 파라미터로 음수를 넣어준다면 위의 코드에서 정의한 것과 동일하게 아래의 사진처럼 예외를 발생시킬 것이다.
try-cathc 구문을 통한 직접 예외를 처리하는 방법과 달리 throws 키워드는 예외가 발생하는 경우 예외를 발생시킨 메소드의 호출 지점으로 예외를 던져 처리할 수 있다.
해당 예제에서는 BufferedReader 객체를 종료할 때 close()메소드에서 예외가 발생한다. 이 때, try-catch 블록으로 처리할 수도 있지만, 해당 메소드를 호출한 main메소드에 예외를 처리해달라는 의무를 전가해줄 때 throws를 사용한다.
package com.livestudy.ninth;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ExceptionTest {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
br.close();
}
}
try 블록에서 자원 객체를 전달하면 try 블록이 끝난 이후 자동으로 자원을 종료(해제)해주는 역할을 한다.
따라서, finally와 catch 블록에서 종료를 해줄 필요가 없다.
try-with-resources는 AutoClosable 인터페이스의 close() 메소드를 구현해야한다.
public class ExceptionTest implements AutoCloseable {
private static String readLine() throws IOException {
// try-with-resources
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))){
return br.readLine();
}
}
@Override
public void close() throws Exception {
throw new IOException();
}
}
프로그램 오류는 에러와 예외 2 가지로 구분할 수 있다.
에러는 StackOverflow, OutOfMemoryError 등 실행 시 어떠한 원인에 의해 비정상적으로 종료되거나 오작동하는 경우를 말하며 발생하면 복구할 수 없는 심각한 프로그램 오류를 말한다.
예외는 갑작스럽게 발생하더라도 해결할 수 있는 프로그램 오류로서 에러보다 비교적 덜심각한 오류이다. 프로그래머가 예외를 처리하는 코드를 작성함으로써 방지가 가능하다.
에러 클래스와 예외 클래스는 모두 Object 클래스의 자식 클래스인 Throwable 클래스에서 상속받는다.
출처 : https://jiwontip.tistory.com/5모든 예외와 에러 클래스들의 조상이 되는 클래스로서 예외나 에러에 대한 정보를 확인할 수 있는 메소드를 가지고 있다.
Exception 클래스의 하위클래스들은 RuntimeException과 그 외 Exception 클래스의 자식 클래스 2가지로 구분할 수 있다.
그렇다면 RuntimeException 클래스와 그 외 클래스들을 구분하는 기준은 무엇일까?
2가지로 구분하는 기준은 CheckedException인지 UncheckedException인지로 판단한다. CheckedException은 반드시 예외를 처리해야하고, UncheckedException은 예외 처리를 강제하지 않는다.
RuntimeException은 런타임 시에 예외를 발생시키며, 실행 전에는 컴파일 에러를 발생시키지 않는다. 즉, RuntimeException은 UncheckedException이다. 예외처리를 강제하지는 않지만 해당 내용이 런타임 시에 예외를 발생시킬 수 있음을 인지하면 예외를 처리해주는 것이 좋다.
대표적인 RuntimeException으론 NullPointerException, IndexOutOfBoundsException, ArithmeticException 등이 있다.
만약 어떠한 정수를 0으로 나누는 코드가 있다고 가정하자. 0으로 정수를 나누는 경우 ArithmeticException을 발생시키지만 컴파일 단계에서 에러를 발생시키지 않는다.
package com.livestudy.ninth;
public class RuntimeExceptionTest {
public static void main(String[] args) throws IOException {
System.out.println(3/0); //컴파일 에러 발생 X
}
}
하지만, 해당 코드를 실행하면 아래와 같이 런타임 시에 에러를 발생시키는 것을 확인할 수 있다. 즉, ArithmeticException은 UncheckedException임을 확인할 수 있다.
RuntimeException 이외의 클래스들은 모두 CheckedException이다. 실행하기 전 컴파일 단계에서 에러를 발생시킨다. 따라서, 실행하기 전 예외를 반드시 처리해줘야 한다.
대표적인 예로, IOException, SQLException 등이 있다.
만약 BufferedReader 타입의 객체로 입력을 받는 경우 데이터를 모두 읽은 이후에는 해당 객체를 종료해줘야한다.
package com.livestudy.ninth;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class RuntimeExceptionTest {
public static void main(String[] args) {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
br.close(); //컴파일 에러 발생 (IOException)
}
}
이 때, close()메소드를 이용하면 컴파일 에러를 발생시키는 것을 확인할 수 있다. 이는 IOException이 CheckedException이기 때문이다.
기존의 정의된 예외 클래스 외에 사용자가 직접 커스텀해서 예외를 만들어 처리하는 것이 가능하다.
일반적으로 Exception 클래스를 상속받아 사용하며, 필요에 따라 맞는 예외 클래스를 선택해 사용한다.
아래와 같이 Exception 클래스를 상속받아 커스텀 예외 클래스를 작성해 보았다.
커스텀 예외 클래스는 생성자를 통해서 예외 시 에러 메세지를 확인할 수 있다.
커스텀 예외 클래스의 생성자를 확인하면 super(message)라는 메소드를 확인할 수 있다. 이는 위에서 Throwable 클래스의 getMessage()를 통해 메세지를 처리한다.
package com.livestudy.ninth;
public class CustomException extends Exception {
private final int ERR_CODE;
//에러 메세지(커스텀)
public CustomException(String message) {
this(message,200);
}
// 에러 메세지(커스텀), 에러 코드(커스텀)
public CustomException(String message, int ERR_CODE) {
super(message);
this.ERR_CODE = ERR_CODE;
}
// 에러코드 리턴
public int getErrorCode() {
return ERR_CODE;
}
}
커스텀 예외 클래스를 테스트 하기 위해 작성한 코드이다. 간단하게 나이를 체크하는 메소드인 checkAge(int birthYear)를 이용해 탄생년도가 2021년 보다 큰 경우 에러코드 200과 에러메세지를 전달하였고, 1800년도 보다 작은경우 에러메세지와 에러코드 100을 전달하였다.
package com.livestudy.ninth;
public class CustomExceptionApp {
public static void main(String[] args) {
try{
checkAge(2022);
} catch (CustomException e){
e.printStackTrace();
System.out.println("ERR_MSG : "+ e.getMessage());
System.out.println("ERR_CODE : "+ e.getErrorCode());
}
}
private static void checkAge(int birthYear) throws CustomException {
final int YEAR = 2021;
//탄생년도가 2021년 보다 큰 경우
if(YEAR - birthYear < 0)
throw new CustomException("Too Big Birth Year");
//탄생년도가 1800년 보다 작은 경우
if(birthYear < 1800)
throw new CustomException("Maybe Wrong Birth Year",100);
}
}
checkAge의 파라미터로 2022를 전달한다면 탄생년도가 2021년 보다 크기 때문에 예외를 처리하고 커스텀 메세지를 출력하는 것을 확인할 수 있다.
마찬가지로, checkAge의 파라미터로 1750을 전달한다면 탄생년도가 1800년 보다 작기 때문에 예외를 처리하고 커스텀 메세지를 출력하는 것을 확인할 수 있다.
try-with-resources : https://ryan-han.com/post/java/try_with_resources/
throw 문 : https://ehpub.co.kr/java-활용-2-3-프로그램-방식으로-예외를-던지는-throw-문/
예외 처리 계층 구조 : https://jiwontip.tistory.com/5
Custom Exception : https://devbox.tistory.com/entry/Java-예외-만들기