예외(exception)는 프로그램이 도는 중에 생겨 정상 흐름을 이어 갈 수 없게 하는 일이고, 예외 처리(exception handling)는 그때 실행할 코드를 미리 적어 두어 프로그램이 멈추지 않고 이어 가게 하는 것입니다. Java는 try·catch·finally로, Python은 try·except·finally로 적습니다.
0으로 나누기, 배열 범위 밖의 칸 읽기, 숫자가 아닌 글자를 수로 바꾸기는 문법이 맞아도 실행할 때 일어납니다. 처리하지 않으면 그 자리에서 프로그램이 멈추고, 아래 줄은 하나도 실행되지 않습니다. 아래 그림은 0으로 나누는 순간 흐름이 catch로 뛰고, finally를 거쳐 다음 줄로 이어지는 장면입니다.
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-1f(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");
}
}
정답
받는 답:
BCa의 번호는 0과 1뿐이라 a[2]에서 ArrayIndexOutOfBoundsException이 던져집니다. 첫 catch의 ArithmeticException과는 종류가 달라 건너뛰고, 모든 예외를 받는 둘째 catch가 B를 찍습니다. 예외를 처리했으니 C까지 찍습니다.
- 틀리는 꼴 AC: 첫 catch가 무엇이든 받는다고 봤습니다. 괄호 안 종류에 드는 예외만 받습니다.
- 틀리는 꼴 ABC: 맞는 catch를 모두 실행한다고 봤습니다. 위에서부터 맞는 첫 하나만 실행합니다.
- 틀리는 꼴 C: 받을 catch가 없다고 봤습니다. Exception은 모든 예외를 받습니다.