본문으로 건너뛰기

예외 처리

정보처리기사 목차언어의 특성과 라이브러리레슨 149 / 233

목차

예외(exception)는 프로그램이 도는 중에 생겨 정상 흐름을 이어 갈 수 없게 하는 일이고, 예외 처리(exception handling)는 그때 실행할 코드를 미리 적어 두어 프로그램이 멈추지 않고 이어 가게 하는 것입니다. Java는 try·catch·finally로, Python은 try·except·finally로 적습니다.

0으로 나누기, 배열 범위 밖의 칸 읽기, 숫자가 아닌 글자를 수로 바꾸기는 문법이 맞아도 실행할 때 일어납니다. 처리하지 않으면 그 자리에서 프로그램이 멈추고, 아래 줄은 하나도 실행되지 않습니다. 아래 그림은 0으로 나누는 순간 흐름이 catch로 뛰고, finally를 거쳐 다음 줄로 이어지는 장면입니다.

8단계 중 1단계. 4~6번 줄 — x에 0을 넣고 try 안에서 10 / x를 계산합니다. 정수는 0으로 나눌 수 없어 ArithmeticException 예외가 던져지고, 6번 줄은 아무것도 찍지 못합니다.

1try에서 예외가 나면 catch로 뛴다

예외가 날 수 있는 문장은 try로 감쌉니다. 그림의 코드입니다. 폰 화면에 맞추려고 main 머리를 두 줄로 나눴습니다. 한 줄로 쓴 것과 같습니다.

public class Main {
  public static void main(
      String[] args) {
    int x = 0;
    try {
      System.out.print(10 / x);
      System.out.print("A");
    } catch (Exception e) {
      System.out.print("B");
    } finally {
      System.out.print("C");
    }
    System.out.println("D");
  }
}
실행 결과
BCD
  • 6번 줄에서 ArithmeticException이 던져지면, 그 아래 try 안 줄(7번 줄 A)은 건너뜁니다.
  • catch는 던져진 예외가 괄호 안 종류에 드는지 견줍니다. Exception은 모든 예외를 품어 무엇이든 받습니다.
  • finally는 예외가 났든 안 났든 마지막에 실행됩니다. 예외가 나지 않으면 catch만 건너뜁니다.
  • 예외를 처리했으니 멈추지 않고 try 문 다음 13번 줄로 이어집니다. 흐름이 뛰는 모양은 절차형 SQL의 오류 처리의 EXCEPTION 블록과 같습니다.

2catch는 위에서부터 하나만: 예외의 종류

자주 나오는 Java 예외 넷입니다. 모두 Exception 아래라 catch (Exception e)가 다 받습니다.

  • ArithmeticException — 정수를 0으로 나눌 때(10 / 0)
  • ArrayIndexOutOfBoundsException — 배열 번호가 범위 밖일 때(길이 3인 배열의 a[3])
  • NullPointerException — null인 참조로 메서드·필드를 쓸 때(s가 null인데 s.length())
  • NumberFormatException — 수로 바꿀 수 없는 글자를 바꿀 때(Integer.parseInt("12a"))

catch가 여럿이면 위에서부터 견주어 맞는 첫 하나만 실행합니다. 아래 catch는 맞아도 건너뜁니다. 그래서 좁은 종류를 위에, Exception을 맨 아래에 둡니다. Exception을 위에 두면 아래 catch에 닿을 수 없어 컴파일 오류가 납니다.

throw new 예외 종류()는 예외를 일부러 던집니다. 메서드 머리의 throws 예외 종류는 그 메서드가 예외를 부른 쪽으로 넘길 수 있다고 적는 자리입니다.

3Python의 try·except·finally

Python은 catch 대신 except, throw 대신 raise를 씁니다. 예외가 나지 않았을 때만 실행되는 else를 except 뒤에 더 둘 수 있습니다. except가 받는 종류와 난 예외가 맞지 않으면 어떻게 될까요?

text = '칠'
try:
    n = int(text)
except ZeroDivisionError:
    print('0으로 나눔')
finally:
    print('끝')
print('다음')
실행 결과
끝Traceback (most recent call last):  File "main.py", line 3, in <module>    n = int(text)ValueError: invalid literal for int() with base 10: '칠'
  • int('칠')이 던진 ValueError는 except가 받는 ZeroDivisionError와 달라 처리되지 않습니다.
  • 그래도 finally는 '끝'을 찍고, 그다음 예외가 프로그램을 멈춰 8번 줄의 '다음'은 찍히지 않습니다.
  • Java도 같습니다. 맞는 catch가 없으면 finally를 실행한 뒤 멈춥니다.

예제

메서드 안의 try에서 return을 만나도 finally는 실행됩니다. 무엇이 찍힐까요?

public class Main {
  static int f(int x) {
    try {
      return 10 / x;
    } catch (Exception e) {
      return -1;
    } finally {
      System.out.print("F");
    }
  }
  public static void main(
      String[] args) {
    System.out.println(f(0));
  }
}
실행 결과 보기
실행 결과
F-1

f(0)에서 10 / 0이 예외를 던져 catch의 return -1로 갑니다. 돌려줄 값 -1을 정해 두고 메서드를 떠나기 전에 finally가 F를 찍습니다. 그다음 main의 println이 돌려받은 -1을 찍어 F가 -1보다 앞에 섭니다.

시험 포인트

  • 필기: 처리 구조는 try(예외가 날 수 있는 문장) → catch(맞는 예외 처리) → finally(늘 실행)입니다. 예외가 나면 try의 남은 줄은 건너뛰고, catch는 위에서부터 맞는 하나만 실행하며, 모든 예외를 받는 Exception은 맨 아래에 둡니다. 예외 이름과 나는 때를 짝짓습니다 — ArithmeticException(0으로 나눔) · ArrayIndexOutOfBoundsException(배열 범위 밖) · NullPointerException(null 참조) · NumberFormatException(수로 못 바꾸는 글자). Python은 except·else·raise를 씁니다.
  • 실기: 출력을 글자 그대로 씁니다(Java 예외 처리 출제 주제). finally는 return이 있어도 실행되고, 맞는 catch가 없으면 finally를 실행한 뒤 멈춥니다. 빈칸 채우기로 묻는 Java 키워드는 try·catch·finally·throw입니다.

확인 문제

1. Java에서 NumberFormatException이 나는 코드를 고르세요.

  • ① 길이 2인 배열 b에 b[-1] = 0;
  • ② Integer.parseInt("3.5");
  • ③ t가 null인데 t.charAt(0);
  • ④ z가 0인데 int r = 5 % z;
정답

②입니다. parseInt는 정수 꼴의 글자만 수로 바꿉니다. "3.5"는 소수점이 있어 정수로 바꿀 수 없습니다.

  • ① 배열 번호는 0부터라 -1은 범위 밖입니다. ArrayIndexOutOfBoundsException이 납니다.
  • ③ null인 참조로 메서드를 불렀습니다. NullPointerException이 납니다.
  • ④ 나머지 연산도 0으로 나누면 ArithmeticException이 납니다.

2. 다음 (가)~(라)에 들어갈 Java 키워드를 쓰세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.

  • (가) 예외가 날 수 있는 문장들을 감싸는 블록입니다. 이 블록에서 예외가 나면 남은 문장을 건너뜁니다.
  • (나) 괄호 안에 받을 예외의 종류를 적고, 그 종류의 예외가 던져졌을 때만 실행되는 블록입니다.
  • (다) 예외가 났든 안 났든, return으로 메서드를 떠나는 중이든 마지막에 실행되는 블록입니다. 파일 닫기 같은 마무리를 둡니다.
  • (라) 나이가 음수면 ___ new Exception();처럼 예외를 일부러 던집니다.
정답

받는 답: (가) try · (나) catch · (다) finally · (라) throw

(가)의 실마리는 '감싸고, 예외가 나면 남은 문장을 건너뛴다', (나)는 '괄호 안 종류', (다)는 '났든 안 났든 마지막', (라)는 '일부러 던진다'입니다.

  • 같은 이름의 다른 꼴: (나) except · (라) raise — Python의 키워드입니다. 문제가 Java 키워드를 물었습니다.
  • 틀리는 꼴 catch — (다)에: 맞는 예외가 던져졌을 때만 실행됩니다. 예외가 없으면 건너뜁니다.
  • 틀리는 꼴 throws — (라)에: 메서드 머리에 '이 메서드가 넘길 수 있는 예외'를 적는 키워드입니다. 문장 안에서 예외를 던지는 것은 throw입니다.

3. 1절 코드에서 4번 줄만 int x = 5;로 바꿨습니다. 실행 결과를 그대로 쓰세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.

정답

받는 답: 2ACD

10 / 5는 2라 예외가 나지 않습니다. 6번 줄이 2, 7번 줄이 A를 찍고, catch는 건너뜁니다. finally는 예외가 없어도 실행되어 C를 찍고, 13번 줄이 D를 찍습니다.

  • 틀리는 꼴 2AD: finally를 예외가 날 때만 실행된다고 봤습니다.
  • 틀리는 꼴 2ABCD: catch도 늘 실행된다고 봤습니다. catch는 맞는 예외가 던져졌을 때만 실행됩니다.

4. 다음 코드의 실행 결과를 그대로 쓰세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.

public class Main {
  public static void main(
      String[] args) {
    int[] a = {3, 0};
    try {
      System.out.print(a[2]);
    } catch (
        ArithmeticException e) {
      System.out.print("A");
    } catch (Exception e) {
      System.out.print("B");
    }
    System.out.println("C");
  }
}
정답

받는 답:

실행 결과
BC

a의 번호는 0과 1뿐이라 a[2]에서 ArrayIndexOutOfBoundsException이 던져집니다. 첫 catch의 ArithmeticException과는 종류가 달라 건너뛰고, 모든 예외를 받는 둘째 catch가 B를 찍습니다. 예외를 처리했으니 C까지 찍습니다.

  • 틀리는 꼴 AC: 첫 catch가 무엇이든 받는다고 봤습니다. 괄호 안 종류에 드는 예외만 받습니다.
  • 틀리는 꼴 ABC: 맞는 catch를 모두 실행한다고 봤습니다. 위에서부터 맞는 첫 하나만 실행합니다.
  • 틀리는 꼴 C: 받을 catch가 없다고 봤습니다. Exception은 모든 예외를 받습니다.

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

정보처리기사 목차로