상세 컨텐츠

본문 제목

Effective Java 2/E 스터디 5일차

SW/미분류

by 푸로그 2019. 7. 5. 00:09

본문

7월 5일 2장 객체의 생성과 소멸

2장 객체의 생성과 삭제 

규칙 1 생성자 대신 정적 팩터리 메서드를 사용할 수 없는지 생각해 보라 
규칙 2 생성자 인자가 많을 때는 Builder 패턴 적용을 고려하라. 
규칙 3 private 생성자나 enum 자료형은 싱글턴 패턴을 따르도록 설계하라 
규칙 4 객체 생성을 막을 때는 private 생성자를 사용하라 
규칙 5 불필요한 객체는 만들지 말라 
규칙 6 유효기간이 지난 객체 참조는 폐기하라 
규칙 7 종료자 사용을 피하라 

객체의 생성과 삭제에 대해 살펴본다.
객체를 만들어야 하는 시점, 생성을 피해야 하는 경우와 그방법, 적절한 순간에 객체가 삭제되도록 보장하는 방법, 삭제 전 반드시 이루어져야 하는 청소작업들을 관리하는 방법을 살펴본다.

규칙 1 생성자 대신 정적 팩터리 메서드를 사용할 수 없는지 생각해 보라

클래스를 통해 객체를 만드는 일반적인 방법은 public으로 선언된 생성자를 이용하는 것이다. 그러나 모든 프로그래머가 반드시 알고 있어야 하는 방법이 하나 더 있다. 클래스에 public으로 선언된 정적 팩터리 메서드를 추가하는 것이다.

클래스를 정의할 때 생성자 대신(아니면 생성자와는 별도로) 정적 팩터리 메서드를 제공할 수 있다. public으로 선언된 생성자 대신 정적 팩터리 메서드를 제공하는 방법의 장단점은 다음과 같다.

첫 번째 장점은, 생성자와는 달리 정적 팩터리 메서드에는 이름이 있다는 것이다.

  • 클래스에는 시그니처별로 하나의 생성자만 넣을 수 있다. 이 제약을 피하는 한 가지 방법은 인자의 순서를 바꾸는 것이다. 하지만 이 방법은 정말로 끔찍하다.

정적 팩터리 메서드를 쓰는 두 번째 장점은, 생성자와는 달리 호출할 때마다 새로운 객체를 생성할 필요는 없다는 것이다.

정적 팩터리 메서드의 세 번째 장점은, 생성자와는 달리 반환값 자료형의 하위 자료형 객체를 반환할 수 있다는 것이다.

정적 팩터리 메서드만 있는 클래스만 만들면 생기는 가장 큰 문제는, public이나 protected로 선언된 생성자가 없으므로 하위 클래스를 만들 수 없다는 것이다.

두 번째 단점은 정적 팩터리 메서드가 다른 정적 메서드와 확연히 구분되지 않는다는 것이다.
클래스나 인터페이스 주석을 통해 정적 팩터리 메서드임을 널리 알리거나, 정적 팩터리 메서드 이름을 지을 때 조심하는 수밖에 없다. 보통 정적 팩터리 메서드의 이름으로는 다음과 같은 것들을 사용한다.

* valueOf : 인자로 주어진 값과 값은 값을 갖는 객체를 반환한다는 뜻이다. 따라서 이런 정적 팩터리 메서드는 형변환 메서드이다.
* of : valueOf를 더 간단하게 쓴 것이다. EnumSet 덕분에(규칙 32) 인기를 모은 이름이다.
* getInstance : 인자에 기술된 객체를 반환하지만, 인자와 같은 값을 갖지 않을 수도 있다. 싱글턴 패턴을 따를 경우, 이 메서드는 인자 없이 항상 같은 객체를 반환한다.
* newInstance : getInstance와 같지만 호출할 때마다 다른 객체를 반환한다.
* getType : getInstance와 같지만, 반환된 객체의 클래스와 다른 클래스에 팩터리 메서드가 있을 때 사용한다. Type은 팩터리 메서드가 반환할 객체의 자료형이다.
* newType : newInstance와 같지만, 반환될 객체의 클래스와 다른 클래스에 팩터리 메서드가 있을 때 사용한다. Type은 팩터리 메서드가 반환할 객체의 자료형이다.

규칙 2 생성자 인자가 많을 때는 Builder 패턴 적용을 고려하라.

정적 팩터리나 생성자는 같은 문제를 갖고 있다. 선택적 인자가 많은 상황에 잘 적응하지 못한다는 것이다.

포장 판매되는 음식에 붙어있는 영양 성분표를 나타내는 클래스를 예로 들어보자. 이 성분표에 반드시 포함되어야 하는 항목은 몇 가지 되지 않는다. 총 제공량, 1회 제공량, 1회 제공량당 칼로리 등이 그런 항목이다. 그러나 선택적인 항목은 무려 20개가 넘는다. 총 지방 함량, 포화 지방 함량, 트랜스 지방 함량, 콜레스테롤 함량, 나트륨 함량 등이 그런 항목이다.

이런 클래스에는 어떤 생성자나 정적 팩터리 메서드가 적합할까? 보통 프로그래머들은 이런 상황에 점층적 생성자 패턴을 적용한다.

필수 인자만 받는 생성자를 하나 정의하고, 선택적 인자를 하나 받는 생성자를 추가하고, 거기에 두 개의 선택적 인자를 받는 생성자를 추가하는 식으로, 생성자들을 쌓아 올리듯 추가하는 것이다.

//점층적 생성자 패턴(Tetescoping constructor pattern)
public class NutritionFacts {
    private final int servingSize;    // (ml)                필수
    private final int servings;        // (per container)    필수
    private final int calories;        //                    선택
    private final int fat;            // (g)                선택
    private final int sodium;        // (mg)                선택
    private final int carbohydrate;    // (g)                선택

    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }
    public NutritionFacts(int servingSize, int servings, 
                int calories) {
        this(servingSize, servings, calories, 0);
    }
    public NutritionFacts(int servingSize, int servings, 
                int calories, int fat) {
        this(servingSize, servings, calories, fat, 0);
    }
    public NutritionFacts(int servingSize, int servings, 
                int calories, int fat, int sodium) {
        this(servingSize, servings, calories, fat, sodium, 0);
    }
    public NutritionFacts(int servingSize, int servings, 
                int calories, int fat, int sodium,
                int carbohydrate) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
        this.carbohydrate = carbohydrate;
    }
}
///
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);

그런데 이렇게 하다 보면, 설정할 필요가 없는 필드에도 인자를 전달해야 하는 경우가 생긴다. 위의 코드에서 fat 필드 설정을 위해 전달한 0이 그런 사례다. 인자가 여섯 개 뿐이니 그다지 흉해 보이지는 않겠지만, 인자 수가 늘어나면 금방 곤란해진다.
요약하자면, 점층적 생성자 패턴은 잘 동작하지만 인자 수가 늘어나면 클라이언트 코드를 작성하기가 어려워지고, 무엇보다 읽기 어려운 코드가 되고 만다. 대체 그 많은 인자가 무슨 값인지 알 수 없게 되고, 그 의미를 알려면 인자를 주의깊게 세어보아야 한다.
자료형이 같은 인자들이 많아지다보면, 종종 미묘한 버그가 발생한다. 클라이언트가 두 개 인자의 순서를 실수로 뒤집어도 컴파일러는 알지 못하며, 프로그램 실행 도중에 문제가 생기게 되는 것이다.(규칙 40)

생성자에 전달되는 인자 수가 많을 때 적용 가능한 두 번째 대안은 자바빈 패턴이다.
인자 없는 생성자를 호출하여 객체부터 만든 다음, 설정 메서드들을 호출하여 필수 필드뿐만 아니라 선택적 필드의 값들까지 채우는 것이다.

//java bean pattern  
public class NutritionFacts {  
private int servingSize = -1; // 필수, 기본값 없음  
private int servings = -1;// 필수, 기본값 없음  
private int calories = 0;  
private int fat = 0;  
private int sodium = 0;  
private int carbohydrate = 0;

public NutritionFacts() { }

public void setServingSize(int val) { servingSize = val; }
public void setServings(int val) { servings = val; }
public void setCalories(int val) { calories = val; }
public void setFat(int val) { fat = val; }
public void setSodium(int val) { sodium = val; }
public void setCarbohydrate(int val) { carbohydrate = val; }
}  
/// 
NutritionFacts cocaCola = new NutritionFacts();  
cocaCola.setServingSize(240);  
cocaCola.setServings(8);  
cocaCola.setCalories(100);  
cocaCola.setSodium(35);  
cocaCola.setCarbohydrate(27);

그러나 불행히도 자바빈 패턴에는 심각한 단점이 있다. 1회의 함수 호출로 객체 생성을 끝낼 수 없으므로, 객체 일관성이 일시적으로 깨질 수 있다는 것이다. 생성자의 인자가 유효한지 검사하여 일관성을 보장하는 단순한 방법을 여기서는 사용할 수 없다. 일관성이 깨진 객체를 사용할 때 생기는 문제는 실제 버그 위치에서 한참 떨어진 곳에서 발생하므로 디버깅 하기도 어렵다.
이와 관련된 또 다른 문제는, 자바빈 패턴으로는 변경 불가능 클래스를 만들 수 없다는 것이다(규칙 15). 스레드 안전성을 제공하기 위해 해야 할 일도 더 많아진다.

점층적 생성자 패턴의 안전성에 자바빈 패턴의 가독성을 결합한 세 번째 대안이 있다는 것이다. 바로 빌더 패턴이다.
필요한 객체를 직접 생성하는 대신, 클라이언트는 먼저 필수 인자들을 생성자에(또는 정적 팩터리 메서드에) 전부 전달하여 빌더 객체를 만든다. 그런 다음 빌더 객체에 정의된 설정 메서드들을 호출하여 선택적 인자들을 추가해 나간다. 그리고 마지막으로 아무런 인자 없이 build 메서드를 호출하여 변경 불가능 객체로 만드는 것이다. 빌더 클래스는 빌더가 만드는 객체 클래스의 정적 멤버 클래스로 정의한다.

public class NutritionFacts {  
private final int servingSize;  
private final int servings1;  
private final int calories;  
private final int fat;  
private final int sodium;  
private final int carbohydrate;

public static class Builder {
    private final int servingSize;
    private final int servings1;
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;

    public Builder(int servingSize, int servins) {
        this.servingSize = servingSize;
        this.servings = servings;
    }

    public Builder calories(int val) { calories = val; return this; }
    public Builder fat(int val) { fat = val; return this; }
    public Builder carbohydrate(int val) { carbohydrate = val; return this; }
    public Builder sodium(int val) { sodium = val; return this; }

    public NutritionFacts build() {
        return new NutritionFacts(this);
    }
}

private NutritionFacts(Builder builder) {
    servingSize = builder.servingSize;
    servings = builder.servings;
    calories = builder.calories;
    fat = builder.fat;
    sodium = builder.sodium;
    carbohydrate = builder.carbohydrate;
}
}  
//  
NutritionFacts cocaCola = new NutritionFacts().Builder(240, 8).  
calories(100).sodium(35).carbohydrate(27).build();

빌더 패턴에도 단점이 있다. 객체를 생성하려면 우선 빌더 객체를 생성해야 한다. 실무에서 빌더 객체를 만드는 오버헤드가 문제가 될 소지는 없어 보이지만, 성능이 중요한 상황에선 그렇지 않을 수도 있다.

또한 빌더 패턴은 점층적 생성자 패턴보다 많은 코드를 요구하기 때문에 인자가 충분히 많은 상황(가령, 네 개 이상)에서 이용해야 한다. 하지만 나중에 새로운 인자를 추가하는 상황이 올 수도 있다는 것은 기억하자. 그러나 우선은 생성자와 정적 팩터리로 시작하더라도, 인자 개수가 통제할 수 없을 정도로 많아지면 빌더 패턴을 적용하자.

규칙 3 private 생성자나 enum 자료형은 싱글턴 패턴을 따르도록 설계하라

싱글턴은 객체를 하나만 만들 수 있는 클래스다.

//public final 필드를 이용한 싱글톤
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() { ... }

    public void leaveTheBuilding() { ... }
}
//static factory를 이용한 싱글톤
public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() { ... }
    public static Elvis getInstance(){ retrun INSTANCE}

    public void leaveTheBuilding() { ... }
}
//java 1.5 enum 을 이용한 싱글톤
public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() { ... }
}

규칙 4 객체 생성을 막을 때는 private 생성자를 사용하라

때로는 정적 메서드나 필드만 모은 클래스를 만들고 싶을 때가 있다. 이런 클래스들은 악명이 높은데, 객체 지향적으로 생각하지 않으려는 사람들이 남용하는 경향이 있기 때문이다. 하지만 이런 클래스들도 분명 필요한 데가 있다.

그런 유틸리티 클래스들은 객체를 만들 목적의 클래스가 아니다. 객체를 만들면 오히려 이상하다. 하지만 생성자를 생략하면 컴파일러는 자동으로 인자 없는 public 기본 생성자를 만들어 버린다. 사용자 입장에서 보면 이 생성자는 일반 생성자와 구별할 수 없다. 따라서 원래 의도와는 달리 객체 생성이 가능한 클래스가 공개 API에 포함되는 일도 드물지 않다.

객체를 만들 수 없도록 하려고 클래스를 abstract로 선언해 봤자 소용없다. 하위 클래스를 정의하는 순간 객체 생성이 가능해지기 때문이다. abstract 클래스니까 계승해서 사용하는 것이 맞을 거라 착각하는 사용자도 있을 수 있다(규칙 17).

하지만 이런 문제들은 간단한 규칙 하나만 따르면 막을 수 있다. 기본 생성자는 클래스에 생성자가 없을 때 만들어지니까, private 생성자를 클래스에 넣어서 객체 생성을 방지하자는 것이다.

// 객체를 만들 수 없는 유틸리티 클래스
public class UtilityClass {
    // 기본 생성자가 자동 생성되지 못하도록 하여 객체 생성 방지
    private UtilityClass() {
        throw new AssertionError();
    }
    ...
}

명시적으로 정의된 생성자가 private이므로 클래스 외부에서는 사용할 수 없다. AssertionError는 반드시 필요한 것은 아니지만, 클래스 안에서 실수로 생성자를 호출하면 바로 알 수 있게 하기 위한 것이다.

따라서 이렇게 구현한 클래스의 객체는 어떤 상황에서도 만들 수가 없다. 생성자를 명시적으로 정의했으나 호출할 수는 없다는 사실이 썩 직관적이지는 않으니, 위에 보인 것처럼 주석을 달아 두는 것이 바람직하다.

규칙 5 불필요한 객체는 만들지 말라

기능적으로 동일한 객체는 필요할 때마다 만드는 것보다 재사용하는 편이 낫다. 객체를 재사용하는 프로그램은 더 빠르고 더 우아하다. 변경 불가능 객체는 언제나 재사용할 수 있다(규칙 15).

절대로 피해야 할 극단적 예를 하나 들어보자.
String s = new String("stringette");

String s = "stringette";

JDK 1.5부터는 쓸데없이 객체를 만들 새로운 방법이 더 생겼다. 자동 객체화라는 것인데, 프로그래머들이 자바의 기본 자료형과 그 객체 표현형을 섞어 사용할 수 있도록 해준다. 둘 간의 변환은 자동으로 이뤄진다.

자동 객체화 덕에 기본 자료형과 그 객체 표현형 사이의 차이가 희미해지긴 했지만, 아주 없어진 것은 아니다. 의미적인 차이는 미미하지만, 성능 차이는 무시하기 어렵다(규칙 49). 아래의 프로그램을 보자. 모든 양의 정수의 합을 계산하는 코드다. long을 사용해 계산을 수행하도록 짜여 있는데, int는 모든 양의 정수 값을 담을 만큼 크지 않아서다.

// 무시무시할 정도로 느린 프로그램. 어디서 객체가 생성되는지 알겠는가?
public static void main(String[] args) {
    Long sum = 0L;
    for (long i = 0; i < Integer.MAX_VALUE; i++) {
        sum += i;
    }
    System.out.println(sum);
}

계산 결과가 정확하긴 하지만, 응당 나와야 할 성능보다 한참 아래의 성능을 보여준다. 알파벳 하나를 잘못 쓴 덕분이다. sum은 long이 아니라 Long으로 선언되어 있는데, 그 덕에 2n개의 쓸데없는 객체가 만들어진다.(long i가 Long sum에 더해질 때마다 하나씩 생긴다고 보면 된다.) sum의 자료형을 Long에서 long으로 바꾸자. 필자 컴퓨터에서의 실행시간은 43초에서 6.8초로 줄어들었다. 여기서 얻을 수 있는 명백한 교훈은, 객체 표현형 대신 기본 자료형을 사용하고, 생각지도 못한 자동 객체화가 발생하지 않도록 유의하라는 것이다.

그러나 여기서 하려는 말은 객체를 만드는 비용이 높으니 무조건 피하라는 것이 아니다. 생성자 안에서 하는 일이 작고 명확한 경우, 객체 생성과 반환은 신속하게 이루어진다. 최신 JVM이라면 더더욱 그렇다. 객체를 만들어서 코드의 명확성과 단순성을 높이고 프로그램의 능력을 향상시킬 수 있다면, 일반적으로는 만드는 것이 좋다.

마찬가지로, 직접 관리하는 객체 풀을 만들어 객체 생성을 피하는 기법은 객체 생성 비용이 극단적으로 높지 않다면 사용하지 않는 것이 좋다. 객체 풀을 만드는 비용이 정당화 될 만한 객체의 예로 가장 고전적인 것은 데이터베이스 연결이다.

규칙 6 유효기간이 지난 객체 참조는 폐기하라

// "메모리 누수"가 어디서 생기는지 보이는가?
public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0) throw new EmptyStackException();
        return elements[--size];
    }

    //배열의 길이를 두배씩 늘린다.
    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}

이 프로그램에 뚜렷이 잘못된 부분은 없다.(제네릭으로 구현된 코드를 보고 싶다면 규칙 26 참조.) 아무리 많은 테스트를 하더라도 다 통과할 것이다. 하지만 이 프로그램에는 보이지 않는 문제가 하나 숨어있다. 간단히 말해 이 프로그램에는 "메모리 누수" 문제가 있다.
그 결과로 쓰레기 수집기가 해야 할 일이 많아져서 성능이 저하되거나, 메모리 요구량이 증가할 것이다. 극단적인 경우에는 디스크 페이징이 발생할 것이고, 심지어는(드문 일이기는 하지만) OutOfMemoryError가 던져지면서 프로그램이 중단될 것이다.
그렇다면 메모리 누수는 어디에서 생기나? 스택이 커졌다가 줄어들면서 제거한 객체들을 쓰레기 수집기가 처리하지 못해서 생긴다. 스택을 사용하는 프로그램이 그 객체들을 더 이상 참조하지 않는데도 말이다. 스택이 그런 객체에 대한 만기 참조를 제거하지 않기 때문이다. 만기 참조란, 다시 이용되지 않을 참조를 말한다. 방금 살펴본 스택의 경우에는 elements 배열에서 실제로 사용되는 부분을 제외한 나머지 영역에 보관된 참조들이 만기 참조다. 첨자 값이 size보다 작은 곳에 있는 요소들은 실제로 쓰이는 참조들이지만, 나머지 영역에 있는 참조들은 그렇지 않다.

이런 문제는 간단히 고칠 수 있다. 쓸 일 없는 객체 참조는 무조건 null로 만드는 것이다.
Stack 클래스의 경우, 스택에서 pop된 객체에 대한 참조는 그 즉시 null로 만들면 된다. 다음은 수정된 코드다.

public Object pop() {
    if (size == 0) throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null;  // 만기 참조 제거
    return result;
}

만기 참조를 null로 만들면 나중에 실수로 그 참조를 사용하더라도 NullPointerException이 발생하기 때문에, 프로그램은 오동작하는 대신 바로 종료된다는 장점이 있다. 프로그래밍 오류는 최대한 빨리 알아내는 것이 좋다.

이런 오류를 한 번 접하고 나면, 객체 사용이 끝나면 즉시 그 참조를 null 처리해야 한다는 강박관념에 사로잡히는 경우가 있다. 하지만 그럴 필요도 없고 바람직하지도 않은 것이, 프로그램만 난잡해지기 때문이다. 객체 참조를 null 처리하는 것은 규범이라기보단 예외적인 조치가 되어야 한다. 만기 참조를 제거하는 가장 좋은 방법은 해당 참조가 보관된 변수가 유효범위를 벗어나게 두는 것이다. 변수를 정의할 때 그 유효범위를 최대한 좁게 만들면 자연스럽게 해결된다(규칙 45).

그렇다면 참조의 null 처리는 언제 하는 것이 좋을까? Stack 클래스의 어떤 면이 메모리 누수를 유발하는가? 간단히 말해서, Stack이 자체적으로 메모리를 관리한다는 것이 문제다. 이 저장공간 풀은 elements 배열의 원소들이다.(각 원소는 객체가 아니라 객체에 대한 참조다.) 이 배열에서 실제로 사용되는 부분에 있는 원소가 참조하는 객체는 할당된 객체지만, 나머지 원소가 참조하는 객체는 반환 가능한 객체들이다. 하지만 쓰레기 수집기는 그 사실을 알 도리가 없다. 쓰레기 수집기 입장에서는 elements 내의 참조들은 전부 유효해 보이기 때문이다. 그러나 사용하지 않는 참조들을 즉시 null로 만들어버리면 쓰레기 수집기는 반환해도 좋은 객체가 어떤 것인지 바로 알 수 있다.

일반적으로, 자체적으로 관리하는 메모리가 있는 클래스를 만들 때는 메모리 누수가 발생하지 않도록 주의해야 한다. 더 이상 사용되지 않는 원소 안에 있는 객체 참조는 반드시 null로 바꿔 주어야 한다.

규칙 7 종료자 사용을 피하라

종료자(finalize() 메서드)는 에측 불가능하며, 대체로 위험하고, 일반적으로 불필요하다.
종료자의 한 가지 단점은, 즉시 실행되리라는 보장이 전혀 없다는 것이다. 어떤 객체에 대한 모든 참조가 사라지고 나서 종료자가 실행되기까지는 긴 시간이 걸릴 수도 있다. 따라서 긴급한 작업을 종료자 안에서 처리하면 안 된다.
예를 들어, 종료자 안에서 파일을 닫도록 하면 치명적이다. 파일 기술자는 유한한 자원이기 때문이다. JVM은 종료자를 천천히 실행하므로 열린 상태의 파일이 많이 남아있을 수 있다. 그런 상황에서 새로운 파일을 열려고 하면, 한 번에 열 수 있는 파일의 개수에 제한이 있으므로 오류가 나게 된다.

자바 명세에는 종료자가 즉시 실행되어야 한다는 문구도 없지만, 종료자가 결국에는 반드시 실행되어야 한다는 문구도 없다. 따라서 종료자가 실행되지 않은 객체가 남은 상태로 프로그램이 끝나게 되는 일도 충분히 가능하다. 그러므로 지속성이 보장되어야 하는 중요 상태 정보는 종료자로 갱신하면 안 된다. 예를 들어 분산 시스템 전체를 먹통으로 만드는 가장 좋은 방법은, 데이터베이스 같은 공유 자원에 대한 지속성 락을 종료자가 반환하게 구현하는 것이다.

System.gc나 System.runFinalization 같은 메서드에 마음이 흔들리면 곤란하다. 이런 메서드들은 종료자가 실행될 가능성을 높여주긴 하지만 보장하진 않는다. 종료자 실행을 보장하는 메서드들은 System.runFinalizersOnExit와 Runtime.runFinalizersOnExit 뿐인데, 이들 메서드는 심각한 결함을 갖고 있어서 이미 명세에서 폐기되었다.

종료자를 사용하면 프로그램 성능이 심각하게 떨어진다. 필자의 컴퓨터에서 간단한 객체를 만들고 삭제하는 데는 5.6ns면 충분한데, 종료자를 붙이자 그 시간은 2,400ns로 늘어났다. 즉, 종료자를 사용해 객체를 삭제하는 프로그램은 430배가량 느려진다는 것이다.

요약하자면,
1) 자원 반환에 대한 최종적 안전장치를 구현하거나, 그다지 중요하지 않은 네이티브 자원을 종료시키려는 것이 아니라면 종료자는 사용하지 말라는 것이다. 굳이 종료자를 사용해야 하는 드문 상황에 처했다면 super.finalize 호출은 잊지 말자.
2) 자원 반환 안전망을 구현하는 경우에는 종료자가 호출될 때마다 클라이언트 코드가 잘못 작성되었음을 알리는 메시지를 로그로 남기자.
3) 마지막으로, 하위 클래스 정의가 가능한 public 클래스에 종료자를 추가해야 하는 상황이라면, 하위 클래스에서 실수로 super.finalize 호출을 잊어도 종료 작업이 진행될 수 있도록 종료 보호자 패턴을 도입하면 좋을지 고려해 보자.

※ 자바 1.7부터는 try-with-resources 문을 지원하는데, 이 문법을 이용하면 finally 블록을 사용하지 않아도 된다. try에 자원 객체를 전달하면 finally 블록으로 종료 처리를 하지 않아도 try 코드 블록이 끝나면 자동으로 자원을 종료해주는 기능이다.

try(FileOutputStream out = new FileOutputStream("exFile.txt")) { //...이후 입출력 로직 처리... }catch(IOException e){ e.printStackTrace(); }

출처: https://dololak.tistory.com/67 [코끼리를 냉장고에 넣는 방법]
3장 모든 객체의 공통 메서드

규칙 8 equals를 재정의할 때는 일반 규약을 따르라  
규칙 9 equals를 재정의할 때는 반드시 hashCode도 재정의하라  
규칙 10 toString은 항상 재정의하라  
규칙 11 clone을 재정의할 때는 신중하라  
규칙 12 Comparable 구현을 고려하라  

관련글 더보기

댓글 영역

페이징