1. finally 블록 소개
파일, 네트워크 연결, 데이터베이스 같은 자원을 사용할 때 — 작업 중 오류가 발생하더라도 자원이 항상 닫히거나 해제되도록 보장하는 것이 중요합니다. Java 에서는 이를 위한 전용 블록인 finally 가 있습니다.
finally 는 어떻게 동작하나요?
finally 블록은 try-catch-finally 구성의 일부입니다. finally 안의 코드는 항상 실행됩니다 (작성되어 있다면) — 예외 발생 여부와 무관하게 실행됩니다. 심지어 try 안에 return 이 있거나 예외가 던져져도 — finally 는 여전히 실행됩니다(컴퓨터 전원이 꺼지거나 System.exit(0) 로 프로그램을 강제 종료한 경우는 제외).
문법:
try {
// 예외가 발생할 수 있는 코드
} catch (ExceptionType e) {
// 오류 처리
} finally {
// 이 코드는 항상 실행됩니다!
}
예시
try {
System.out.println("작업 시작");
int result = 10 / 0; // 여기서 오류가 발생함
System.out.println("결과: " + result);
} catch (ArithmeticException e) {
System.out.println("오류: 0으로 나눔");
} finally {
System.out.println("이 코드는 어떤 경우에도 실행됩니다");
}
실행 결과:
작업 시작
오류: 0으로 나눔
이 코드는 어떤 경우에도 실행됩니다
무슨 일이 일어나나요?
- try 에서 두 수를 나누려다 오류가 발생합니다.
- 나누기 중 오류가 발생하면 — catch 가 이를 처리합니다.
- 하지만! 어떤 경우든 finally 가 실행되어 콘솔에 메시지를 출력합니다.
2. catch 없이 finally
구성에는 3가지 변형이 있습니다:
- 완전한 형태: try-catch-finally
- finally 없음: try-catch
- catch 없음: try-finally
세 번째 형태는 오류를 잡고 처리하는 역할을 상위 메서드에 맡기고자 할 때 사용합니다. 하지만 finally 블록은 특정 코드를 반드시 실행하기 위해 필요합니다:
- 파일, 네트워크 연결, 데이터베이스 닫기.
- 어떤 자원이든 해제(예: 락 해제).
- 로깅: 작업 종료 정보 기록.
예시:
try {
System.out.println("수를 나눕니다");
int result = 10 / 0; // 오류!
System.out.println("결과: " + result);
} finally {
System.out.println("finally 블록이 실행됨");
}
결과:
수를 나눕니다
finally 블록이 실행됨
Exception in thread "main" java.lang.ArithmeticException: / by zero
finally 가 실행되지 않는 경우?
거의 항상 실행됩니다. 예외는 다음과 같습니다:
- System.exit(0) 로 프로그램을 강제 종료한 경우.
- finally 가 실행되는 스레드를 강제로 종료한 경우.
- 컴퓨터 전원이 꺼진 경우.
3. throw 연산자: 예외를 직접 발생시키기
때로는 Java 가 스스로 예외를 “던집니다”(예: 0으로 나누기, 배열 범위 초과). 하지만 개발자가 명시적으로 “이건 오류다! 더는 실행할 수 없다!”라고 알려야 하는 경우도 있습니다. 이를 위해 Java 에는 throw 연산자가 있습니다.
비유: 가게에서 유통기한이 지난 상품을 보면 신고를 “던지듯” 제기하겠죠. 코드에서도 마찬가지로, 문제가 있으면 예외를 던집니다.
throw 문법
throw new ExceptionType("오류 메시지");
ExceptionType 은 Throwable 을 상속하는 어떤 클래스든 가능합니다(보통 Exception 또는 RuntimeException). 괄호 안의 메시지는 무엇이 잘못되었는지 이해하는 데 도움을 줍니다.
예시: 메서드 인자 검증
public static int safeDivide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("분모는 0이 될 수 없습니다");
}
return a / b;
}
사용법:
public static void main(String[] args) {
try {
int result = safeDivide(10, 0);
System.out.println("결과: " + result);
} catch (IllegalArgumentException e) {
System.out.println("오류: " + e.getMessage());
}
}
결과:
오류: 분모는 0이 될 수 없습니다
throw 를 언제 사용하나요?
- 메서드 인자 검증(예: null 이 오거나 잘못된 데이터인 경우).
- 객체 상태 검증(예: 잔액이 0유로인 계좌에서 출금 시도).
- catch 내부 — 예외를 더 멀리 “다시 던지고”(rethrow) 싶을 때(예: 추가 정보를 부가).
4. try-catch-finally 와 throw 의 결합
이 구성들이 함께 동작할 때가 있습니다. 예를 들어, 하나의 예외를 잡은 뒤 더 의미 있는 예외를 새로 던질 수 있습니다.
public static int parseAndDivide(String text, int divisor) {
try {
int number = Integer.parseInt(text);
if (divisor == 0) {
throw new IllegalArgumentException("분모는 0이 될 수 없습니다");
}
return number / divisor;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("문자열 '" + text + "' 은 숫자가 아닙니다");
} finally {
System.out.println("문자열 처리 시도: " + text);
}
}
사용법:
try {
int result = parseAndDivide("42a", 2);
System.out.println("결과: " + result);
} catch (IllegalArgumentException e) {
System.out.println("오류: " + e.getMessage());
}
결과:
문자열 처리 시도: 42a
오류: 문자열 '42a' 은 숫자가 아닙니다
중요한 포인트: return 과 finally
try 블록에 return 이 있어도 finally 는 여전히 실행됩니다!
public static int getValue() {
try {
return 10;
} finally {
System.out.println("finally 는 어쨌든 실행됩니다!");
}
}
getValue() 호출 시 출력:
finally 는 어쨌든 실행됩니다!
5. finally 와 throw 사용 시 전형적인 실수
실수 №1: finally 없이 자원을 닫지 않음.
아주 흔한 문제입니다. 파일을 열어 놓고 닫지 않으면 자원 누수가 발생합니다. 항상 finally(또는 나중에 다룰 try-with-resources) 를 사용하세요.
실수 №2: 예외를 던졌지만 처리하지 않음.
throw 로 예외를 던지고 어디에서도 잡지 않으면(try-catch 없음) 프로그램은 비정상 종료됩니다. 여러분이 던진 예외를 누가 잡을지 항상 고민해야 합니다.
실수 №3: finally 안의 return.
실수로 finally 내부에 return 을 작성하면, 그 반환이 이전의 모든 return 이나 throw 를 “덮어써” 버립니다. 이는 매우 찾기 어려운 버그를 유발할 수 있으므로 절대 권장되지 않습니다!
public int tricky() {
try {
return 1;
} finally {
return 2; // 위험: 1이 아니라 2가 반환됩니다!
}
}
결과: try 에서 1 이었지만 2 가 반환됩니다.
실수 №4: 예외 정보 손실.
하나의 예외를 잡은 뒤 새로운 예외를 던지면서 기존 예외(e) 정보를 전달하지 않으면 호출 스택을 잃게 되어 디버깅이 어려워집니다. 다음처럼 작성하는 것이 좋습니다:
catch (NumberFormatException e) {
throw new IllegalArgumentException("변환 오류", e);
}
GO TO FULL VERSION