Java/복습

[Java/복습] 예외 처리

hrming 2023. 8. 13. 23:14

1. 오류 & 에러 

오류(error)는 시스템 레벨에서 프로그램에 심각한 문제를 야기하여 실행 중인 프로그램을 종료시킨다. 이러한 오류는 개발자가 예측하여 처리할 수 없는 것이 대부분이므로, 오류에 대한 처리는 할 수 없다.

예외(exception)는 오류와 마찬가지로 실행 중인 프로그램을 비정상적으로 종료시키지만, 발생할 수 있는 상황을 미리 예측하여 처리할 수 있다. 따라서 개발자는 예외 처리(exception handling)를 통해 예외 상황을 처리할 수 있도록 코드의 흐름을 바꿀 필요가 있다.


2. 예외 처리 (exception handling)

2-1.  try / catch / finally 문을 사용

try {
    예외를 처리하길 원하는 실행 코드;
} catch (e1) {
    e1 예외가 발생할 경우에 실행될 코드;
} catch (e2) {
    e2 예외가 발생할 경우에 실행될 코드;
}
...
finally {
    예외 발생 여부와 상관없이 무조건 실행될 코드;
}

① try 블록 : 기본적으로 맨 먼저 실행되는 코드로 여기에서 발생한 예외는 catch 블록에서 처리된다.

② catch 블록 : try 블록에서 발생한 예외 코드나 예외 객체를 인수로 전달받아 그 처리를 담당한다.

③ finally 블록 : 이 블록은 try 블록에서 예외가 발생하건 안 하건 맨 마지막에 무조건 실행된다.

 

④ catch 블록과 finally 블록은 선택적인 옵션으로 반드시 사용할 필요는 없다. 사용 가능한 try 구문은 다음과 같다.

  - try / catch

  - try / finally

  - try / catch / ... / finally

다른 제어문과는 달리 예외 처리문은 중괄호({})를 생략할 수 없다.


2-2. 예외 처리 메커니즘 

①번 try 블록에서 예외가 발생하지 않고, 바깥쪽 try 블록에서도 예외가 발생하지 않으면, ⑥번 finally 블록이 바로 실행될 것.

 

①번 try 블록에서 예외가 발생하면, ②번과 ③번 catch 블록에서 해당 예외를 처리할 수 있는지 검사하게 된다.

만약 적절한 catch 블록을 찾지 못하면, 바깥쪽 try 블록의 ④번과 ⑤번 catch 블록도 차례대로 검사하게 된다.

이때 해당 예외를 처리할 수 있는 catch 블록을 찾게 되면, 해당 catch 블록을 실행한 후 ⑥번 finally 블록을 실행한다. 

하지만 모든 catch 블록이 해당 예외를 처리할 수 없으면, 예외는 처리되지 못한 채 해당 프로그램은 강제 종료될 것. 

 


3. 예외 클래스 

3-1. 예외 클래스

: 자바에서 모든 예외의 조상 클래스가 되는 Exception 클래스는 크게 다음과 같이 구분할 수 있다.

  - RuntimeException 클래스

  - 그 외의 Exception 클래스의 자식 클래스

RuntimeException 클래스를 상속받는 자식 클래스들은 주로 치명적인 예외 상황을 발생시키지 않는 예외들로 구성된다.

따라서 try / catch 문을 사용하기보다는 프로그램을 작성하면서 예외가 발생하지 않도록 주의를 기울이는 편이 좋다.

 

그 외의 Exception 클래스에 속하는 자식 클래스들은 치명적인 예외 상황을 발생시키므로, 반드시 try / catch 문을 사용하여 예외를 처리해야만 한다. 따라서 자바 컴파일러는 RuntimeException 클래스 이외의 Exception 클래스의 자식 클래스에 속하는 예외가 발생할 가능성이 있는 구문에는 반드시 예외를 처리하도록 강제하고 있다. 만약 이러한 예외가 발생할 가능성이 있는 구문을 예외 처리하지 않았을 때는 컴파일 시 오류를 발생시킨다.


3-2. 예외 처리의 계층 관계

 아래 예시에서, IOException은 Exception의 자식 클래스이므로, 첫 번째 catch 블록에서도 IOException을 처리할 수 있다.

따라서 IOException을 비롯한 Exception 클래스의 자식 클래스에 해당하는 예외가 발생하면, 언제나 첫 번째 catch 블록에서만 처리될 것이다.  IOException만을 따로 처리하고자 한다면, Exception 클래스와 IOException 클래스의 catch 블록의 순서를 변경해야 한다. 

try {
    System.out.write(list);
     // Exception 클래스는 IOException의 조상클래스 이므로, 
     // IOException을 포함한, Exception 클래스의 자손 클래스에 해당하는 예외는
     // Exception 클래스에서 모두 처리 가능. 
} catch (Exception e) {
    e.printStackTrace();
     // 이 경우에는, 아래 catch문은 작동하지 않음
} catch (IOException e) {
    e.printStackTrace();
}

이처럼 범위가 더 좁은 예외를 처리하는 catch 블록을 먼저 명시하고, 범위가 더 넓은 예외를 처리하는 catch 블록은 나중에 명시해야만 정상적으로 해당 예외를 처리할 수 있다.


3-3. 여러 예외 타입의 동시 처리

: Java SE 7부터는 '|' 기호를 사용하여 하나의 catch 블록에서 여러 타입의 예외를 동시에 처리할 수 있다.

: 하지만 둘 이상의 예외 타입를 동시에 처리하는 catch 블록에서 매개변수로 전달받은 예외 객체는 묵시적으로 final 제어자를 가지게 됩니다. 따라서 catch 블록 내에서 해당 매개변수에는 어떠한 값도 대입할 수 없다.

try {
    this.db.commit();
} catch (IOException e) {
    e.printStackTrace();
} catch (SQLException e) {
    e.printStackTrace();
}


// 위 코드를 아래와 같이 변경 가능
try {
    this.db.commit();
} catch (IOException | SQLException e) {
    e.printStackTrace();
}

 


4. Throwable 클래스

자바에서 Throwable 클래스는 모든 예외의 조상이 되는 Exception 클래스와 모든 오류의 조상이 되는 Error 클래스의 부모 클래스이다. Throwable 타입과 이 클래스를 상속받은 서브 타입만이 자바 가상 머신(JVM)이나 throw 키워드에 의해 던져질 수 있다. 이 클래스에는 예외나 오류에 관한 다양한 정보를 확인할 수 있는 다음과 같은 메소드가 포함되어 있다.

 

메소드 설명
String getMessage() 해당 throwable 객체에 대한 자세한 내용을 문자열로 반환함.
void printStackTrace() 해당 throwable 객체와 표준 오류 스트림(standard error stream)에서 해당 객체의 스택 트레이스(stack trace)를 출력함.
String toString() 해당 throwable 객체에 대한 간략한 내용을 문자열로 반환함.

 


4. 자주 사용되는 예외 클래스

클래 설명
ClassCastException 수행할 수 없는 타입 변환이 진행될 경우
ArrayIndexOutOfBoundsException 배열에 잘못된 인덱스를 사용하여 접근할 경우
NullPointerException null 객체의 인스턴스 메소드를 호출하는 등의 경우
ArithmeticException 산술 연산에서 정수를 0으로 나누는 등 연산을 수행할 수 없는 경우

 


5. 예외 발생 및 회피

5-1. 예외 발생시키기

: 자바에서는 throw 키워드를 사용하여 강제로 예외를 발생시킬 수 있다.

Exception e = new Exception("오류메시지");
...
throw e;
// 위의 예제처럼 생성자에 전달된 문자열은 
// getMessage() 메소드를 사용하여 오류 메시지로 출력할 수 있습니다.

5-2. 예외 회피하기

: 메소드 선언부에 throws 키워드를 사용하여 해당 메소드를 사용할 때 발생할 수 있는 예외를 미리 명시할 수도 있다.

이렇게 하면 해당 메소드를 사용할 때 발생할 수 있는 예외를 사용자가 충분히 인지할 수 있으며, 그에 대한 처리까지도 강제할 수 있다 따라서 더욱 안정성 있는 프로그램을 손쉽게 작성할 수 있도록 도와줄 수 있다.

 

- 다음 예제는 호출된 메소드에서 발생한 예외를 호출된 메소드에서 처리하는 예제입니다.

public class Exception03 {

    static void handlingException() {
        try {
            throw new Exception();
        } catch (Exception e) {
            System.out.println("호출된 메소드에서 예외가 처리됨!");
        }
    }

     public static void main(String[] args) {
        try {
            handlingException();
        } catch (Exception e) {
            System.out.println("main() 메소드에서 예외가 처리됨!");
        }
    }
}

// 호출된 메소드에서 예외가 처리됨!
// 이때 호출된 메소드의 try / catch 문을 생략하면 컴파일 오류가 발생합니다.
// 또한, 이 메소드를 호출한 main() 메소드는 호출된 메소드에서 예외가 발생한 사실을 알 수 없습니다.

- 다음 예제는 throws 키워드를 사용하여 호출된 메소드에서 발생한 예외를 호출한 메소드로 넘기는 예제입니다.

public class Exception04 {
    static void handlingException() throws Exception { throw new Exception(); }
    public static void main(String[] args) {
        try {
            handlingException();
        } catch (Exception e) {
            System.out.println("main() 메소드에서 예외가 처리됨!");
        }
    }
}

// main() 메소드에서 예외가 처리됨!
// 이렇게 함으로써 호출된 메소드에는 try / catch 문을 생략할 수 있습니다.
// 그리고 호출된 메소드에서 발생한 예외를 해당 메소드를 호출한 main() 메소드에서 처리할 수 있게 됩니다.

5-3. 사용자 정의 예외 클래스

자바에서는 Exception 클래스를 상속받아 자신만의 새로운 예외 클래스를 정의하여 사용할 수 있다.

사용자 정의 예외 클래스에는 생성자뿐만 아니라 필드 및 메소드도 원하는 만큼 추가할 수 있다.

class MyException extends RuntimeException {
    MyException(String errMsg) {
        super(errMsg);
    }
}
// 요즘에는 위와 같이 Exception 클래스가 아닌 
// 예외 처리를 강제하지 않는 RuntimeException 클래스를 상속받아 작성하는 경우가 많습니다.

// RuntimeException(String message)
// Constructs a new runtime exception with the specified detail message.

5-4. try - with - resources 문

try (파일을열거나자원을할당하는명령문) {
     ...
}

// 위와 같이 try 블록에 괄호(())를 추가하여 파일을 열거나 자원을 할당하는 명령문을 명시하면,
// 해당 try 블록이 끝나자마자 자동으로 파일을 닫거나 할당된 자원을 해제해 줍니다.
static String readFile(String filePath) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(filePath));
    try {
        return br.readLine();
    } finally {
        if (br != null)
            br.close();
		    // 위와 같이 Java SE 7 이전에서는 finally 블록을 사용하여 사용한 파일을 닫아줘야 했습니다.
    }
}
static String readFile(String filePath) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
        return br.readLine();
        // 하지만 try-with-resources 문을 사용하면 다음과 같이 자동으로 파일의 닫기를 수행할 수 있습니다.
    }
}

 

** try - with - resources 문 관련하여, 하기 블로그 참고!

https://mangkyu.tistory.com/217

 

[Java] try-with-resources란? try-with-resources 사용법 예시와 try-with-resources를 사용해야 하는 이유

이번에는 오랜만에 자바 문법을 살펴보고자 합니다. Java7부터는 기존의 try-catch를 개선한 try-with-resources가 도입되었는데, 왜 try-catch가 아닌 try-with-resources를 사용해야 하는지, 어떻게 사용하는지

mangkyu.tistory.com

 

 

 

 

참고 😎

1. http://www.tcpschool.com/java/java_exception_intro

2. http://www.tcpschool.com/java/java_exception_class

3. http://www.tcpschool.com/java/java_exception_throw

4. https://mangkyu.tistory.com/217