뭐라도 끄적이는 BLOG

Java Exception, Error 본문

Java/Java 기본

Java Exception, Error

Drawhale 2023. 7. 2. 05:34

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

try - catch - final

예외를 처리하기 위한 try-catch기본적인 구문으로 구조는 아래와 같다.

try {
    예외를 처리하길 원하는 실행 코드;
} catch (e1) {
    e1 예외가 발생할 경우에 실행될 코드;
} catch (e2) {
    e2 예외가 발생할 경우에 실행될 코드;
}

try 블록은 기본적으로 먼저 실행되는 코드로 이곳에서 예외가 발생하면 catch 블록에서 처리하게 된다. catch블록은 try블록에서 발생항 예외 코드나 예외 객체를 인수로 전달받아 그 처리를 담당한다.

위 코드에서 finally문을 추가해서 사용할 수 있다.

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

finally블록은 try에서 예외가 발생하건 안하건 맨 마지막에 무조건 실행되는 블록이다. try에서 catch와 finally중 하나는 반드시 사용해야한다.

  1. try / catch
  2. try / finally
  3. try / catch / finally
다른 제어문들과 달리 예외처리는 중괄호를 생략할 수 없다.

try - with - resources

java7 이전 try-catch-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);
        int data = -1;
        while((data = bis.read()) != -1){
            System.out.print((char) data);
        }
    } finally {
        // close resources
        if (is != null) is.close();
        if (bis != null) bis.close();
    }
}

try에서 InputStream 객체를 생성하고 finally에서 close를 했다. Exception 발생시 모든 코드가 실행되지 않을 수 있으므로 finally에 close코드를 넣어 줘야한다. 심지어 InputStream객체가 null인지 체크해줘야하며 close에 대한 Exception처리도 해줘야 한다. 위 코드에서는 main에 IOException을 throws한다고 선언했기 때문에 close에 대한 try-catch를 작성하지 않았습니다. throws하지 않으면 finally는 아래와 같이 더 복잡해진다.

}finally{
	if(is != null){
		try{
			is.close();
		}catch(IOException e){
		}
	}
	if(bis != null){
		try{
			bis.close();
		}catch(IOException e){
		}
	}
}

사실 위 코드도 is.close()에서 Exception이 발생해버리면 bis가 close 되지 않을 위험이 있다.

public static void main(String args[]) throws IOException {
    FileInputStream is = null;
    BufferedInputStream bis = null;
    try {
        is = new FileInputStream("file.txt");
        try {
            bis = new BufferedInputStream(is);
            int data = -1;
            while((data = bis.read()) != -1){
                System.out.print((char) data);
            }
        } finally {
            if (bis != null)
            {
                try{
                    bis.close();
                }catch(IOException e){
                }
            }
        }
    } finally {
        if (is != null){
            try{
                is.close();
            }catch(IOException e){
            }
        }
    }
}

java7부터 추가된 try-with-resources문은 exception시 resources를 자동적으로 close()해준다. 객체는 AutoCloseable인터페이스를 구현한 객체여야한다.

public interface AutoCloseable { 
    void close() throws Exception; 
}

이제 위 코드를 try-with-resources문으로 변경해 본다.

public static void main(String args[]) {
    try (
        FileInputStream is = new FileInputStream("file.txt");
        BufferedInputStream bis = new BufferedInputStream(is)
    ) {
        int data = -1;
        while ((data = bis.read()) != -1) {
            System.out.print((char) data);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

try괄호안에 객체 선언 및 할당이 들어가고 여기서 선언한 변수들은 try안에서 사용할 수 있다. 그리고 try를 벗어나면 try-with-resources는 try괄호안의 객체의 close()메소드들을 호출한다.

코드가 짧고 간결하게 만들어 유지보수가 더 편해지고 실수로 close를 빼먹는 경우를 예방할수 있다. java9에서는 try-with-resources가 더 향상되었다.

public static void main(String args[]) {
    FileInputStream is = new FileInputStream("file.txt");
    BufferedInputStream bis = new BufferedInputStream(is);
    try (is; bis) {
        int data = -1;
        while ((data = bis.read()) != -1) {
            System.out.print((char) data);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

외부에서 선언 및 할당을 해주고 try괄호 안에 참조변수를 넣으면 된다.

e.printStackTrace();

catch문의 참조변수는 예외처리시 발생한 예외에 대한 정보가 담겨 있다. 예외 인수에 printStackTrace();를 통해 예외가 발생하기 까지의 정보를 콘솔에 프린트 해준다.

e.getMessage();

발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다. 예외 정보는 메모리의 Stack영역에 Stack Frame이 쌓이게 되는데 예외가 발생되면 예외의 내용을 보여주는 Stack Trace의 각 라인은 하나의 Stack Frame을 표현하는 것이며, 메모리 영역의 Stack에 쌓여있는 Stack Frame들을 pop한다.

예외처리 비용

예외를 사용하는 것은 비용이 비싸다. try-catch동작에서 발생하는 검사들도 하나의 원인이지만 Throwable 생성자의 fillInStackTrace()메서드가 주 원인이다. 이 메서드는 예외가 발생한 메서드의 Stack Trace를 모두 출력해주기 때문이다. 여기서 StackTrace란 Application이 실행된 시점부터 현재 실행 위치까지의 메소드 호출 목록을 말한다.

충분히 로직으로 처리할 수 있는 것이라면 Exception보다 로직을 이용하는 것이 좋다.

예외 처리 메커니즘

자바에서 예외 처리는 다음과 같은 순서로 진행된다.

  1. try블록에 도달한 프로그램의 제어는 try블록 내의 코드를 실행한다. 이때 만약 예외가 발생(throw)하지 않고, finally블록이 존재하면 프로그램의 제어는 바로finally블록으로 이동한다.
  2. try블록에서 예외가 발생하면 catch 핸들러는 다음과 같은 순서로 적절한 catch블록을 찾게 된다.
    1. 스택에서 try블록과 가장 가까운 catch블록부터 차례대로 검사
    2. 적절한 catch블록을 찾지 못하면, 바로 다음 바깥쪽 try블록 다음에 위치한 catch블록을 차례대로 검사
    3. 이러한 과정을 가장 바깥쪽 catch블록까지 계속 검사
    4. 그래도 적절한 catch를 찾지 못하면 예외는 처리되지 못한다.
  3. 만약 적절한 catch블록을 찾게 되면, throw문의 피연사자는 예외 객체의 형식 매개변수로 전달된다.
  4. 모든 예외처리가 끝나면 프로그램의 제어는 finally블록으로 이동한다.
  5. finally 블록이 모두 처리되면, 프로그램의 제어는 예외 처리문 바로 다음으로 이동한다.

예회처리의 흐름 예시

  1. try 블록 내의 문장을 실행하다가 예외가 발생하여 예외 인스턴스를 생성하고 첫 catch블록으로 이동
  2. 괄호 내에 선언된 참조 변수의 종류와 생성된 예외 클래스의 인스턴스에 instanceof연산자를 이용하여 검사하였을때 false이기 때문에 다음 catch문으로 이동
  3. catch문의 인스턴스와는 true이면 블록 내의 코드를 실행한후 바로 finally문을 실행

Multicatch

try {
    System.out.println(1 / 0);
} catch (IllegalArgumentException | ArithmeticException e) {
    System.out.println(e.getMessage());
}

java 7부터 여러 catch 블록을 하나로 합칠수 있게 되었다. 단 "|"로 구분된 예외클래스들이 상속관계를 가지고 있다면 오류가 발생한다. 왜냐하면 자식 클래스로 잡을 수 있는 예외는 부모클래스도 잡아낼 수 있기 때문에 사실상 코드가 중복된것이기 때문이다.

Multicatch는 하나의 블록으로 여러 예외를 처리하는 것이기 때문에 블록내에서 발생한 예외는 정확히 어떤 예외인지 알수 없다. 이때 e에는 연결된 예외들의 공통 조상 클래스에 대한 정보가 저장된다.

throw

throw키워드는 고의로 예외를 발생시킬 수 있는 키워드이다.

throw new IllegalArgumentException("부적절한 입력입니다.");

throws

throws 키워드는 발생한 예외를 메소드를 호출한 곳에서 처리할수 있도록 예외를 던지는(위임하는) 키워드이다. throws는 main메소드까지 사용할 수있는데 main에서 던진 예외를 JVM이 받아 대신 처리하게 된다. 결국 예외를 처리하지 않는것과 다름없어지기 때문에 귀찮아서 throws를 하는경우 없는것이 좋다.

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

자바에서는 실행시 발생할 수 있는 Exception과 Error를 클래스로 정의하고 있다. java.lang.Throwable 클래스는 Exception 및 Error클래스들의 루트 클래스이다.

예외 유형

Checked Exception

RuntimeException 및 Error를 제외하고 Throwable 클래스를 직접 상속하는 클래스는 IOException, SQLException등과 같은 예외는 컴파일시 확인된다.

Unchecked Exception

RuntimeException을 상속하는 클래스는 컴파일시 확인되지 않지만 런타임시 확인 된다.

Error

StackOverflowError, OutOfMemoryError, VirtualMachineError 등과 같은 오류는 직접 복구 할 수 없다.

 

Exception과 Error의 차이는?

Error는 메모리 부족(OutOfMemoryError), 스택오버플로우(StackOverFlowError)처럼 JVM이나 하드웨어 등의 기반 시스템의 문제로 발생하는 것이다. 시스템 레벨에서 발생하기 때문에 개발자가 미리 예측하여 처리할 수 없다. 그렇기 때문에 애플리케이션에서 오류에 대한 처리를 신경쓰지 않아도 된다.

Error를 처리하지 않아도 되지만 에러를 발생시켜볼 순 있다. 아래 예는 StackOverflowError가 발생하는 코드이다.

public class Test {
    public static void main(String args[]) {
        ErrorDemo demo = new ErrorDemo();
        demo.method1();
    }
}
class ErrorDemo{
    public void method1()
    {
        this.method2();
    }
    public void method2()
    {
        this.method1();
    }
}

애초에 이런 코드를 만들지 않는것이 예방이라면 예방일수 있다.

Exception은 Error와 다르게 개발자가 구현한 로직에서 발생하기 때문에 프로그래머가 적절한 코드로 발생상황을 미리 예측하여 처리할 수 있다. Exception은 개발자가 처리할 수 있기 때문에 예외를 구분하고 그에 따른 처리 방법을 명확히 알고 적용하는 것이 중요하다.

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

Java에서 RuntimeException은 Unchecked Exception으로 RuntimeException, Checked Exception으로 분류한다.

Checked Exception의 경우 예외를 예측하고 복구할 수 있기 때문에 예외를 처리하는 Exception Handler가 강제된다. 하지만 Unchecked Exception은 강제되지 않는다.

그리고 Unchecked Exception은 java.lang.RuntimeException을 상속받아 만들어지고 Checked Exception은 java.lang.Exception을 상속 받아 예외클래스를 만든다.

Checked Exception을 사용하면 클래스들간의 결합이 강해지기 때문에 RuntimeException을 사용하여 예외처리를 하는 경우가 많다.

커스텀한 예외 만드는 방법

사용자가 직접 예외를 만들수 있지만 되도록이면 자바에서 제공해주는 표준 예외를 사용하는 것이 좋다. 일반 코드의 재사용처럼 예외또한 재사용하는 것이 좋으며 자바 라이브러리는 충분한 예외를 제공해 주고 있기 때문이다. 그리고 다른 사람이 코드를 이해하기가 더 쉬워진다.

그렇더라도 예외를 만들고싶다면 오라클에서는 4가지정도 상황을 고려해보는 것이 좋다고 말한다.

  • Java플랫폼에서 제공되지 않는 예외 유형이 필요한가?
  • 다른 Vendor가 작성한 클래스에서 발생한 예외와 구별 할 수 있다면 사용자에게 도움이 되는가?
  • 코드가 둘 이상의 관련 예외를 발생시키는가?
  • 다른 사람의 예외를 사용하는 경우 사용자가 해당 예외에 액세스할 수 있는가? 비슷한 질문으로 패키지가 독립적이고 독립적이어야 하는가?

위 상황이 절대적인것은 아니다.

커스텀 Checked Exception

Exception 클래스를 상속 받아서 만들 수 있습니다.

public class InvalidNameException extends Exception{
    public InvalidNameException(){
        super();
    }
    public InvalidNameException(Throwable e){
        super(e);
    }
}

커스텀 Unchecked Exception

RuntimeException 클래스를 상속 받아 만들 수 있습니다.

반응형

'Java > Java 기본' 카테고리의 다른 글

Java Enum  (0) 2023.07.02
Java Thread  (0) 2023.07.02
Java Interface  (0) 2023.07.02
Java 패키지  (0) 2023.07.02
Java 상속(Inheritance)  (0) 2023.07.02