CodeGym /행동 /JAVA 25 SELF /finally 와 throw: 정리와 예외 발생

finally 와 throw: 정리와 예외 발생

JAVA 25 SELF
레벨 11 , 레슨 3
사용 가능

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으로 나눔
이 코드는 어떤 경우에도 실행됩니다

무슨 일이 일어나나요?

  1. try 에서 두 수를 나누려다 오류가 발생합니다.
  2. 나누기 중 오류가 발생하면 — catch 가 이를 처리합니다.
  3. 하지만! 어떤 경우든 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 가 실행되지 않는 경우?

거의 항상 실행됩니다. 예외는 다음과 같습니다:

  1. System.exit(0) 로 프로그램을 강제 종료한 경우.
  2. finally 가 실행되는 스레드를 강제로 종료한 경우.
  3. 컴퓨터 전원이 꺼진 경우.

3. throw 연산자: 예외를 직접 발생시키기

때로는 Java 가 스스로 예외를 “던집니다”(예: 0으로 나누기, 배열 범위 초과). 하지만 개발자가 명시적으로 “이건 오류다! 더는 실행할 수 없다!”라고 알려야 하는 경우도 있습니다. 이를 위해 Java 에는 throw 연산자가 있습니다.

비유: 가게에서 유통기한이 지난 상품을 보면 신고를 “던지듯” 제기하겠죠. 코드에서도 마찬가지로, 문제가 있으면 예외를 던집니다.

throw 문법

throw new ExceptionType("오류 메시지");

ExceptionTypeThrowable 을 상속하는 어떤 클래스든 가능합니다(보통 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);
}
1
과제
JAVA 25 SELF, 레벨 11, 레슨 3
잠금
청소 로봇과 완료 보고서 🧹
청소 로봇과 완료 보고서 🧹
1
과제
JAVA 25 SELF, 레벨 11, 레슨 3
잠금
점수 집계 시스템: 양수만 허용! 🏅
점수 집계 시스템: 양수만 허용! 🏅
1
과제
JAVA 25 SELF, 레벨 11, 레슨 3
잠금
핵심 모듈 및 보고를 동반한 비정상 종료 💥
핵심 모듈 및 보고를 동반한 비정상 종료 💥
1
과제
JAVA 25 SELF, 레벨 11, 레슨 3
잠금
회원 등록 시스템에서 사용자 이름 검증 📝
회원 등록 시스템에서 사용자 이름 검증 📝
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION