Web/Java

[Java] 예외

 

 

예외처리

 

프로그램 오류는 발생시점에 따라 3가지로 나눌 수 있습니다.

  1. 컴파일 에러 : 컴파일 시에 발생하는 에러
  2. 런타임 에러 : 실행 시에 발생하는 에러
  3. 논리적 에러 : 실행은 되지만, 의도와 다르게 동작하는 것

에러가 발생하면, 프로그램의 비정상적인 종료를 막을 길이 없지만, 예외는 발생하더라도 프로그래머가 이에 대한 적절한 코드를 미리 작성해 놓음으로써 프로그램의 비정상적인 종료를 막을 수 있습니다.

 

 

 

 

 

 

Exception과 Error의 차이

  1. 에러는 일단 발생하면 복구할 수 없는 심각한 오류(메모리부족, 스택오버플로우 등)이고 예외는 발생하더라도 수습할 수 있는 비교적 덜 심각한 것
  2. 에러가 발생하면 프로그램은 비정상적인 종료를 하지만, 예외는 발생하더라도 프로그래머가 이에 대한 적절한 코드를 미리 작성해 놓음으로써 프로그램의 비정상적인 종료를 막을 수 있음

 

 

 

 

자바가 제공하는 예외 계층 구조

 

자바에서는 실행 시 발생할 수 있는 오류(Exception과 Error)를 클래스로 정의했습니다.

 

모든 클래스의 조상은 Object클래스이므로 Exception과 Error클래스 역시 Object클래스의 자손들입니다.

 

 

 

 

 

 

 

위 그림처럼 모든 "예외"의 최고 조상은 Exception클래스입니다.

그리고 Exception와 Error 클래스의 부모는 Throwable입니다.

 

 

 

RuntimeException과 RE가 아닌 것의 차이는?

 

* RuntimeException (UnChecked Exception)

RuntimeException은 프로그램 실행 도중에 발생합니다.

RuntimeException클래스들은 주로 프로그래머가 코드작성시의 실수가 실행될 때 발생할 수 있는 예외들로 프로그래밍 요소들과 관계가 깊습니다. (ex. 배열의 범위를 벗어남, null인 참조변수의 멤버호출, 클래스간의 형변환, 정수를 0으로 나누는 경우 등)

 

그래서 RE는 예외처리를 해주지 않더라도 컴파일시에 컴파일에러가 발생하지 않습니다.

 

 

* RE가 아닌 Exception (Checked Exception)

주로 외부의 영향으로 컴파일시에 검사가 가능한 것들입니다.

(ex. 존재하지 않는 파일의 이름 입력, 클래스이름 잘못입력, 잘못된 입력데이터 형식 등)

 

 

 

 

예외 처리 방법

 

프로그램의 실행도중에 발생하는 "에러"는 어쩔 수 없습니다.

 

하지만 "예외"는 실행중인 프로그램의 갑작스런 비정상 종료를 막고, 정상적인 실행상태를 유지할 수 있도록 프로그래머가 이에 대한 처리를 미리 해주어야 합니다.

 

 

 

try catch

 

구조

try {

} catch (Exception1 e) {

} catch (Exception2 e) {

} catch (Exception3 e) {

}

 

하나의 try 블럭 다음에는 여러 종류의 예외를 처리할 수 있도록 하나 이상의 catch블럭이 올 수 있습니다.

이 중 발생한 예외의 종류와 일치하는 단 한 개의 catch 블럭만 수행됩니다.

만약 발생한 예외의 종류와 일치하는 catch 블럭이 없으면 예외는 처리되지 않습니다.

 

 

주의

참조변수 이름 e는 catch 블럭들마다 같은 이름으로 쓰일 수 있습니다.

catch 블럭은 서로 다른 블럭이기 때문입니다.

 

하지만 catch 블럭안에서 또 try-catch문을 사용할때는 당연히 변수이름이 e가 아닌 다른 이름을 써야합니다.

 

 

 

예시

 

아래코드는 100을 0~9사이의 임의의 정수로 나눈 결과 출력을 10번 반복하는 코드입니다.

try-catch문이 없으므로 ArithmeticException이 발생하고 프로그램이 종료될 것입니다.

 

package javastudy.ch9;

public class ExceptionEx1 {
    public static void main(String[] args) {

        int number = 100;
        int result = 0;

        for (int i = 0; i < 10; i++) {
            result = number / (int) (Math.random() * 10);
            System.out.println(result);
        }
    }
}

 

 

ArithmeticException이 발생하는 부분을 try-catch문으로 바꿔보겠습니다.

package javastudy.ch9;

public class ExceptionEx1 {
    public static void main(String[] args) {

        int number = 100;
        int result = 0;

        for (int i = 0; i < 10; i++) {
            try {
                result = number / (int) (Math.random() * 10);
                System.out.println(result);
            } catch (ArithmeticException e) {
                System.out.println("0");
                
            }
        }
    }
}

 

ArithmeticException이 발생한 부분에서 0이 출력되는 것을 볼 수 있습니다.

 

 

 

try-catch문에서의 흐름

- try블럭 내에서 예외가 발생한 경우
1. 발생한 예외와 일치하는 catch블럭이 있는지 확인한다.
2. 일치하는 catch블럭을 찾게 되면, 그 catch블럭 내의 문장들을 수행하고 전체 try-catch문을 빠져나가서 그 다음 문장을 계속해서 수행한다.
만일 일치하는 catch블럭을 찾지 못하면, 예외는 처리되지 못한다.

- try 블럭 내에서 예외가 발생하지 않은 경우
1. catch 블럭을 거치지 않고 전체 try-catch문을 빠져나가서 수행을 계속한다.

 

 

catch블럭을 찾는 원리

 

  1. 예외가 발생하면, 발생한 예외에 해당하는 클래스의 인스턴스가 만들어진다.
  2. 예외가 발생한 문장이 try블럭에 포함되어 있다면, 이 예외를 처리할 수 있는 catch블럭 찾기를 시작한다.
  3. 첫 번째 catch 블럭부터 차례로 내려가면서 catch블럭의 괄호()내에 선언된 참조변수의 종류와 생성된 예외클래스의 인스턴스에 instanceof연산자를 이용해서 검사한다.

예외발생 - 예외클래스인스턴스 생성 - catch블럭의 참조변수와 instanceof 연산자로 검사

 

 

 

 

finally

finally 블럭은 try-catch문의 끝에 선택적으로 덧붙여 사용이 가능한 블럭입니다.

(try-catch-finally)

 

예외에 관계없이 실행되어야할 코드를 포함시킬 목적으로 사용됩니다.

 

구조

try{
    // 예외가 발생할 가능성이 있는 문장
} catch (Exception1 e) {
    // 예외 처리 문장
} finally {
    // 예외 발생 여부에 관계없이 항상 수행되어야하는 문장
    // finally블럭은 항상 try-catch 문의 맨 마지막에 위치
}

 

예시

public class FinallyTest {
    public static void main(String[] args) {
        try{
            startInstall();
            copyFiles();
        } catch (Exception1 e) {
            e.printStackTrace();
        } finally {
            deleteTempFiles();
        }
    }
}

 

 

 

throw

키워드 throw를 사용해서 프로그래머가 고의로 예외를 발생시킬 수 있습니다.

 

방법

1. 먼저 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든다.
Exception e = new Exception("고의로 발생시켰음");

2. 키워드 throw를 이용해서 예외를 발생시킨다.
throw e;

 

예시

package javastudy.ch9;

public class ExceptionEx2 {
    public static void main(String[] args) {
        try{
            throw new Exception("고의로 발생시켰음. ");
        } catch (Exception e) {
            System.out.println("에러 메시지 : " + e.getMessage());
            e.printStackTrace();
        }
        System.out.println();
    }
}

 

printStackTrace()와 getMessage()

printStackTrace() : 예외 발생 당시의 호출스택에 있었던 메서드의 정보와 예외 메세지를 화면에 출력한다.

getMessage() : 발생한 예외 클래스의 인스턴스에 저장된 메세지를 얻을 수 있다.

 

 

 

throws

try-catch문 외에도 예외를 메서드에 선언하는 방법이 있습니다.

 

throws는 현재 메서드내에서 예외처리가 되지 않으면 자신을 호출한 메서드로 예외를 던집니다.

void method() throws Exception1, Exception2, ... ExceptionN {
    // 메서드의 내용
}

 

예외를 선언하면, 이 예외뿐만 아니라 그 자손타입의 예외까지도 발생할 수 있습니다.

(Exception은 모든 예외의 최고조상이므로 모든 예외가 발생할 수 있습니다.)

 

 

throws를 사용하는 이유는 메서드의 선언부에 예외를 선언함으로써 메서드를 사용하려는 사람이 메서드의 선언부를 보았을 때, 이 메서드를 사용하기 위해서는 어떠한 예외들이 처리되어져야 하는지 쉽게 알 수 있기 때문입니다.

 

 

주의

예외처리가 되지 않으면 자신을 호출한 메서드로 예외를 던집니다.

그래서 어느 한쪽에서는 예외처리를 꼭 해주어야 합니다. 그렇지 않으면 프로그램이 중단됩니다.

 

아래코드에서 method1()에서 예외를 던지고 main에서드에서 try catch로 예외처리 하는 모습을 볼 수 있습니다.

package javastudy.ch9;

public class ExceptionEx3 {
    public static void main(String[] args) {
        try {
            method1();
        } catch (Exception e) {
            System.out.println("main메서드에서 예외가 처리되었습니다.");
            e.printStackTrace();
        }
    }

    static void method1() throws Exception {
        throw new Exception();
    }
}

 

 

 

 

커스텀 예외 만들기 (사용자정의 예외)

기존에 정의된 예외 클래스 외에 프로그래머가 새로운 예외 클래스를 정의하여 사용할 수 있습니다.

 

사용자 정의 예외는

  • Exception 클래스를 상속받을지
  • RuntimeException 클래스를 상속받을지

에 따라 나뉩니다.

 

 

일단 두 가지 방식에 대한 차이는 뒤에서 언급하고, 사용자 정의 예외 만드는 방법부터 알아보겠습니다.

 

 

방법

Exception클래스를 상속받고, 필요한 멤버변수나 메서드를 추가할 수 있습니다.

 

Exception클래스의 생성자는 String값을 받아서 메시지로 저장할 수 있기 때문에 MyException에서도 super()를 이용해 메시지를 저장할 수 있도록 생성자를 만들 수 있습니다.

class MyException extends Exception {
    MyException(String message) {
        super(message);
    }
}

 

 

필요한 멤버변수나 메서드 추가

class MyException extends Exception {
    private final int ERR_CODE;
    
    MyException(String message) {
        this(message, 100);
    }

    MyException(String message, int errCode) {
        super(message);
        ERR_CODE = errCode;
    }

    public int getERR_CODE() {
        return ERR_CODE;
    }

}

 

 

 

 

 

사용자 정의 예외 Exception vs RuntimeException

위에서 Exception을 상속받아 만든 MyException은 CheckedException이기 때문에 무조건 예외처리를 해주어야 합니다.

(Exception 상속 사용자정의 예외는 예외처리에 강제성 부여)

 

반면에 RuntimeException을 상속받아 만들면 반드시 예외처리를 하지 않아도 됩니다.

 

 

  Checked Exception Unchecked Exception
처리여부 반드시 예외 처리 예외 처리 하지 않아도 됨
트랜잭션 Rollback 여부 Rollback 안됨 Rollback 진행
대표 Exception IOException, SQLException NullPointerException, IllegalArgumentException

 

 

또 하나 눈여겨 보아야 할 것은 트랜잭션 Rollback의 여부 입니다.

기본적으로 Checked Exception은 복구가 가능할 것이라는 메커니즘을 갖고 있습니다.

 

예를들어 특정 이미지 파일을 찾아 전송해주는 메서드에서 만약 이미지를 찾지 못했을 경우 기본이미지를 전송한다고 생각해보면, 이 예외는 복구 전략을 가집니다.

 

하지만 현실적으로 생각해 보겠습니다.

 

사용자에게 중복된 이메일을 받으면 SQLException이 발생한다고 생각해보면,

트랜잭션 커밋이 되어버리는 Checked Exception보다는 그냥 RuntimeException을 발생시키고 사용자에게 입력을 다시 하도록 하는 것이 더 좋습니다.

 

 

 

그럼 어떻게 해결?

이런 경우에는 CheckedException이 발생했을 때, 더 명확한 이름의 UncheckedException으로 바꾸어 발생시키는 것이 좋습니다.

 

RuntimeException을 상속받아 만든 사용자정의 예외클래스 DuplicateEmailException(Unchecked Exception)을 발생시키면 됩니다.

 

 

출처 : https://cheese10yun.github.io/checked-exception/

 

 

 

 

예외 처리 전략 정리

 

1. 대부분의 경우에 발생하는 Checked Exception은 더 명확한 이름의 Unchecked Exception으로 던지는 것이 좋습니다.


2. 무분별한 throws를 남발하지 않습니다. throws를 사용할땐 어떤 메서드에서 예외처리를 해줄지 정확한 설계가 잡혀있어야 합니다.

'Web > Java' 카테고리의 다른 글

[Java] 애노테이션  (1) 2021.08.11
[Java] 멀티쓰레드 프로그래밍  (1) 2021.08.06
[Java] 인터페이스  (2) 2021.07.26
[Java] brew로 자바 특정버전 설치하고 적용하기  (0) 2021.07.19
[Java] 패키지  (2) 2021.07.19