본문 바로가기
공부/자바

예외 처리(Exception handling)

by xladmt 2024. 6. 4.

프로그램이 실행 중 어떤 원인에 의해서 오작동을 하거나 비정상적으로 종ㄹ료되는 경우가 있다. 이러한 결과를 초래하는 원인을 프로그램 에러 또는 오류라고 한다.

 

오류의 종류

  • 컴파일 에러 : 컴파일 시에 발생하는 에러
  • 런타임 에러 : 실행 시에 발생하는 에러
  • 논리적 에러 : 실행은 되지만, 의도와 다르게 동작하는 것
▶ 에러 : 메모리 부족이나 스택오버플로우와 같이 일단 발생하면 복구할 수 없는 심각한 오류
 예외 : 발생하더라도 수습될 수 있는 비교적 덜 심각한 것

 

 

예외 클래스의 계층 구조

 

예외클래스 계층도

 

  • Exception 클래스들 : 사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외
  • RuntimeException 클래스들 : 프로그래머의 실수로 발생하는 예외

 

예외 처리하기

예외처리란?

프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것이며, 예외처리의 목적은 예외의 발생으로 인한 실행 중인 프로그램의 갑작스런 비정상 종료를 막고, 정상적인 실행상태를 유지할 수 있도록 하는 것이다.

* 발생한 예외를 처리하지 못하면, 프로그램은 비정상적으로 종료되며, 처리되지 못한 예외(uncaught exception)는 JVM의 '예외처리기'가 받아서 예외의 원인을 화면에 출력한다.

 

<예외처리 방법>

① try-catch문 (직접처리)

② 예외 선언하기(떠넘기기)

③ 덮기, 무시(은폐)

 

try-catch문

try-catch문에서 예외가 발생한 경우와 발생하지 않았을 때의 흐름(문장의 실행순서)이 달라지는데, 이 두 가지 경우에 따른 문장 실행 순서는 아래와 같다.

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

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

 

코드로 이해하기

class ExceptionEx{
    public static void main(Strig args[]) {
        System.out.println(1);
        System.out.println(2);
        try {
            System.out.println(3);
            System.out.println(0/0);  // 0으로 나눠서 ArithmeticException 발생
            System.out.println(4);    // 실행되지 않음.
        } catch (ArithmeticException ae) {
            if(ae instanceof ArithmeticException)
                System.out.println("true");
            System.out.println("ArighmeticException");
        } catch (Exception e) {     // ArighmeticException을 제외한 모든 예외가 처리됨
            System.out.println("Exception");
        }
        System.out.println(6);
    }
}

 

실행결과

1
2
3
true
ArithmeticException
6

 

 

printStackTrace()와 getMessage()

예외가 발생했을 때 생성되는 예외 클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨져 있으며, getMessage()와 printStackTrace()를 통해서 이 정보들을 얻을 수 있다. catch 블럭의 괄호()에 선언된 참조변수를 통해 이 인스턴스에 접근할 수 있다. 이 참조변수는 선언된 catch블럭 내에서만 사용 가능하며, 자주 사용된다.

printStackTrace() 예외 발생 당시의 호출스택(Call Stack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력한다.
getMessage() 발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.

 

 

finally블럭

finally 블럭은 try-catch문과 함께 예외의 발생여부에 상관없이 실행되어야 할 코드를 포함시킬 목적으로 사용되며 try-catch-finally 순서로 구성된다.

 

자동 자원 반환 ( try-with-resources문)

주로 입출력에 사용되는 클래스 중에서는 사용한 후에 꼭 닫아 줘얗 하는 것들이 있다. 그래야 사용했던 자원(resources)이 반환되기 때문이다.

 

코드 예)

try(fis = new FileInputStream("score.dat");
    dis = new DataInputStream(fis)) {
    while(true){
        score = dis.readInt();
        System.out.println(score);
        sum += score;
    }catch(EOFException e){
        System.out.println("점수 총합"+sum);
    }catch(IOException ie){
        ie.printStackTrace();
    }

try-with-resources문의 괄호() 안에 객체를 생성하는 문장을 넣으면, 이 객체는 따로 close()를 호출하지 않아도 try블럭을 벗어나는 순간 자동적으로 close()가 호출된다. 이처럼 try-with-resources문에 의해 자동으로 객체의 close()가 호출될 수 있으려면, 클래스가 AutoCloseable이라는 인터페이스를 구현한 것이어야만 한다.

 

예외 되던지기(Exception re-throwing)

한 개 이상의 예외에 대해서 try-catch문을 통해 메서드 내에서 자체적으로 처리하고, 그 나머지는 선언부에 지정하여 호출한 메서드에서 처리하도록 함으로써 양쪽에서 나눠서 처리되도록 할 수 있다. 예외를 처리한 후에 인위적으로 다시 발생시키는 방법으로 할 수 있는데, 이것을 '예외 던지기' 라고 한다.

 

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

    static void method1() throws Exception {
        try{
            throw new Exception();
        }catch (Exception e){
            System.out.println("method1메서드에서 예외가 처리되었습니다.");
            throw e;   // 다시 예외를 발생시킨다.
        }
    }
}

// 출력 결과
// method1메서드에서 예외가 처리되었습니다.
// main 메서드에서 예외가 처리되었습니다.

 

[반환값이 있는 return문의 경우]

    static int method1(){
        try{
            System.out.println("method1()이 호출되었습니다.");
            return 0;
        }catch(Exception e){
            e.printStackTrace();
            return 1;             // catch 블럭에도 return문이 필요하다.
        }finally {
            System.out.println("method1()의 finally 블럭이 실행되었습니다.");
        }
    }

catch 블럭에도 return문이 있어야 한다. 예외가 발생했을 경우에도 값을 반환해야하기 때문이다. 또는, catch블럭에서 예외 되던지기를 할 경우, return문이 없어도 된다.

// ...
}catch(Exception e) {
    e.printStackTrace();
    //return 1;           
    throw new Exception(); // return 문 대신 예외를 호출한 메서드로 전달
} //...

 

 

연결된 예외(chained exception)

한 예외가 다른 예외를 발생시킬 수도 있다. 예를 들어 예외 A가 예외 B를 발생시켰다면, A를 B의 '원인 예외'라고 한다. 

 

  • SpaceException을 원인 예외로 하는 InstallException을 발생시키는 방법
void install() throws InstallException {
    try{
        startInstall();
        copyFiles();
    }catch (SpaceException e) {
        InstallException ie = new InstallException("설치중 예외발생"); // 예외 생성
        ie.initCause(e);    // InstallException의 원인 예외를 SpaceException으로 지정
        throw ie;           // InstallException을 발생시킨다.
    }catch(MemoryException me) {
        // ...
    }
}

먼저, InstallEsception을 생성한 후에, initCause()로 SpaceException을 InstallException의 원인 예외로 등록한다. 그리고 'throw'로 이 예외를 던진다.

 

*원인 예외로 등록해서 다시 예를 발생시키는 이유

① 여러가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위해서이다.

② checked예외(Exception 자손 필수처리)를 unchecked예외(RuntimeException 자손 선택처리)로 바꿀 수 있도록 하기 위해서이다.

 

[참고]

Java의 정석 (남궁성 저) 8장 예외처리 

'공부 > 자바' 카테고리의 다른 글

제네릭스(Generics)  (0) 2024.06.12
컬렉션 프레임워크(Collections Framework)  (0) 2024.06.10
내부 클래스(inner class)  (0) 2024.06.01
인터페이스(interface)  (0) 2024.05.31
추상 클래스(abstract class)  (0) 2024.05.31