본문 바로가기

Java/Java SE, EE

[Java/Java SE, EE] 예외 처리 try-catch-finally, throws

예외 처리

프로그램에서 예외가 발생하면 프로그램이 비정상적으로 종료 될 수 있습니다. 예외가 발생할 때 프로그램이 정상 실행을 유지할 수 있도록 처리하는 것을 예외 처리라고 부릅니다.

  • 일반 예외(Exception)
    자바 컴파일러가 소스 파일을 컴파일 할 때, 일반 예외가 발생할 수 있는 코드에 컴파일 오류를 발생시켜 예외 처리를 강제합니다.
  • 런타임 예외(Runtime exception)
    자바 컴파일러가 예외 처리를 강제하지 않으므로, 예외 처리는 개발자의 경험에 의존됩니다.

try-catch-finally

예외 처리를 위한 하나의 코드 블록입니다. 예외 처리는 일반 예외런타임 예외에 대해서 모두 감지합니다.

try {

} catch ([예외 클래스] [예외 변수 이름]) {

} finally {
   
}

try-catch-finally 코드 블록은 3개의 블록으로 구성됩니다.

  • try { }
    예외가 발생할 가능성이 있는 코드를 작성합니다.
  • catch ([예외 클래스] [예외 변수 이름]) { }
    try { } 블록에서 예외가 발생했을 때 실행됩니다. 괄호()에는 감지하려는 예외 클래스와 변수 이름을 입력합니다.
  • finally { }
    예외 발생 여부와 관계없이 항상 실행됩니다. 이 블록은 생략 될 수 있습니다.

finally 구문은 생략 될 수 있습니다. 가장 기본적인 예외 처리문의 구성은 다음과 같습니다.

try {

} catch ([예외 클래스] [예외 변수 이름]) {

}

예외 처리문 실행 순서

일단 예외 처리문이 호출되면, try { } 블록은 반드시 실행됩니다. 하지만 try { } 블록 내 모든 코드 실행을 보장하지는 않습니다.

  1. try { }
    예외 처리문에서 가장 먼저 실행됩니다. 블록 내에서 예외가 발생하면 남은 코드의 실행을 멈추고 catch 블록으로 이동합니다.
  2. catch ([예외 클래스] [예외 변수 이름]) { }
    try { } 블록에서 예외가 발생에만 실행됩니다. catch 블록이 여러 개일 때 try { } 블록에서 발생한 예외 클래스와 일치하는 블록이 실행됩니다.
  3. finally { }
    try { } 블록의 예외 발생 여부와 관계 없이, 예외 처리문의 가장 마지막에 실행됩니다.

다음 예시에서는 Integer.valueOf("abcd")에서 NumberFormatException이 발생합니다. 

try {
   Integer.valueOf(1000);
   // (1-1) NumberFormatException 예외 발생
   Integer.valueOf("abcd");
   // (1-2) 이하 코드 미실행
   Integer.valueOf(9876);
} catch (NumberFormatException e) {
   // (2) 예외 처리 코드 실행
} finally {
   // (3) 예외 처리문의 마지막에 항상 실행
}

스택 프레임

자바에서는 하나의 메소드가 실행되기 위해 필요한 메모리를 프레임 단위로 구성합니다. try-catch-finally 각 블록 역시 서로 다른 스택 프레임을 구성합니다.

  • try-catch-fianlly의 각 블록은 서로의 지역 변수에 접근 할 수 없습니다.
  • try-catch-finally의 각 블록은 예외 처리문을 호출하고 있는 메소드의 지역 변수 등에 접근 할 수 있습니다.
  • 반면, 메소드에서는 try-catch-finally의 각 블록에 선언된 지역 변수에 접근 할 수 없습니다. 

예외 처리문에서 각 블록은 다른 블록에 선언된 지역 변수를 참조할 수 없습니다. 각 블록이 하나의 스택 프레임을 구성하기 때문입니다. 반면 예외 처리문의 블록은 이를 호출하는 메소드의 지역 변수 등에는 접근 할 수 있습니다.

int a = 10;

try {
   int b = a;           // 바깥 스택 프레임의 변수에 접근 가능
} catch (Exception e) {
   int c = b;           // 서로 다른 스택 프레임의 지역 변수 접근 불가. 컴파일 오류
} finally {
   int d = c;           // 서로 다른 스택 프레임의 지역 변수 접근 불가. 컴파일 오류
}

int e = d + c + b;      // 내부 스택 프레임의 지역 변수 접근 불가. 컴파일 오류

코드의 스택 프레임을 그림으로 나타내면 다음과 같습니다.

 

예외 처리문의 각 블록이 실행될때는, 다른 블록이 아직 실행되기 이전이거나 실행이 완료되어 스택 프레임이 미적재/제거 된 상태입니다. 따라서 서로 다른 스택 프레임에 있는 지역 변수에 접근 할 수 없습니다.

마찬가지로 int e = d + c + b 코드 역시 예외 처리문 블록 내 지역 변수 b, c, d를 포함하는 이미 스택 프레임이 이미 제거 된 상태이기 때문에 오류가 발생합니다.

다중 catch, try-catch-catch-finally

try 블록 내부에서는 여러 종류의 예외가 발생 할 수 있습니다. 예외 처리를 여러 개 예외에 맞게 처리하려면, catch 블록을 N개 이어 붙입니다. catch 블록은 2개 이상 붙여 사용 할 수 있습니다.

try {

} catch ([예외 클래스 1] [예외 변수 이름 1]) {

} catch ([예외 클래스 2] [예외 변수 이름 2]) {

} finally {
   
}

try 블록에서 예외는 동시에 하나만 발생합니다. 따라서 예외가 발생하면, 발생 예외 클래스와 일치하는 catch 블록 하나만 실행됩니다.

일단 try 블록에서 예외가 발생하면 catch 블록은 순차적으로 예외 클래스의 일치 여부를 검사합니다. 예외 클래스에서 가장 기초가 되는 부모 클래스는 java.lang.Exception입니다. java.lang.Exception 예외 클래스가 가장 먼저 검사되면 모든 예외는 첫 catch 구문에서 감지되며 이어지는 catch 구문은 절대 실행되지 않습니다.

try {

} catch (Exception e1) {

} catch (ArrayIndexOutOfBoundsException e2) {
    // Exception 'java.lang.ArrayIndexOutOfBoundsException' has already been caught
} catch (NumberFormatException e3) {
    // Exception 'java.lang.NumberFormatException' has already been caught
} finally {

}
  • catch 블록은 N개 이어붙여 사용 할 수 있습니다.
  • 예외는 동시에 하나만 발생하므로, 예외 클래스와 일치하는 하나의 catch 블록만 실행됩니다.
  • catch 블록은 반드시 하위 예외 클래스부터 감지되어야 합니다.

복합 catch

하나의 catch 블록은 여러 개의 예외 클래스를 동시에 감지 할 수 있습니다.

try {

} catch ([예외 클래스|예외 클래스|...|예외 클래스] [예외 변수 이름]) {

} finally {
   
}

 

각 예외 클래스는 구분자(|)를 사용하여 열거됩니다.

try {
   Integer.valueOf(1000);
   Integer.valueOf("abcd");
   Integer.valueOf(9876);
} catch (ArrayIndexOutOfBoundsException | NumberFormatException | ClassCastException e) {
   // 예외 처리 코드 실행
} finally {
   // 예외 처리문의 마지막에 항상 실행
}

throws

예외 처리의 또 다른 방법은 throws 키워드를 사용하는 것입니다. throws는 다음 목적으로 사용됩니다.

  • 메소드를 호출하는 다른 코드에서 예외 처리하도록 강요
  • 메소드에 발생할 여지가 있는 예외를 다른 코드에서 예최 처리하도록 강요

throws 키워드는 메소드 선언부의 마지막에 첨부됩니다.

[접근 지정자 public|protected|default|private] [리턴 타입] [함수 이름] ([매개 변수]) throws [예외 클래스] {
    // 메소드 블록
}

각 예외 클래스는 콤마(,) 구분자를 사용하여 열거 될 수 있습니다.

[접근 지정자 public|protected|default|private] [리턴 타입] [함수 이름] ([매개 변수]) throws [예외 클래스, 예외 클래스, ...] {
    // 메소드 블록
}

다음 예시는 java.lang.NullPointerException과 java.lang.NumberFormatException을 throws하고 있습니다.

public Integer[] parseStringsToIntegers(String ... _args) throws NullPointerException, NumberFormatException {
   Integer[] o = new Integer[_args.length];
   int i = 0;
   for (String s : _args) {
      o[i++] = Integer.parseInt(s);
   }
   
   return o;
}

이 메소드를 호출하는 코드는 메소드가 throws 하는 예외 클래스에 대한 예외 처리 코드가 포함되어야 합니다.

try {
   Integer[] o = parseStringsToIntegers("123", "abc");
} catch (NullPointerException | NumberFormatException  e) {
   e.printStackTrace();
}

호출 메소드(parseStringsToIntegers) 내부에서 예외가 발생하면, 이를 호출하는 메소드가 예외를 대신 감지하고 처리합니다.