자바에서 예외는 프로그램이 비정상적으로 종료될 상황을 미리 예측하여 처리하는 것이다.
try{
//예외를 처리하길 원하는 실행 코드
}catch(e1){
//e1 예외가 발생할 경우 실행될 코드
}catch(e2){
//e2 예외가 발생할 경우 실행될 코드
}
...
finally{
//예외 발생 여부와 상관없이 무조건 실행될 코드
}
1. try / catch
2. try / finally
3. try / catch / ... / finally
try구문은 다음과 같이 catch와 finally를 옵션으로 선택하여 사용할 수 있다.
JDK 1.7부터는 위의 try/catch/finally 문의 catch 블록을 하나로 합칠 수 있다.
try{
} catch (e1 | e2){
}
단, 나열된 예외 클래스들이 부모-자식 관계에 있다면 오류가 발생한다.
public class ExceptionTest{
public static void main(String[] args){
try{
System.out.println(1 / 0);
}catch(RuntimeException | ArithmeticException e){
System.out.println(e.getMessage());
}
}
}
다음과 같이 ArithmeticException은 RuntimeException을 상속받는 클래스이다. 자식클래스로 잡아낼 수 있는 예외는 부모 클래스로도 잡아낼 수 있기 때문에 컴파일러는 중복된 코드를 제거하기 위해 오류를 발생시킨다.
작성자가 예외처리를 하고 싶은 경우를 throw키워드를 사용해 처리할 수 있다.
public class ExceptionTest{
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
System.out.println("키워드를 입력하세요");
String keyword = scanner.nextLine();
try{
if(keyword.equals("Jieun")){
throw new IllegalArgumentException("적절하지 않은 키워드입니다.");
}
}catch(IllegalArgumentException e){
System.out.println(e.getMessage());
}
}
}
이처럼 "Jieun"이라는 키워드를 입력하지 못하게 하고 싶다면 다음과 같이 throw를 통해 예외를 발생시킬 수 있다.
throws를 사용해 메소드의 예외를 선언할 수 있다.
void method() throws Exception1, Exception2, ... , ExceptionN{
// ...
}
다음과 같이 메소드 선언부에 예외를 선언해두어 해당 메소드를 사용할 때 이 메소드는 어떠한 예외가 필요한지 알려주는 역할을 한다. 따라서 throws자체는 예외처리를 직접하지 않고 메소드를 사용할 때 예외를 처리하도록 책임을 전가하게 된다.
java7부터 나온 것으로 자원 해제를 자동으로 해주는 구문이다.
public static void main(String args[]) throws IOException{
FileInputStream file = null;
try{
file = new FileInputStream("file.txt");
//...
}catch(IOException e){
//error처리
}finally{
if(is!=null) is.close();
}
}
다음과 같이 FIle input을 할 시 finally구문으로 반드시 자원을 닫아주어야 했었다. 하지만 try-with-resources구문을 사용하면 아래와 같이 깔끔하게 작성할 수 있다.
public static void main(string args[]){
try(
FileInputStream file = new FileInputStream("file.txt");
){
//...
}catch(IOException e){
//error처리
}
}
try 괄호 속 자원을 할당 받으면 해당 자원은 블록 수행 이후 자동으로 반환된다.
※단, AutoCloseable 인터페이스를 구현한 클래스만 자동으로 반환된다.

자바에서는 실행 시 발생할 수 있는 오류를 클래스로 정의한다. 위의 그림에서 파란색 부분은 Checked exception, 초록색 부분은 Uncecked exception이다. Checked exception은 컴파일 시점에서 확인될 수 있는 예외처리를 해주지않으면 컴파일에러가 발생한다. Unchecked exception은 컴파일 단계에서 확인되지 않는 예외로 프로그래머가 알아서 처리를 해야하는 영역이다.
프로그램을 실행하다보면 다양한 원인에 의해 프로그램이 비정상적 종료되는 상황이 발생한다.
StackOverflowError : 호출의 깊이가 싶어지거나 재귀가 지속되어 stack overflow가 발생해 생기는 error이다.
OutOfMemoryError : JVM이 할당된 메모리가 부족해 더 이상 객체를 할당 할 수 없을 때 던져지는 error이다. Garbage Collector에 의해 추가적인 메모리가 확보되지 못하는 상황이기도 하다.
Error는 간접적으로 재귀의 사용을 주의하거나 가시적인 loop를 사용하는 등의 간접적인 예방은 가능하겠지만 대부분 개발자가 미리 대처하기가 힘들다. 개발자 입장에서 신경써야하는 것은 Exception처리이다.
exception은 error와 마찬가지로 프로그램을 비정상적으로 종료시키긴하지만 발생할 수 있는 상황을 미리 예측하여 처리할 수 있다.
Exception은 RuntimeException과 그 밖의 exception의 차이와 함께 살펴보자.

모든 예외 클래스는 java.lang.Exception 클래스를 상속받는다. RuntimeException와 그 외의 일반예외는 위에서 다룬 것처럼 Checked exception인지 Uncecked exception인지의 차이가 있다.
Checked exception으로 소스 코드를 컴파일하는 과정에서 예외처리가 필요하다고 판단하는 예외로 없다면 컴파일 오류가 발생한다. 프로그램의 사용자들의 동작에 의해서 발생하는 경우가 많다.
Uncecked exception으로 개발자의 실수로 발생하는 경우로 컴파일과정에서 예외 처리코드를 검사하지 않아 예외 처리를 하지 않아도 컴파일이 가능한 비검사형 예외이다.
Exception과 Error 클래스는 Throwable클래스를 상속받아 작성되어있다. Error와 관련된 클래스는 개발자가 절대 손대서는 안된다. 하지만 Exception이라면 java.lang.Exception을 상속받아 직접 처리할만한 예외를 만들 수 있다.
getMessage()를 통해 예외 메시지를 확인할 수 있다.throw new CustomException("예외가 발생하였습니다.");
...
try{
}catch(CustomException e){
String message = e.getMessage(); // message = "예외가 발생하였습니다."
}
잔고 부족 예외를 커스텀하며 작성하여보자.
public class BalanceInsufficientException extends Exception{
public BalanceInsufficientException(){}
public BalanceInsufficientException(String message){
super(message);
}
}
public class Account{
private long balance;
public Account(){
}
public long getBalance(){
return balance;
}
public void deposit(int money){
balance += money;
}
// throws를 통해 예외를 처리하도록 책임 전가
public void withdraw(int money) throws BalanceInsufficientException{
if(balance < money){
throw new BalanceInsufficientException((money-balance)+"만큼 잔고가 부족함");
}
balance -= money;
}
}
public class AccountExample {
public static void main(String[] args) {
Account account = new Account();
//10000원 입금
account.deposit(10000);
System.out.println("예금액 : "+account.getBalance());
//30000원 출금으로 예외발생
try {
account.withdraw(30000);
}catch(BalanceInsufficientException e) {
String message = e.getMessage();
System.out.println(message);
System.out.println();
e.printStackTrace(); //예외 추적 후 출력
}
}
}

이름으로 예외의 정보전달이 가능하다.
예를들어 NoSuchElementException예외가 발생했다. 하지만 이 예외를 보았을때는 어떤 요소가 없는지 알 수 없다. 하지만 PostNotFoundException을 custom예외로 작성하여 사용하였다면 Post를 찾는 요청을 보냈지만 해당 요소가 없다는 상황을 이름만으로 유추할 수 있다.
같은 예외가 여러 곳에서 발생할 시 유용하다.
이러한 이유가 있지만 기존에 정의되어있는 표준 예외로도 충분히 의미를 전달할 수 있어 커스텀 예외를 작성하는 것이 불필요하다는 의견도 있어 잘 활용하는 것이 중요한 것 같다.
📚 Reference
본 스터디는 2020 백기선님의 자바스터디의 커리큘럼을 참고하여 진행하고 있습니다.