Java/라이브스터디

예외 처리

목표

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

학습할 것 (필수)

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

 

자바에서의 예외처리

자바에서의 예외처리는 일반적으로 try-catch-finally 블록을 이용하여 처리합니다.

try{
    // 예외가 일어날 가능성이 있어서 검사해야하는 코드 
}catch(IOException e){
    // 특정 예외의 발생 시 처리해당하는 예외를 처리하는 블록
}catch(RuntimeException e){
    // 특정 예외의 발생 시 처리해당하는 예외를 처리하는 블록
}finally{
    // 예외 발생의 유무와 상관없이 마지막에 실행되는 블록
}
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
    String str = br.readLine();
} catch (IOException e) {
    e.printStackTrace();
}

 

 

try

예외가 발생할 가능성이 있는 코드를 try블록 내에 기입함으로써 해당 코드에서의 예외 발생유무를 체크할 수 있습니다.

만일 예외가 발생하면 예외가 발생한 이후의 코드는 실행되지 않습니다.

 

catch

try{

}catch (IOException | IndexOutOfBoundsException e) {
    e.printStackTrace();
}
// Java SE 7 이후.

try{

} catch(IndexOutOfBoundsException e){
	e.printStackTrace();
} catch(IOException e){
	e.printStackTrace();
}
// 기존

try블록 내에 있는 코드에서 예외가 발생했을 시, 예외의 종류에 해당하는 catch 블록으로 이동하여 예외를 처리합니다.

자바의 Exception은 최상의 Throwable 클래스로부터 상속관계를 가지고 있기 때문에 해당하는 예외보다 상위클래스의 예외를 걸더라도 해당 예외에 잡히게됩니다.

 

자바 SE 7버전 이후로 하나의 catch 블록에 하나 이상의 예외타입을 걸 수 있습니다.

 

 

try{
    System.out.println(10/0);
}catch (Exception e){

} /*catch (ArithmeticException e){ // 해당 블록에 빨간 줄이 그임.
    System.out.println("ArithmeticException : " + e.getMessage());
} */

 

여러개의 catch블록을 걸었을 경우, 맨 위에 있는 catch 블록부터 아래로 검사하기 때문에 위쪽 catch 일 수록 아래 블록보다 낮은 계층의 클래스를 둬야합니다.

 

 

finally

try{
    System.out.println(10/0);
}catch (Exception e) {
    System.out.println(e.getClass() + " : " + e.getMessage());
}finally {
    System.out.println("Finally!");
}

== 출력 결과 ==

class java.lang.ArithmeticException : / by zero
Finally!

try 블록에 있는 코드의 예외발생 유무에 상관없이 항상 실행되어야하는 코드를 작성할 수 있는 블록입니다.

예외의 발생 유무에 상관없이 try-catch 작업이 모두 끝나면 마지막에 항상 실행됩니다.

 

try {
    fr = new FileReader("/파일이름");
}catch (FileNotFoundException e){
    e.printStackTrace();
}finally{
    if(fr != null){
        try {
            fr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }else{
        System.out.println("파일이 열려있지 않습니다.");
    }
}

=== 출력 결과 ===
java.io.FileNotFoundException: \파일이름 (지정된 파일을 찾을 수 없습니다)
	at java.base/java.io.FileInputStream.open0(Native Method)
	at java.base/java.io.FileInputStream.open(FileInputStream.java:211)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:153)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:108)
	at java.base/java.io.FileReader.<init>(FileReader.java:60)
	at SUBMAIN.main(SUBMAIN.java:9)
파일이 열려있지 않습니다.

위 예시와 예외의 발생유무에 상관없이 항상 수행해야 하는 작업을 적어놓는 경우가 많습니다.

 

 

try-with-resource

BufferedReader와 같이 사용후에 자원을 반납해야하는 경우 finally 에서 자원을 반환하던 코드 대신 사용할 수 있습니다.
finally를 사용한 자원관리보다 가독성면에서 뛰어납니다.

 

try(BufferedReader br = new BufferedReader(new FileReader("path"))){
    br.readLine();
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
// 바이트 코드
try {
    BufferedReader var1 = new BufferedReader(new FileReader("path"));

    try {
        var1.readLine();
    } catch (Throwable var5) {
        try {
            var1.close();
        } catch (Throwable var4) {
            var5.addSuppressed(var4);
        }

        throw var5;
    }

    var1.close();
} catch (FileNotFoundException var6) {
    var6.printStackTrace();
} catch (IOException var7) {
    var7.printStackTrace();
}

바이트코드를 확인해보면 try의 괄호 안에 있던 코드가 전체 try블록의 최상단에서 실행되고, try 블록내의 코드는 새로운 try-catch 블록에 감싸서 예외를 처리하는 모습입니다.

 

finally를 사용하지 않아도 자원이 반납되기 때문에 전체 블록의 가독성을 높일 수 있고 finally 블록을 자원관리 이외의 용도로 사용할 수 있습니다.

 

try의 괄호안 코드의 경우 ;으로 구분하여 복수의 자원을 적을 수 있으며 [java.lang.AutoCloseable]을 구현하는 모든 객체를 자원으로 사용할 수 있습니다.

 

 

throw

// 사용 방법
throw new [Exception type 생성자];
// 예제
try(BufferedReader br = new BufferedReader(new InputStreamReader(System.in))){
    int size = Integer.parseInt(br.readLine());
    if(size == 0){
        throw new EmptyStackException();
    }
} catch (IOException | EmptyStackException e) {
    System.out.println("발생한 예외 : " + e.getClass());
    e.printStackTrace();
} finally{
    System.out.println("예외 처리 종료");
}

== 0 입력 시 출력 결과 == 

0
발생한 예외 : class java.util.EmptyStackException
예외 처리 종료
java.util.EmptyStackException
	at SUBMAIN.main(SUBMAIN.java:10)

예외를 던지는, 즉 예외를 발생시키는 키워드입니다.


개발자가 코드 작성간에 자신이 특정하는 예외를 발생시키고 싶을 때 사용되는 키워드입니다.

시스템상 예외가 발생하지 않는 코드라도 개발자가 원하는 순간에 예외를 발생시킬 수 있습니다.

 

try-catch 블록으로 싸여져 있는 경우, 발생 시킨 예외 타입과 동일한 catch문으로 코드의 흐름을 조정하게됩니다.

 

 

throws

public static void main(String[] args) {
    try(BufferedReader br = new BufferedReader(new InputStreamReader(System.in))){
        int size = Integer.parseInt(br.readLine());
        method(size);
    } catch (IOException | EmptyStackException e) {
        System.out.println("발생한 예외 : " + e.getClass());
        e.printStackTrace();
    } finally{
        System.out.println("예외 처리 종료");
    }
}

public static void method(int size) throws EmptyStackException{
    if(size == 0) throw new EmptyStackException();
}

발생한 예외를 외부로 던지는 키워드입니다.

여기서 말하는 외부는 자신을 호출한 클래스 또는 JVM을 의미합니다.

 

메소드 선언시에 사용되며, 자신을 호출한 클래스나 메소드가 존재한다면 해당 개체에게 예외의 처리를 떠넘깁니다.
만약 main 메소드와 같이 개발자가 건들 수 있는 상위 개체가 존재하지 않는다면 JVM에게 처리를 맡깁니다.

 

, 를 이용하여 여러개의 예외타입을 throws할 수 있습니다.

 

 

자바의 예외계층

 

출처 : https://www.manishsanger.com/java-exception-hierarchy/

자바의 예외계층은 최상위 Throwable 클래스를 기점으로 Error와 Exception으로 나뉘며, Exception 안에서 RuntimeException과 기타 Exception으로 나뉩니다.

  • Throwable
    • Error
    • Exception
      • RuntimeException
      • Other Exception

관련된 문서는 https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/lang/Throwable.html 에서 확인할 수 있습니다.

 

 

Exception 과 Error

Exception

개발자가 코드작성간에 발생유무를 확인하고, 처리할 수 있는 오류를 의미합니다.
try-catch-finally 와 thorws 등을 사용하여 처리할 수 있습니다.

 

Error

개발자가 코드작성간에 파악할 수 없는 오류를 의미합니다.
OutOfMemoryError 또는 StackOverFlowError 등이 존재합니다.

개발자가 오류를 파악하더라도 코드작성으로 해결할 수 없는 치명적인 오류입니다.

 

 

RuntimeException과 기타 Exception

Exception은 RunTimeException과 기타 Excpeption으로 나뉩니다.

Checked Exception

개발자가 반드시 예외처리를 해줘야하는 예외를 의미합니다.
컴파일 단계에서 예외처리가 가능합니다.
예외 처리를 하지않으면 빨간줄이 그입니다.

 

Unchecked Exception

개발자가 예외처리를 명시하지 않아도 되는 예외를 의미합니다.

컴파일 단계에서 확인이 불가능하며 RuntimeException와 Error가 해당됩니다.

대부분 개발자의 실수로 인하여 발생합니다.

 

 

커스텀 예외

// 커스텀 예외 작성방법
public class MyCustomException extends Exception{
    public MyCustomException(String msg){
        super(msg);
    }
}

public static void main(String[] args) {
    try{
        throw new MyCustomException("custom");
    }catch (MyCustomException e){
        System.out.println(e.getClass() + " : " + e.getMessage());
    }
}

=== 출력 결과 ===

class MyCustomException : custom

자바플랫폼에서 제공하지 않거나 개발자의 판단에의해 발생시켜야하는 예외가 존재한다면 직접 예외를 생성할 수 있습니다.
예외의 종류에 따라 Exception, RuntimeException, Throwable 등의 상위 클래스들을 상속하여 만들 수 있습니다.

 

커스텀 예외를 작성하기 전에 원하는 작업을 수행하는 예외가 이미 존재하는지 확인해 볼 필요가 있습니다.

 

 

예외 처리시에 사용할 수 있는 메소드

자바의 모든 예외들은 Throwable 클래스를 상속하고 있기 때문에, 해당 클래스의 메소드들을 사용할 수 있습니다.

 

타입 메소드 설명
Throwable getCause() 예외의 원인을 반환하거나 알 수 없는 경우 null을 반환합니다.
String getMessage() 해당 예외에 대한 세부 내용을 출력합니다.
void PrintStackTrace() 해당 예외가 발생하기까지의 모든 경로를 역추적하여 출력합니다.

 

 

참고 사이트 및 자료