|

자바 final, finally, finalize 차이

자바 final finally finalize 차이를 설명하는 대표 이미지
이름은 비슷하지만 final, finally, finalize는 서로 다른 층위의 개념이다

자바 final finally finalize 차이는 이름이 비슷해서 초보자가 특히 자주 헷갈리는 주제입니다. 하지만 final은 언어 키워드, finally는 예외 처리 블록, finalize()는 GC와 연결된 오래된 정리 메서드입니다. 같은 종류가 아니라는 점부터 잡으면 훨씬 덜 헷갈립니다.

이번 글에서는 왜 셋이 자주 섞이는지부터 짚고, final의 실제 의미, finally와 try-with-resources의 관계, finalize()가 왜 현대 자바에서 피해야 할 레거시 개념이 되었는지까지 실무 기준으로 정리하겠습니다.


왜 이렇게 자주 헷갈릴까

헷갈리는 이유는 단순합니다. 셋 다 final로 시작하고, 입문서에서 비슷한 시기에 만나고, 모두 “마지막 정리” 같은 느낌을 주기 때문입니다. 그래서 많은 사람이 한 묶음 문법처럼 외워 버립니다.

  • final은 선언에 붙는 키워드다
  • finally는 try-catch와 함께 쓰는 블록이다
  • finalize()는 Object에 있던 메서드다

즉, 같은 층위의 개념이 아닙니다. 언어 키워드 / 예외 처리 블록 / GC 기반 레거시 메서드로 나눠 기억하는 편이 훨씬 안전합니다.


자바 final finally finalize 차이 먼저 한 장으로 구분하기

  • final → 바꾸지 못하게 하거나 확장을 막는 쪽
  • finally → 예외가 나든 안 나든 마지막 정리 코드를 두는 쪽
  • finalize() → 객체가 사라질 때 언젠가 정리되길 기대하던 옛 방식

실무 감각으로 번역하면 더 단순합니다. final은 지금도 자주 쓰고, finally는 개념은 중요하지만 자원 정리는 try-with-resources가 더 자주 보이며, finalize()는 새 코드에서 일부러 쓰지 않는다고 보면 됩니다.


final은 자바 언어 키워드다

final은 선언에 붙는 키워드입니다. 무언가를 다시 바꾸지 못하게 하거나, 확장을 막는 데 쓰입니다. 대상이 변수인지, 메서드인지, 클래스인지에 따라 의미가 조금씩 달라집니다.

final 변수

변수에 final을 붙이면 한 번만 대입할 수 있습니다. 기본형에서는 “값을 다시 못 바꾼다”로 이해하면 거의 맞습니다.

final int maxRetry = 3;
// maxRetry = 5; // 컴파일 오류

하지만 참조형에서는 오해가 생기기 쉽습니다. 참조를 다시 바꾸지 못한다는 뜻이지, 객체 내부 상태까지 모두 얼어붙는다는 뜻은 아닙니다.

import java.util.ArrayList;
import java.util.List;

final List<String> names = new ArrayList<>();
names.add("Java");
names.add("Spring");

// names = new ArrayList<>(); // 컴파일 오류

final 참조와 immutable 객체는 같은 말이 아닙니다. 이 차이를 놓치면 final을 과하게 단순화하게 됩니다.

final 메서드와 final 클래스

메서드에 붙으면 override를 막고, 클래스에 붙으면 상속을 막습니다. 즉, 설계자가 “여기까지는 열어 두지 않겠다”는 뜻을 문법으로 표현하는 셈입니다.

class PaymentService {
    final void validate() {
        System.out.println("validate payment");
    }
}

final class ApiKey {
    private final String value;

    ApiKey(String value) {
        this.value = value;
    }
}

정리하면 final은 자바 문법 차원에서 재할당 금지, 재정의 금지, 상속 금지를 표현하는 키워드입니다.


finally는 예외 처리 흐름에서 쓰는 블록이다

finally는 변수나 메서드에 붙는 것이 아닙니다. try-catch와 함께 쓰며, 예외가 나더라도 마지막에 실행해야 하는 정리 코드를 두는 블록입니다.

try {
    System.out.println("파일 작업 시작");
    return;
} finally {
    System.out.println("정리 작업 실행");
}

이 예시처럼 return이 있어도 정리 코드를 건너뛰지 않게 만드는 것이 finally의 핵심입니다. 예전에는 파일 닫기, 스트림 닫기 같은 자원 정리에 finally를 많이 썼습니다.

요즘은 왜 try-with-resources를 더 많이 볼까

여기서 중요한 실무 판단이 나옵니다. finally 개념은 여전히 중요하지만, 파일이나 소켓처럼 close()가 필요한 자원 정리는 오늘 기준으로 try-with-resources가 더 자연스럽습니다. Oracle 튜토리얼도 자원 정리에는 finally보다 try-with-resources를 권장합니다.

try (java.io.BufferedReader reader =
         new java.io.BufferedReader(new java.io.FileReader("data.txt"))) {
    System.out.println(reader.readLine());
}

AutoCloseable을 구현한 자원은 블록을 벗어날 때 자동으로 close()가 호출됩니다. 그래서 finally는 예외 처리 개념으로 이해하되, 자원 정리 기본 패턴은 try-with-resources로 기억하는 편이 현대 자바에 더 맞습니다.

참고로 입문 설명에서 “finally는 무조건 항상 실행된다”라고 외우기도 하지만, JVM 강제 종료 같은 특수 상황은 예외가 될 수 있습니다. 그래서 안전한 표현은 “보통 try를 빠져나갈 때 정리 코드를 실행하도록 하는 블록”입니다.


finalize()는 오래된 정리 방식이고, 지금은 피해야 한다

finalize()try-catch-finally 문법의 일부가 아닙니다. Object 클래스에 있던 메서드이고, 객체가 더 이상 쓰이지 않을 때 가비지 컬렉터가 언젠가 호출할 수도 있다고 기대하던 방식이었습니다.

문제는 그 “언젠가”가 너무 불확실하다는 점입니다. OpenJDK의 JEP 421은 finalization의 문제로 예측 불가능한 지연, 실행 보장 부재, 성능과 신뢰성 문제, 객체 resurrection 가능성 등을 설명합니다.

Java SE 21의 Object API에서도 finalize()Deprecated, for removal 상태입니다. 즉, 새 코드의 정상적인 자원 정리 수단으로 보면 안 됩니다.

왜 finalize()에 자원 정리를 맡기면 안 될까

파일, 소켓, DB 연결 같은 자원은 GC가 메모리를 치운다고 바로 안전하게 정리되는 문제가 아닙니다. 운영체제 자원은 필요한 시점에 명시적으로 닫아야 하는데, finalize()는 호출 시점이 예측되지 않아서 믿을 수 있는 안전망이 되지 못했습니다.

  • 자원은 직접 close() 한다
  • 가능하면 try-with-resources를 쓴다
  • 특수한 장기 자원 정리 문제는 Cleaner 같은 대안을 검토할 수 있다
  • finalize()에는 의존하지 않는다

레거시 코드에서 finalize()를 만나면 “과거 자원 정리 관습의 흔적”으로 이해하면 됩니다. 하지만 지금 새 코드를 쓸 때는 따라 하지 않는 편이 맞습니다.


세 용어를 한 번에 비교하면 이렇게 보인다

  • final: 키워드 / 변수, 메서드, 클래스 선언 / 재할당, override, 상속 제한
  • finally: 블록 / try-catch 뒤 / 마지막 정리 코드 실행
  • finalize(): 메서드 / Object 계열 / 과거의 GC 기반 정리 훅, 현재는 deprecated for removal

이 비교만 머리에 들어와도 이름 때문에 생기는 혼동은 거의 사라집니다. 셋은 비슷한 문법 패밀리가 아니라, 우연히 이름이 닮은 서로 다른 개념입니다.


실전에서는 이렇게 기억하면 충분하다

  1. final은 지금도 아주 자주 쓴다
  2. finally는 개념을 이해하되, 자원 정리는 try-with-resources를 먼저 본다
  3. finalize()는 알아는 두되 새 코드에서는 쓰지 않는다

면접용 정의를 길게 외우기보다 이 세 줄이 훨씬 오래 갑니다. 특히 finalize()를 finally의 메서드 버전처럼 오해하지 않는 것이 중요합니다.


자주 하는 오해

  • final과 finally를 같은 계열 문법으로 보는 오해
  • finally와 finalize()를 같은 정리 기능으로 보는 오해
  • final이 붙으면 무조건 immutable이라고 보는 오해

이 세 가지 오해만 조심해도 final, finally, finalize를 거의 틀리지 않게 됩니다. 특히 참조형 final과 immutable을 같은 말로 묶는 습관은 초반에 바로잡는 편이 좋습니다.


정리

자바에서 final, finally, finalize()는 이름만 비슷할 뿐 역할은 완전히 다릅니다. final은 언어 키워드, finally는 예외 처리 흐름의 정리 블록, finalize()는 GC 기반의 오래된 정리 메서드입니다.

실무 기준으로 더 짧게 줄이면 이렇습니다. final은 자주 쓴다, finally는 이해해야 한다, finalize()는 새 코드에서 피한다. 이 기준이면 대부분의 혼동은 정리됩니다.


같이 보면 좋은 글

자바 static과 final 차이: 함께 자주 나오지만 역할이 다른 문법 정리
자바 ==와 equals 차이: 문자열 비교에서 가장 많이 틀리는 문법 정리
자바 오버로딩과 오버라이딩 차이: 면접용 암기보다 중요한 실제 코드 판단 기준

함께보면 좋은 글