백기선 자바 스터디 1기 9주차

2023. 10. 22. 02:21Java/java-live-study

728x90
반응형

목표

자바의 예외 처리에 대해 학습하세요.

학습할 것

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

프로그램 오류란?

프로그램이 실행 중 어떠한 원인에 의해 오작동 하거나 비정상적으로 종료되는 경우를 프로그램 에러 또는 오류라고 한다.

 

이것을 발생시점에 따라 '컴파일 에러'와 '런타임 에러'로 나눌 수 있다.

 

컴파일 에러란 소스코드 컴파일 시점에 컴파일러가 문법 오류가 있다면 발생 시키는 오류이다.

런타임 에러란 프로그램 실행 도중 발생하는 에러로 자바에서는 런타임 에러를 '에러(error)' 와 '예외(Exception)' 두 가지로 구분한다.

 

에러는 메모리 부족(OutOfMemorryError)나 스택오버플로우(StackOverflowError)와 같이 발생하면 복구할 수 없는 심각한 오류이고, 

예외는 발생하더라도 코드로 수습이 가능한 덜 심각한 오류이다.


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

자바에서는 실행 시 발생할 수 있는 오류를 클래스로 정의하였다.

자바에서는 모든 클래스의 조상은 Object 클래스이기 때문에 Exception과 Error 클래스도 마찬가지로 Object 클래스의 자손들이다.

 

 

이 중에서도 예외(Exception)의 계층을 자세히 살펴보면 다음 그림과 같다.

 

모든 예외의 조상은 Exception 클래스이다.


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

위의 그림에서 RuntimeException 클래스라는 것이 있다.

 

자바에서는 Exception 중에서 RuntimeException 이 아는 것들은 'Checked Exception', RuntimeException 이거나 상속받는 예외들은 'Unchecked Exception'이라고 부른다.

 

그렇다면 Checked Exception(이하 CE)과 Unchecked Exception(이하 UE)을 구분지은 이유는 무엇일까?

 

우선 CE는 아래와 같이 개발자가 CE를 일으킬 가능성을 가진 메소드를 사용할때 'try' 나 'throws' 로 적절한 예외처리를 하지 않는다면 컴파일러가 컴파일 에러를 발생시킨다.

 

반면 UE는 개발자가 UE를 일으킬 가능성을 가진 메소드를 사용하더라도 컴파일러는 별다른 동작을 취하지 않는다.

CE는 주로 IO, 네트워크, DB와 같이 외부 요인으로 인한 예외적 상황을 나타내기 때문에 Java 코드를 잘 작성하더라도 외부 요인에 따라 충분히 발생할 수 있다.

 

이 때문에 Java는 개발자가 이러한 잠재적인 예외를 고려하고 처리할 수 있도록 보장하기 위해 CE의 예외처리를 컴파일 타임에 강제하는 것이다.

 

반면, UE는 주로 코드의 오류로 발생한다. 예를 들어 '0으로 나누기', '널 포인터 참조', '배열 인덱스 초과' 등이 있는데 이러한 오류들은 Java 코드를 잘 작성하는 것으로 예외를 방지할 수 있다. 물론 개발자가 원한다면 CE와 마찬가지로 'try', 'throws' 문을 통해 예외처리를 할 수 있지만 이를 강제하지는 않는다.

 

UE는 개발자에게 더 많은 유연성을 제공하고 코드를 단순화할 수 있지만 더 많은 책임을 부여하기 때문에 코드 작성시 유의해야한다.


자바에서 예외 처리 방법 (try, catch, throw, thorws, finally)

try-catch

예외 처리를 위해서는 try-catch 구문을 이용하며 그 구조는 다음과 같다.

try {
    /// 예외가 발생할 가능성이 있는 코드
} catch (Exception1 e1) {
    /// Exception1이 발생했을 때, 이를 처리하기 위한 코드
} catch (Exception2 e2) {
    /// Exception2가 발생했을 때, 이를 처리하기 위한 코드
}

 

 

try-catch문의 흐름

try-catch문의 실행 흐름은 아래와 같다.

class Main {
    public static void main(String[] args) {
        System.out.println(1); //출력

        try {
            System.out.println(2); //출력
            System.out.println(3 / 0); //예외 발생
        } catch (ArithmeticException e) {
            System.out.println(4); //출력
        } catch (Exception e) {
            System.out.println(5); //건너뜀
        }

        System.out.println(6); //출력
    }
}

ArithmeticException이 catch 되고 try-catch 문을 빠져나오기 때문에 숫자 5는 출력되지않는다.

 

Multicatch block

JDK 1.7부터 여러 catch block을 하나로 합칠 수 있게 되었다.

class Main {
    public static void main(String[] args) {
        try {
            System.out.println(1);
        } catch (ArithmeticException | IllegalArgumentException e) {
            System.out.println(2);
        }
    }
}

단 이때, 나열된 예외 클래스들이 부모-자식 관계에 있다면 오류가 발생한다.

class Main {
    public static void main(String[] args) {
        try {
            System.out.println(1);
        } catch (ArithmeticException | RuntimeException e) {
            System.out.println(2);
        }
    }
}

왜냐하면, 자식 클래스로 잡아낼 수 있는 예외는 부모 클래스로도 잡아낼 수 있기 때문에 사실상 코드가 중복된 것이나 마찬가지이기 때문이다. 이때 컴파일러는 중복된 코드를 제거하라는 의미에서 에러를 발생시킨다.

또한 멀티캐치는 하나의 블록으로 여러 예외를 처리하는 것이기 때문에 멀티 캐치 블록 내에서는 발생한 예외가 정확이 어디에 속한 것인지 알 수 없다.

 

throw

throw 키워드를 이용해서 고의로 예외를 발생시킬 수도 있다.

class Main {
    public static void main(String[] args) {
        try {
            Exception e = new Exception();
            throw e;
        } catch (Exception e) {
            e.getMessage();
        }
    }
}

 

throws

throws 키워드를 통해 메서드에 예외를 선언할 수 있다. 여러 개의 메서드를 쉼표로 구분해서 선언할 수 있다. 형태는 다음과 같다.

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

thorws는 메서드 선언부에 예외를 선언해둠으로써 해당 메서드를 사용하는 사람들이 어떤 예외를 처리해야 하는 지를 알려주는 역할을 한다.

throws 자체는 예외의 처리와는 관계가 없다. throws로 예외가 선언된 메서드를 사용할 때, 사용자가 각자 알아서 예외를 처리해줘야 한다. 즉 throws는 해당 메서드에서 예외를 처리하지 않고, 해당 메서드를 사용하는 쪽이 예외를 처리하도록 책임을 전가하는 역할을 한다.

 

finally

finally는 try-catch와 함께 예외의 발생 여부와 상관없이 항상 실행되어야 할 코드를 포함시킬 목적으로 사용된다. try-catch문의 끝에 선택적으로 덧붙여 사용할 수 있으며, try-catch-finally의 순서로 구성된다.

try {
    // 예외가 발생할 가능성이 있는 문장을 넣는다.
} catch {
    // 예외 처리를 위한 문장을 넣는다.
} finally {
    // 예외 발생 여부와 상관없이 항상 실행되어야 할 문장을 넣는다.
}

예외가 발생한 경우에는 try -> catch -> finally 순으로 실행되고, 예외가 발생하지 않은 경우에는 try - finally 순으로 실행된다.

한 가지 짚고 넘어갈 점은 finally 블록 내의 문장은 try, catch 블록에 return문이 있더라도 실행된다는 것이다.

public class ExceptionDemo {

    public static void main(String[] args) throws Exception {
        methodA();
        System.out.println(“methodA가 복귀한 후 실행될 문장”);
    }

    static void methodA() {
        try {
            System.out.println(“트라이 블록 수행”);
            return;
        } catch (Exception e) {
            System.out.println(“캐치 블록 수행”);
        } finally {
            System.out.println(“파이널리 블록 수행”);
        }
    }
}

 

try-with-resource

java7부터 자원 해제를 자동으로 해주는 try-with-resources 구문이 추가되었다. 원래 File input이나 DB 커넥션 생성 같이 시스템 자원을 사용하는 코드의 경우 finally 구문을 통해서 예외가 발생하더라도 반드시 자원을 닫아주는 형태로 코드를 작성했다.

public static void main(String args[]) throws IOException {
    FileInputStream is = null;
    BufferedInputStream bis = null;
    try {
        is = new FileInputStream("file.txt");
        bis = new BufferedInputStream(is);
        //... do something

    } catch (IOException e) {
        // 에러처리
    } finally {
        // 어떤 경우에도 반드시 자원을 닫아야함
        if (is != null) is.close();
        if (bis != null) bis.close();
    }
}

하지만 try-with-resources 구문을 사용하면 같은 기능을 좀 더 깔끔한 코드로 작성할 수 있다.

public static void main(String args[]) {
    try (
        FileInputStream is = new FileInputStream("file.txt");
        BufferedInputStream bis = new BufferedInputStream(is)
    ) {
        //... do something
    } catch (IOException e) {
        // 에러처리
    }
}

try 옆에 괄호를 열고 자원을 할당 받으면 해당 자원은 블록 수행 이후 자동으로 반환이 된다. 모든 객체가 자동으로 반환되는 것은 아니고 AutoCloseable 인터페이스를 구현한 클래스만 자동으로 반환이 된다.


커스텀한 예외 만드는 방법

Java에서 기본으로 제공하는 예외 외에도 커스텀한 예외를 만들 수 있다.

 

아래와 같이 Exception 클래스나 상황에 따라 알맞은 예외 클래스를 상속받아 만들면 된다.

class CustomException extends Exception {
    public CustomException(String message) {
        super(message);
    }
}

(참고. https://m.blog.naver.com/sthwin/221144722072, https://dzone.com/articles/implementing-custom-exceptions-in-java?fromrel=true)

728x90
반응형