4 분 소요

final, finally, finalize의 차이는 뭘까?

(틀린 게 있다면 편하게 댓글 달아주세요!)

final, finally, finalize 차이 정리

1. final

정의: “변경할 수 없다”는 뜻. 변수, 메서드, 클래스에 붙일 수 있어.

  • 변수에 사용: 초기화 후 값 변경 불가
final int x = 10;
x = 20; // 컴파일 에러
  • 메서드에 사용: 오버라이딩 방지
public final void doSomething() { }
  • 클래스에 사용: 상속 방지
public final class MyClass { }

2. finally

정의: try-catch-finally 블록에서 예외 발생 여부와 상관없이 반드시 실행되는 블록

  • 주로 리소스 정리, 닫기 용도로 사용
try {
    // 예외 발생 가능 코드
} catch (Exception e) {
    // 예외 처리
} finally {
    // 무조건 실행됨 (예외가 발생하든 말든)
}

3. finalize

  • Object 클래스의 protected 메서드
  • GC(Garbage Collector)가 객체를 메모리에서 제거하기 전에 호출되는 메서드
@Override
protected void finalize() throws Throwable {
    try {
        // 자원 정리 코드
    } finally {
        super.finalize();
    }
}

🧠 하지만 지금은 거의 쓰지 않음!

  • Java 9 이후로는 Deprecated(사용 권장하지 않음)
  • 대신 try-with-resources나 명시적인 자원 해제 사용 권장 (AutoCloseable)

🔍 추가 질문

Q1. final 키워드를 매개변수에 붙이는 이유는 뭘까? 언제 써야 해? Q2. try-finally만 쓰고 catch 없이 쓰는 건 어떤 상황에서 유용할까? Q3. finalize() 대신 요즘 자바에서 자원 정리를 권장하는 방식은 무엇일까? 예시 코드와 함께 설명해줘.

Q1

매개변수에 final을 붙이는 이유는 ‘변하면 안 되는 변수’ 인데 이게 무슨 소리냐면 메서드 안에서 그 매개변수 값을 바꾸지 못하도록 컴파일 시점에서 방지함 특히 익명 클래스나 람다 내부에서 외부 변수 참조시, final 혹은 사실상 final 인 변수만 참조할 수 있기 때문에 이런 상황에 유용함

public void foo(final int x) {
    // x = 10; // 컴파일 에러!
}

Q2

try-finally 는 무조건 실행해야하 할 작업이 있지만 예외는 신경쓰지 않는 상황 예를 들어 리소스 닫기(close) 작업은 예외가 있든 없든 무조건 해야함 fianlly 블록에 둠 -> catch 없이 try-finally 를 쓰면 예외는 상위로 던지면서도, finally 에서 리소스 정리는 보장 가능

Q3

자바 9부터 finalize() 는 deprecated 되었고 불확실한 타이밍에 호출되고 성능 저하문제도 있어서 안쓴다고 함

요즘 자원 정리 권장 방식은?

try-with-resources 문법과 AutoCloseable 인터페이스를 사용

예시:

try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    // br 사용
} catch (IOException e) {
    e.printStackTrace();
}
// try가 끝나면 br.close() 자동 호출
  • try-with-resources 블록을 벗어나면 자동으로 close() 호출
  • AutoCloseable을 구현한 객체만 사용 가능
  • 명시적으로 자원 해제를 보장하고, finalize()보다 훨씬 안정적이고 예측 가능

finalize()try-with-resources 비교

finalize()
  • 자바 객체가 가비지 컬렉터(GC)에 의해 메모리에서 삭제되기 전에 호출되는 메서드
  • 보통 객체가 소유한 외부 자원(파일, 네트워크 등)을 해제하는 데 사용했음
  • 단점: 호출 시점이 불확실하고, 호출 안 될 수도 있음. 성능 저하, 디버깅 어려움.
  • Java 9부터 Deprecated → 더 이상 사용 권장하지 않음.
    try-with-resources
  • 자바 7부터 도입된 문법으로, AutoCloseable 인터페이스를 구현한 객체의 자원을 자동으로 닫아주는 구조
  • try 블록이 끝나면 자동으로 close() 호출
  • 장점: 호출 시점이 명확하고, 예외가 발생해도 안전하게 자원 해제 가능
  • 코드가 간결해지고, 안전성 보장

AutoCloseable 인터페이스 직접 구현 예제

public class MyResource implements AutoCloseable {
    public MyResource() {
        System.out.println("Resource opened");
    }
    public void doSomething() {
        System.out.println("Doing something with resource");
    }
    @Override
    public void close() {
        System.out.println("Resource closed");
    }
}

public class Main {
    public static void main(String[] args) {
        try (MyResource r = new MyResource()) {
            r.doSomething();
        } // try 종료 시점에 자동으로 r.close() 호출
    }
}
실행결과
Resource opened
Doing something with resource
Resource closed

MyResourceAutoCloseable을 구현해서, try-with-resources 구문에서 자동으로 close()를 호출함.

정리
구분 finalize() try-with-resources
호출 시점 GC가 객체 제거 시 호출, 시점 불확실 try 종료 시점에 명확하게 호출
사용 여부 Java 9부터 deprecated 권장되는 자원 관리 방법
안정성 불안정, 예외 처리 어려움 안정적, 예외 발생해도 자원 해제 보장
코드 복잡도 복잡하고 디버깅 어려움 간결하고 명확

1. try-with-resources의 사용 시점과 상황

언제 사용하나?

  • 명시적으로 닫아야 하는 자원(Resource)이 있을 때 쓴다.
  • 대표적인 자원은 파일 입출력, 데이터베이스 연결, 네트워크 소켓, 스트림(Streams) 등이다.

왜 쓰나?

  • 자원을 열었으면 반드시 닫아야 한다.
  • 닫지 않으면 메모리 누수, 리소스 고갈, 데이터 손상 위험 발생
  • 예외가 발생해도 자원을 안전하게 닫아주기 위해서다.

예시 상황

  • 파일 읽기/쓰기할 때
  • DB 커넥션, 쿼리 실행 시
  • HTTP 클라이언트 소켓 다룰 때

2. finalize()의 사용 시점과 상황

언제 쓰였나?

  • 객체가 메모리에서 GC로 제거될 때 자원 해제를 위해 썼다.
  • 근데 호출 시점이 불확실하고 성능 문제로 지금은 쓰지 않는다.

    지금은?

  • 거의 사용 안 함!
  • 대신 try-with-resources 같은 명시적 자원 관리 권장
  • 만약 꼭 필요하면, java.lang.ref.Cleaner 같은 최신 대체 기술을 사용

3. Todo 앱 같은 백엔드에서의 적용

try-with-resources 적용 예

  • 파일 업로드/다운로드 기능을 만들 때
  • DB Connection, Statement, ResultSet 같은 리소스 관리
  • 예를 들어, 파일에 로그를 남기거나, 임시 파일을 만들고 자동으로 닫거나 삭제할 때

💡 상황:

사용자가 완료한 Todo 리스트를 CSV 파일로 저장해서 다운로드하는 기능이 있다고 가정


✅ 목표:

  • 완료된 Todo 항목을 List<Todo>에서 가져옴
  • CSV 파일로 저장함
  • try-with-resourcesFileWriter를 안전하게 닫음

📦 예제 코드 (Spring Boot 환경 가정)

import java.io.FileWriter;
import java.io.IOException;
import java.util.List;

public class TodoExporter {

    public void exportToCSV(List<Todo> todos, String filePath) {
        try (FileWriter writer = new FileWriter(filePath)) {
            writer.write("id,title,completed\n");

            for (Todo todo : todos) {
                writer.write(String.format("%d,%s,%b\n",
                        todo.getId(),
                        todo.getTitle(),
                        todo.isCompleted()));
            }

            System.out.println("CSV 파일이 저장되었습니다: " + filePath);
        } catch (IOException e) {
            System.err.println("파일 저장 중 오류 발생: " + e.getMessage());
        }
        // 여기서 writer는 자동으로 close됨!
    }
}

🧾 Todo 클래스 예시

public class Todo {
    private Long id;
    private String title;
    private boolean completed;

    // 생성자, getter, setter 생략
}

🔍 설명

  • FileWriter는 외부 리소스(파일 핸들)를 사용하는 클래스다.
  • 이걸 닫지 않으면 파일이 잠기거나, 데이터 유실, 리소스 누수가 생길 수 있음.
  • 그래서 try-with-resourcesFileWriter를 생성하면, try 블록이 끝나는 순간 close()가 자동 호출됨.

📌 실제 Todo 앱에선 어디에 쓸 수 있을까?

  • 유저가 Todo 리스트를 다운로드할 때
  • 작업 로그를 파일로 남길 때 (ex. 백업, 감사 로그 등)
  • 스프링 JdbcTemplate, BufferedReader, ZipOutputStream, InputStream, OutputStream 같은 것들 다 해당됨

댓글남기기