본문 바로가기

Java

[Java] 예외처리에 대한 공부

예외(Exception)란 

개발자가 구현한 로직에서 발새한 실수나 사용자의 영향에 의해 발생한다. 오류와 달리 미리 예측이 가능하여 상황에 맞게 예외처리를 해야 한다. 

 

오류(Error)란 

시스템이 종료되어야 할 수준의 상황과 같이 수습할 수 없는 심각한 문제를 의미한다. 미리 예측이 불가능하여 방지할 수 없다.

 

예외 계층

체크 예외

애플링케이션 로직에서 사용할 수 있는 실질적인 최상위 예외이다. Exception과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다. RuntimeExceptiond을 제외한 모든 예외는 체크예외이다.

 

언체크 예외(런타임 예외)

컴파일러가 체크하지 않는 언체크 예외이다. RuntimeException과 하위 언체크 예외를 모두 런타임 예외라고 한다.

 

예외 처리 란? 

예기치 못한 예외의 발생에 대비하여 작성한 코드이다.

예외처리는 예외가 발생하면 잡아서 처리하거나, 밖으로 던지는 2가지 행동 중 하나를 해야 한다.

 

예외 처리

위 그림처럼 예외가 발생하고 중간(service구간)에서 예외를 처리하면 다음 로직에서는 정상적인 흐름을 반환한다. 예외를 catch로 잡으면 하위 예외들도 모두 잡을 수 있다.

 

try-catch

try-catch문을 사용하면 예외를 catch로 잡으면서 하위 예외들도 모두 잡을 수 있다. try-catch는 다음과 같은 형식을 가진다.

try{
    int a=Integer.parseInt(br.readLine());
    int b=Integer.parseInt(br.readLine());
    int sum=0;
    
    sum=a/b;
    
    System.out.println(sum);
} catch (ArithmeticException e) { // 나누기 0을 시도할때 나오는 예외
	System.out.println("0으로 나눌 수 없습니다.");
}

위 코드에서 보면 a와 b의 값을 입력받는다. 정상적인 흐름을 가지는 로직이라면 위 try-catch문에서는 sum를 출력한다.

하지만, 만약 b에서 입력하는 값이 0일 경우, sum=a/b에서 ArithmeticException가 발생한다. 이를 감지하고 더 이상 로직을 멈추고 catch영역에서  ArithmeticException를 처리한다. 그리고 catch영역의 로직인 System.out.println("0으로 나눌 수 없습니다.");을 실행한다.

 

위에 글을 읽다 보면 try-catch문은 실행을 하다가 예외가 발생하는 catch문으로 강제로 실행된다. 하지만 코드를 작성하다 보면 예외가 발생해도 반드시 실행해야 하는 코드가 있다.

 

try-catch-finally

이를 위한 게 try-catch-finally문이다. try-catch- finally는 다음과 같은 형식을 가진다.

try{
    int a=Integer.parseInt(br.readLine());
    int b=Integer.parseInt(br.readLine());
    int sum=0;
    
    sum=a/b;
    
    System.out.println(sum);
} catch (ArithmeticException e) { // 나누기 0을 시도할때 나오는 예외
	System.out.println("0으로 나눌 수 없습니다.");
} finally{
	System.out.println("프로그램이 종료되었습니다.");
}

위 코드에서 예외가 발생해도 finally문 실행된다. 즉 finally문인 System.out.println("프로그램이 종료되었습니다.");코드는 예외가 발생하든 안 하든 무조건 실행된다는 것이다.

 

예외 던지기

위 그림처럼 예외가 발생하고 계속해서 던지면 예외 로그를 출력하면서 시스템이 종료된다.

예외를 throws로 던지면 하위 예외들도 모두 던질 수 있다.

public static void main(String[] args){
    try{
    	int se=sumException();
        System.out.println(se);
  	  
        int s=sum();
        System.out.println(s);
    }catch(ArithmeticException e){
    	System.out.println("0으로 나눌 수 없습니다.");
    }catch(Exception e){
    	System.out.println("예외가 발생 했습니다.");
    }
}

public static int sumException() throws Exception { // 여기서 나올 수 있는 모든 예외를 던진다.
    int a=Integer.parseInt(br.readLine());
    int b=Integer.parseInt(br.readLine());
    int sum=0;
    
    return sum=a/b;
}

public static int sum() throws ArithmeticException { //0으로 나눌때 나오는 예외를 던진다.
    int a=Integer.parseInt(br.readLine());
    int b=Integer.parseInt(br.readLine());
    int sum=0;
    
    return sum=a/b;
}

위 코드를 보면 sum()에서는 0으로 나누기를 시도할 때만 예외를 던진다. 하지만 sumException에서는 로직에서 나올 수 있는 모든 예외를 던진다. 그렇다면 모든 예외를 던지는 예외의 최상위 코드인 Exception로 던지면 모든 예외를 던질 수 있는데 코드를 작성할 때 Exception으로만 던지면 되는 거 아닌가?라는 생각을 할 수 있다.

위 코드에 main() 메서드를 보면 try-catch문이 나오는데 ArithmeticException일 때와 Exception일 때로 나누어진다. 실제로 0으로 나눌 때 catch문에서  ArithmeticException이 실행되어 "0으로 나눌 수 없습니다."라는 문장을 출력하여 어떤 부분에서 문제가 생겼는지 알 수 있다. 만약  0으로 나눌 때  catch문에서 ArithmeticException이 없다면 Exception이 실행될 텐데 그렇게 된다면 "예외가 발생했습니다."라는 문장이 출력되는데 이는 어디서 예외가 발생했는지 알기 어렵다. 또한 Exception을 던지기 때문에 로직에서 체크 예외가 발생해도 문법적으로 맞아서 넘어가서 체크예외를 인식하지 못하는 경우가 많다. 그래서 단순히 Exception이 아닌 구체적인 예외를 던져야 한다.  

 

체크 예외는 어떻게 처리하는지 

예외를 잡아서 처리하지 않으면 throws 선언해야 한다. 

public class Main{
    public static void main(String[] args) throws IOException{
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        
        int x=Integer.parseInt(br.readLine());
        int y=Integer.parseInt(br.readLine());
        
        int sum=x+y;
        
        System.out.println(sum);
    }
}

위 코드에서 처럼 예외가 발생해도 catch 하지 않으면 throws를 통해서 예외를 처리해주어야 한다.  throws를 하지 않고 코드를 실행하면  must be caught or declared to be thrown으로 꼭 잡거나 던져야 한다고 오류가 나온다. 

 

런타임 예외(언체크 예외)는 어떻게 처리하는지

예외를 잡아서 처리하지 않아도 throws 키워드를 생략할 수 있다.

public static void test(){
    int x=Integer.parseInt(br.readLine());
    int y=Integer.parseInt(br.readLine());
    
    int sum=x+y;
}

위 코드에서 실제로 ArithmeticException 이외에도 여러 가지 예외가 발생할 수 있지만 이는 런타임 예외로 예외를 던지는 키워드인 throws를 생략이 가능하다. 실제로 써도 문제가 되지는 않는다. 

 

 

 

 

출처

https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EC%A4%91%EA%B8%89-1/dashboard

https://toneyparky.tistory.com/40

https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%ACException-%EB%AC%B8%EB%B2%95-%EC%9D%91%EC%9A%A9-%EC%A0%95%EB%A6%AC