📌 4장 : 클래스와 인터페이스

아이템 15. 클래스와 멤버의 접근 권한을 최소화하라

  • 잘 설계된 컴포넌트 : 모든 내부 구현을 완벽히 숨겨, 구현과 API를 깔끔하게 분리한다.
  • 클래스, 인터페이스, 멤버가 의도치 않게 API로 공개되는 일이 없도록 해야 한다.
  • public 클래스에서 public static final 이외에 어떤 public 필드가 있어서는 안된다.
  • public static final 필드가 참조하는 객체가 불변인지 확인한다.
  • 정보 은닉의 장점
    • 시스템을 구성하는 컴포넌트들을 서로 독립시켜서 개발, 테스트, 최적화, 적용, 분석, 수정을 개별적으로 할 수 있게 해주는 것과 연관되어 있다.
    • 시스템의 개발 속도를 높인다. (여러 컴포넌트를 병렬로 개발)
    • 시스템 관리 비용을 낮춘다. (각 컴포넌트 교체 부담이 적다.)
    • 성능 최적화에 도움을 준다.
    • 소프트웨어 재사용성을 높인다. 외부에 거의 의존하지 않고, 독자적으로 동작 가능한 컴포넌트는 낯선 환경에서도 유용하게 쓰일 수 있다.
    • 큰 시스템을 제작하는 난이도를 낮춰준다.
  • 접근 제어 메커니즘
    • 클래스, 인터페이스, 멤버의 접근성(접근 허용 범위)을 명시한다.
    • 각 요소의 접근성은 선언된 위치와 접근 제한자로 정해진다.
    • 접근 제한자를 제대로 활용하는 것이 정보 은닉의 핵심
  • 기본 원칙
    • 모든 클래스와 멤버의 접근성을 가능한 좁혀야 한다.
  • private : 멤버를 선언한 톱레벨 클래스에서만 접근 가능
  • package-private : 멤버가 소속된 패키지 안의 모든 클래스에서 접근할 수 있다.
    • 클라이언트에 피해 없이 다음 릴리스에서 수정, 교체, 제거 가능
  • protected : package-private의 접근 범위를 포함하며, 이 멤버를 선언한 클래스의 하위 클래스에서도 접근 가능
  • public : 모든 곳에서 접근 가능. 공개 API, 하위 호환을 위해 계속 관리 필요
  • 상위 클래스의 메서드를 재정의할 때는 접근 수준을 상위 클래스에서보다 좁게 설정할 수 없다.
    • 상위 클래스의 인스턴스는 하위 클래스의 인스턴스로 대체해 사용할 수 있어야 한다.(리스코프 치환 원칙)
  • public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다.
    • 필드가 가변 객체를 참조하거나 final이 아닌 인스턴스 필드를 public으로 선언하면 그 필드에 담을 수 있는 값을 제한할 힘을 잃는다.
    • 꼭 필요한 구성요소의 상수일 경우 public static final 필드로 공개해도 된다.
  • 클래스에서 public static final 배열 필드를 두거나 이 필드를 반환하는 접근자 메서드를 제공하면 안된다.
    • 길이가 0이 아닌 배열은 모두 변경이 가능하다.
    • 클라이언트에서 이런 접근자를 제공하면 그 배열의 내용을 수정할 수 있게 되기 떄문이다.
  • 첫 번째 방법 : public 배열을 private으로 만들고 public 불변 리스트를 추가한다.
private static final Thing[] PRIVATE_VALUES = { ... }; 
public static final List<Thing> VALUES =
    Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
  • 두 번째 방법 : 배열을 private 으로 만들고 복사본을 반환하는 public 메서드를 추가(방어적 복사)
private static final Thing[] PRIVATE_VALUES = { ... }; 
public static final Thing[] values() {
    return PRIVATE_VALUES.clone(); }

아이템 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라

  • 데이터 필드에 직접 접근할 수 있지만, 캡슐화의 이점을 제공할 수 없는 상황
class Point {
    public double x;
    public double y; 
}

필드들을 모두 private으로 바꾸고 public 접근자(getter) 추가

class Point {
    private double x; 
    private double y;
    
    public Point(double xf double y) { 
        this.x = x;
        this.y = y; 
    }

    public double getX() { return x; } 
    public double getY() { return y; }
    
    public void setX(double x) { this.x = x; }
    public void setY(double y) { this.y = y; }
}
  • 접근자와 변경자(mutator) 메서드를 활용해 데이터를 캡슐화한다.
  • package-private 클래스 혹은 private 중첩 클래스에서는 데이터 필드를 노출해도 문제가 없다.
  • 패키지 바깥 코드는 변경하지 않아도 데이터 표현 방식을 바꿀 수 있다.

아이템 17. 변경 가능성을 최소화하라

  • 불변 클래스 : 인스턴스 내부 값을 수정할 수 없는 클래스
    • 가변 클래스보다 설계, 구현, 사용이 간단하고 오류가 생길 여지가 적다.
  • 자바 플랫폼 라이브러리의 불변 클래스 : String, 기본 타입의 박싱된 클래스, BigInteger, BigDecimal
  • 불변 클래스 만드는 5가지 규칙
    • 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
    • 클래스 확장을 할 수 없게 한다. ex. final
    • 모든 필드를 final으로 선언한다.
    • 모든 필드를 private으로 선언한다.
    • 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
      • 클래스에 가변 객체를 참조하는 필드가 있으면, 클라이언트에서 객체를 참조할 수 없게 해야 한다.
      • 생성자, 접근자, readObject 메서드에서 방어적 복사를 수행해라
  • 함수형 프로그래밍
    • 피연산자에 함수를 적용해서 결과를 반환하지만, 피연산자 자체는 그대로인 프로그래밍 패턴
    • 절차적 혹은 명령형 프로그래밍은 메서드에서 피연산자인 자신을 수정해 자신의 상태가 변한다.
public final class Complex { 
    private final double re; 
    private final double im;

    public Complex(double re, double im) { 
    this.re = re;
    this.im = im; 
    }

    public double realPartf) { return re; } 
    public double imaginaryPart() { return im; }

    public Complex plus(Complex c) {
        return new Complex(re + c.re, im + c.im);
    }
    
    ...
}
  • 불변 객체는 근본적으로 스레드 안전하여 따로 동기화가 필요없다.
  • 불변 클래스는 한 번 만든 인스턴스를 최대한 재활용한다.
  • 가장 쉬운 재활용 방법 : 자주 쓰이는 값들을 상수로 제공한다. ex. public static final
  • 불변 클래스는 자주 사용되는 인스턴스를 캐싱해서 같은 인스턴스를 중복 생성하지 않도록 정적 팩터리를 제공할 수 있다.
  • 정적 패터리를 사용하면 여러 클라이언트가 인스턴스를 공유하여 메모리 사용량과 가비지 컬렉션 비용을 줄어든다.
  • public 생성자 대신에 정적 팩터리를 만들어두면, 클라이언트를 수정하지 않아도 캐시 기능을 덧붙일 수 있다.
  • 방어적 복사도 필요 없기 때문에 clone 메서드나 복사 생성자를 제공하지 않는 것이 좋다.
  • 불변 객체는 자유롭게 공유할 수 있고, 불변 객채꼐리 내부 데이터를 공유할 수 있다.
  • 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다.
  • 불변 객체는 그 자체로 실패 원자성을 제공한다.
  • 단점으로는 값이 다르면 반드시 독립된 객체로 만들어야 한다는 점이다.
  • 중간 단계에서 만들어진 객체들이 모두 버려지는 경우 성능 문제
    • 문제 대처 방법 : 다단계 연산(multistep operation) 들을 예측해서 기본 기능으로 제공하는 방법
      • 클라이언트가 원하는 복잡한 연산들을 정확하게 예측하면 package-private의 가변 동반 클래스만으로도 충분하다.
  • 모든 클래스를 불변으로 만들 수 없다. 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄인다.
  • 다른 합당한 이유가 없으면 모든 필드는 private final 이어야 한다.
  • 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.

아이템 18. 상속보다는 컴포지션을 사용하라

  • 상속 : 클래스가 다른 클래스를 확장하는 구현 상속
  • 메서드 호출과 다르게 상속은 캡슐화를 깨뜨린다.
    • 상위 클래스에 따라 하위 클래스의 동작에 이상이 생길 수 있다.
  • 잘못된 상속 예시 : addAll 메서드로 원소 3개를 더했을 때
InstrumentedHashSet<String> s = new InstrumentedHashSeto();
s.addAlKList.of("틱", "탁탁", "펑,,));
  • getAddCount 메서드를 호출하면 3을 반환해야 하지만 6을 반환한다.
  • HashSet의 addAll 메서드가 add 메서드를 사용해 구현되어서 addCount에 값이 중복해서 더해져서 최종 값이 6으로 늘어났다.
  • 하위 클래스에서 addAll 메서드를 재정의하지 않으면 문제를 고칠 수 없다.
public class InstrumentedHashSet<E> extends HashSet<E> {
    private int addCount = 0;
    
    public InstrumentedHashSet() { }
    
    public InstrumentedHashSet(int initCap, float loadFactor) { 
        super(initCap, LoadFactor);
    }
    
    @Override public boolean add(E e) { 
        addCount++;
        return super.add(e); 
    }
    
    @Override public boolean addAll(Collection<? extends E> c) { 
        addCount += c.sizeO;
        return super.addAll(c);
    }
    
    public int getAddCount() { 
        return addCount;
    }
}
  • 컴포지션(composition) : 기존 클래스를 확장하는 대신, 새로운 클래스를 만들고 private 필드로 기존 클래스의 인스턴스를 참조하는 방법
  • 새 클래스의 인스턴스 메서드들은 기존 클래스의 대응하는 메서드를 호출해서 결과를 반환한다.
  • 래퍼 클래스
    • 다른 Set 인스턴스를 감싸고 있는 뜻에서 래퍼 클래스라고 한다.
    • 다른 계측 기능을 덧씌운다는 뜻에서 데코레이터 패턴(decorator pattern) 이라고도 부른다.
    • 단점이 거의 없지만, 래퍼 클래스가 콜백(callback) 프레임워크와는 어울리지 않는다는 점을 주의해야 한다.
public class InstrumentedSet<E> extends Fon시ardingSet<E> {
    private int addCo나nt = 0;
    public InstrumentedSet(Set<E> s) { 
        super(s);
    }
    
    @Override public boolean add(E e) { 
        addCount++;
        return super.add(e); 
    }
    
    @Override public boolean addAll(Collection<? extends E> c) { 
        addCount += c.size();
        return super.addAll(c);
    }
    
    public int getAddCount() { 
    return addCount;
}
  • 재사용할 수 있는 전달(forwarding) 클래스
public class ForwardingSet<E> implements Set<E> { 
    private final Set<E> s;
    public ForwardingSet(Set<E> s) { this.s = s; }
    
    public void clear() { s.clearO; }
    public boolean contains(Object o) { return s.contains(o); }
    public boolean isEmpty() { return s.isEmpty(); }
    public int size() { return s.size(); }
    public Iterator<E> iterater() { return s.iterator(); }
    public boolean add(E e) { return s.add(e); }
    public boolean remove(Object o) { return s.remove(o); }
    public boolean containsAll(Collection<?> c) { return s.containsAll(c); } 
    public boolean addAll(Collection<? extends E> c){ return s.addAH(c); } 
    public boolean removeAll(Collection<?> c){ return s. removeAH(c); } 
    public boolean retainAlKCollection<?> c){ return s. retainAlKc); } 
    public Object[] toArrayO { return s.toArray(); } 
    public <T> T[] toArray(T[] a) { return s.toArray(a); } 
    @Override public boolean equals(Object o){ return s.equals(o); } 
    @Override public int hashCode() { return s.hashCodef); } 
    @Override public String toString() { return s.toString(); }
}

아이템 19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라

  • 상속용 클래스는 재정의할 수 있는 메소드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 한다.
  • 내부 메커니즘을 문서로 남기는 것이 상속을 위한 설계의 전부는 아니다.
  • 클래스의 내부 동작 과정 중간에 끼어들 수 있는 훅(hook)을 잘 선별하여 protected 메서드 형태로 공개해야 할 수도 있따.
  • 상속용 클래스를 시험하는 방법은 직접 하위 클래스를 만들어보는 것이 유일하다.
  • clone과 readObject 모두 직접적으로든 간접적으로든 재정의 가능 메서드를 호출해서는 안된다.
  • clone이 잘못되면 복제본말고도 원본 객체에도 피해를 줄 수 있다.
  • 상속용으로 설계하지 않은 클래스는 상속을 금지하는 것이 나을 수 있다.
  • 상속을 금지하는 방법 2가지
    • 클래스를 final으로 선언하는 방법
    • 모든 생성자를 private이나 package-private으로 선언하고 public 정적 팩터리를 만들어주는 방법
      • 생성자 모두를 외부에서 접근할 수 없도록 만든다.

아이템 20. 추상 클래스보다는 인터페이스를 우선하라

  • 자바가 제공하는 다중 구현 메커니즘 : 인터페이스, 추상클래스
  • 두 메커니즘 공통점 : 인스턴스 메서드를 구현 형태로 제공 가능
  • 차이점 : 추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 한다.
    • 추상 클래스 방식은 새로운 타입을 정의하는 데 커다란 제약이 생긴다.
    • 인터페이스 방식은 선언한 메서드를 모두 정의하고 일반 규약을 잘 지킨 클래스라면 어떤 클래스를 상속해도 같은 타입으로 취급된다.
  • 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있다.
  • 인터페이스가 요구하는 메서드를 추가하거나 클래스 선언에 implements 구문만 추가하면 된다.
  • 추상 클래스 방식은 클래스 계층 구조에 혼란을 일으켜 새로 추가된 추상 클래스의 모든 자손이 상속을 하게 되는 문제가 생길 수 있다.
  • 인터페이스는 믹스인(mixin) 정의에 안성맞춤이다.
  • 믹스인 : 클래스가 구현할 수 있는 타입.
    • 믹스인을 구현한 클래스에 원래의 '주된 타입' 외에도 특정 선택적 행위를 제공한다고 선언하는 효과를 준다.
    • 대상 타입의 주된 기능에 선택적 기능을 혼합한다고 해서 믹스인이라고 부른다.
  • 인터페이스로 계층 구조가 없는 타입 프레임워크를 만들 수 있다.
  • 타입을 계층적으로 정의하면 개념을 구조적으로 잘 표현 가능하지만, 표현이 어려운 개념도 있다.
public interface Singer { 
    AudioClip sing(Song s);
}

public interface Songwriter {
    Song compose(int chartPosition);
}
  • 작곡도 하는 가수가 있기 때문에 가수 클래스가 Singer와 Songwriter 모두를 구현해도 문제가 되지 않는다.
  • 가능한 조합 전부를 각각의 클래스로 정의한 복잡한 계층 구조가 만들어질 수 있다.
  • 래퍼 클래스 관용구와 함께 사용하면 인터페이스는 기능을 향상시키는 안전하고 강력한 수단이 된다.
  • 인터페이스와 추상 골격 구현(skeletal implementation) 클래스를 함께 제공하는 방법으로 인터페이스와 추상 클래스 장점을 모두 취하는 방법도 있다.
  • 템플릿 메서드 패턴 : 인터페이스로 타입 정의, 필요하면 디폴트 메서드도 제공, 골격 구현 클래스는 나머지 메서드까지 구현한다.
  • 인터페이스 이름이 Interface 이면, 골격 구현 클래스 이름은 AbstractInterface
  • 골격 구현을 사용해 완성한 구체 클래스 예시
static List<Integer> intArrayAsList(int[] a) {
    Objects.requireNonNull(a);
    
    // 다이아몬드 연산자를 이렇게 사용하는 건 자바 9부터 가능하다. 
    // 더 낮은 버전을 사용한다면 <Integer>로 수정하자.
    return new AbstractListo() {
        @Override public Integer get(int i) { 
            return a[i]; // 오토박싱(아이템 6)
        }

        @Override public Integer set(int i, Integer val) { 
            int oldVal = a[i];
            a[i] = val; // 오토언박싱
            return oldVal; // 오토박싱
        }
        
        @Override public int size() { 
            return a.length;
        } 
    };
}

아이템 21. 인터페이스를 구현하는 쪽을 생각해 설계하라

  • 자바 8 이전에는 추가된 메서드가 우연히 기존 구현체에 이미 존재할 가능성이 낮기 때문에, 기존 구현체를 깨뜨리지 않으면 인터페이스에 메소드를 추가할 방법이 없었다.
  • 자바 8 이후부터는 기존 인터페이스에 메서드를 추가할 수 있도록 디폴트가 생겼지만, 위험이 아예 없어진 것은 아니다.
    • 디폴트 메서드를 선언하면, 인터페이스 구현 후 디폴트 메서드를 재정의하지 않은 모든 클래스에서 디폴트 구현이 쓰이게 된다.
    • 모든 기존 구현체들이 매끄럽게 연동되는 보장이 없다.
  • 생각할 수 있는 모든 상황에서 불변식을 해치지 않는 디폴트 메서드 작성은 어렵다.
  • 디폴트 메서드는 컴파일은 성공해도, 기존 구현체에 런타임 오류를 일으킬 수 있다.
  • 기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는 일은 꼭 필요한 것이 아니면 피해야 한다.
  • 새로운 인터페이스를 만드는 경우에는 표준적인 메서드 구현을 제공하는 데 유용한 수단이 된다.

아이템 22. 인터페이스는 타입을 정의하는 용도로만 사용하라

  • 인터페이스는 자신을 구현한 클래스의 인스턴스를 참조할 수 있는 타입 역할
  • 자신의 인스턴스로 무엇을 할 수 있는지 클라이언트에게 알려줄 수 있다.
  • 지침에 맞지 않는 예시 : static final 필드로만 가득찬 인터페이스
public interface PhysicalConstants {
    // 아보가드로 수 (1/몰)
    static final double AVOGADROS_NUMBER = 6.022_140_857e23; 
    
    // 볼츠만 상수 (J/K)
    static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;
    
    // 전자 질량 (kg)
    static final double ELECTRON_MASS = 9.109_383_56e-31; 
}

아이템 23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라

  • 태그가 달린 클래스
class Figure {
    enum Shape { RECTANGLE, CIRCLE };
    
    // 태그 필드 - 현재 모양을 나타낸다.
    final Shape shape;
    
    // 다음 필드들은 모양이 사각형(RECTANGLE)일 때만 쓰인다. 
    double length;
    double width;
    
    // 다음 필드는 모양이 원(CIRCLE)일 때만 쓰인다. 
    double radius;
    
    // 원용 생성자
    Figure(double radius) { 
        shape = Shape.CIRCLE; 
        this.radius = radius;
    }
    
    // 사각형용 생성자
    Figure(double length, double width) { 
        shape = Shape.RECTANGLE; 
        this.length = length;
        this.width = width;
    }
    
    double area() { 
        switch(shape) {
            case RECTANGLE:
                return length * width;
            case CIRCLE:
                return Math.PI * (radius * radius);
            default:
                throw new AssertionError(shape);
        }
    }
}
  • 태그가 있는 클래스 단점
    • 열거 타입 선언, 태그 필드, switch 문 등등 쓸데없는 코드가 너무 많아 가독성이 나쁘다.
    • 많은 메모리 사용
    • 필드를 final로 선언하려면 쓰이지 않는 필드도 생성자에서 초기화해야 한다.
    • 태그 달린 클래스는 장황하고, 오류가 생기기 쉽고, 비효율적이다.
  • 자바와 같은 객체지향 언어에서는 타입 하나로 다양한 의미의 객체를 표현하는 나은 수단을 제공한다.
    • 클래스 계층 구조를 활용하는 서브타이핑(subtyping)
  • 태그 달린 클래스를 클래스 계층 구조로 변환
abstract class Figure { 
    abstract double area();
}

class Circle extends Figure { 
    final double radius;
    
    Circle(double radius) { this.radius = radius; }
    
    @Override double area() { return Math.PI * (radius * radius); } 
}

class Rectangle extends Figure { 
    final double length;
    final double width;
    
    Rectangle(double length, double width) { 
        this.length = length;
        this.width = width;
    }

    @Override double area() { return length * width; } 
}

아이템 24. 멤버 클래스는 되도록 static으로 만들라

  • 중첩 클래스(nested class)
    • 다른 클래스 안에 정의된 클래스
    • 자신을 감싼 바깥 클래스에서만 사용해야 한다.
    • 종류 : 정적 멤버 클래스. (비정적) 멤버 클래스, 익명 클래스, 지역 클래스
    • 정적 멤버 클래스를 제외한 나머지는 내부 클래스(inner class)
  • 정적 멤버 클래스
    • 다른 클래스 안에 선언되고, 바깥 클래스의 private 멤버에 접근할 수 있다.
    • 바깥 클래스와 함께 쓰일 때 유용한 public 도우미 클래스로 쓰인다.
    • 중첩 클래스의 인스턴스가 바깥 인스턴스와 독립적으로 존재할 수 있으면 정적 멤버 클래스로 만들어야 한다.
  • 비정적 멤버 클래스
    • 비정적 멤버 클래스의 인스턴스 메서드에서 정규화된 this를 사용해 바깥 인스턴스의 메서드를 호출하거나 바깥 인스턴스의 참조를 가져올 수 있다.
    • 정규화된 this : 클래스명.this
    • 바깥 인스턴스 없이 생성할 수 없다.
    • 비정적 멤버 클래스의 인스턴스 안에 관계 정보가 만들어져 메모리 공간을 차지하고, 생성 시간이 더 걸린다.
    • 어댑터 정의에 주로 사용된다.
    • 멤버 클래스에서 바깥 인스턴스에 접근할 일이 없으면 정적 멤버 클래스로 만들어준다.
  • 익명 클래스
    • 쓰이는 시점에 선언과 동시에 인스턴스가 만들어진다.
    • 선언한 지점에서만 인스턴스를 만들 수 있고, 클래스 이름이 필요한 작업은 수행할 수 없다.
    • 여러 인터페이스를 구현할 수 없고, 인터페이스를 구현하는 동시에 다른 클래스를 상속할 수 없다.
    • 주로 사용되는 경우는 팩터리 메서드 구현
  • 지역 클래스
    • 가장 드물게 사용되는 중첩 클래스
    • 지역변수를 선언할 수 있는 곳이면 어디서든 선언 가능
    • 유효 범위도 지역변수와 같다.
    • 멤버 클래스처럼 이름이 있고 반복해서 사용 가능
    • 익명 클래스처럼 비정적 문맥에서 사용될 때만 바깥 인스턴스를 참조할 수 있고, 정적 멤버는 가질 수 없고, 가독성을 위해 짧게 작성해야 한다.

아이템 25. 톱레벨 클래스는 한 파일에 하나만 담으라

  • 컴파일러가 한 클래스에 대한 정의를 여러 개 만들지 않도록, 소스 파일 하나에 톱레벨 클래스를 하나만 담는다.

📌 3장 : 모든 객체의 공통 메서드

아이템 10. equals는 일반 규약을 지켜 재정의하라

  • 아래의 상황 중 하나에 해당하면 재정의하지 않는 것이 좋다.
    • 각 인스턴스가 본질적으로 고유하다.
    • 인스턴스의 '논리적 동치성(logical equality)'을 검사할 일이 없다.
    • 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다.
    • 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없다.
  • 값 클래스 = Integer와 String 처럼 값을 표현하는 클래스
  • equals를 재정의해야 하는 경우?
    • 주로 값 클래스가 해당된다.
    • 값이 같은 인스턴스가 둘 이상 만들어지지 않음을 보장하는 인스턴스 통제 클래스면 equals 재정의를 하지 않아도 된다.
  • Object 명세 규약
    • equals 메서드가 쓸모 있으려면 모든 원소가 같은 동치류에 속한 어떤 원소와도 서로 교환할 수 있어야 한다.
      • 반사성(reflexivity) : 자기 자신과 같아야 한다.
      • 대칭성(symmetry) : 두 객체는 서로에 대한 동치 여부에 똑같이 답해야 한다.
      • 추이성(transitivity) : 첫 번째 객체와 두 번째 객체가 같고, 두 번째 객체와 세 번째 객체가 같으면, 첫 번째 객체와 세 번째 객체도 같아야 한다.
      • 일관성(consistency) : 두 객체가 같으면 앞으로도 영원히 같아야 한다.
      • null-아님

아이템 11. equals를 재정의하려거든 hashCode도 재정의하라

  • hashCode도 재정의하지 않으면, hashCode 일반 규약을 어기게 되어 해당 클래스의 인스턴스를 컬렉션의 원소로 사용할 때 문제가 생긴다.
  • Object 명세 규약
    • equals 비교에 사용되는 정보가 변경되지 않았으면, 애플리케이션이 실행되는 동안 그 객체의 hashCode 메서드는 몇 번 호출해도 일관되게 항상 같은 값을 반환해야 한다.
    • equals(Object)가 두 객체를 같다고 판단했다면, 두 객체의 hashCode는 똑같은 값을 반환해야 한다.
    • equals(Object)가 두 객체를 다르다고 판단했더라도, 두 객체의 hashCode가 서로 다른 값을 반환할 필요는 없다.
  • hashCode 재정의를 잘못하게 되면, 2번째 조항에 문제가 생긴다.
  • 논리적으로 같은 객체는 같은 해시코드를 반환해야 한다.
  • AutoValue 프레임워크를 사용하면 equals, hashCode를 자동으로 만들어준다.

아이템 12. toString을 항상 재정의하라

  • toString 일반 규약
    • 간결하면서 사람이 읽기 쉬운 형태의 유익한 정보를 반환해야 한다.
    • 모든 하위 클래스에서 이 메서드를 재정의하라고 한다.
  • 실전에서 toString은 그 객체의 모든 주요 정보를 반환하는 게 좋다. ex. 전화번호
  • toString이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하자.
  • 모든 구체 클래스에서 Object의 toString을 재정의하자. (상위 클래스에서 이미 알맞게 재정의한 경우는 예외)

아이템 13. clone 재정의는 주의해서 진행하라

  • Cloneable : 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스(mixin interface)
  • 문제점 : clone 메서드가 선언된 곳이 Cloneable이 아닌 Object + protected
    • Cloneable을 구현한다고 외부 객체에서 clone 메서드를 호출할 수 없다.
  • Cloneable 인터페이스는 Object의 protected 메서드인 clone 동작 방식을 결정한다.
  • 인스턴스에서 clone을 호출하면 그 객체의 필드들을 복사한 객체를 반환하고, 그렇지 않은 클래스의 인스턴스에서 호출하면 CloneNotSupportedException을 던진다.
  • 실무에서 Cloneable을 구현한 클래스는 clone 메서드를 public으로 제공하고, 사용자는 당연히 복제가 제대로 이뤄지 않아야 한다.
  • clone 메서드는 사실상 생성자와 같은 효과를 낸다.
  • clone은 원본 객체에 아무런 해를 끼치지 않고 복제된 객체의 불변식을 보장해야 한다.
@Override public Stack cloneO { 
  try {
    Stack result = (Stack) super.clone(); 
    result.elements = elements.clone(); 
    return result;
  } catch (CloneNotSupportedException e) { 
    throw new AssertionError();
  } 
}
Entry deepCopy() {
  return new Entry(key, value,
    next = null ? null : next.deepCopy());
}
  • 연결리스트를 복제하는 방법으로, deepCopy를 사용하는 것은 좋지 않다.
  • 재귀 호출 때문에 리스트의 원소 수만큼 스택 프레임을 소비하기 때문에 리스트가 길면 스택 오버플로를 일으킬 위험이 있다.
  • 이 문제를 피하려면, deepCopy를 재귀 호출 대신 반복자를 써서 순회하는 방법으로 수정한다.
Entry deepCopy() {
  Entry result = new Entry(key, value, next);
  for (Entry p = result; p.next != null; p = p.next)
    p.next = new Entry(p.next.key, p.next.value, p.next.next); 
  return res나It;
}
  • 생성자에서는 재정의 될 수 있는 메서드를 호출하지 않아야 한다.
    • clone 메소드에서도 하위 클래스에서 재정의한 메소드를 호출하면, 하위 클래스는 복제 과정에서 자신의 상태를 수정할 기회가 없어지기 때문에 원본과 복제본의 상태가 달라질 수 있다.
    • put(key, value) 메서드는 final 혹은 private 이어야 한다.
  • 상속용 클래스는 Cloneable을 구현하면 안된다.
@Override
protected final Object cloneO throws CloneNotSupportedException {
   throw new CloneNotSupportedException(); 
}
  • 기본 원칙은 '복제 기능은 생성자와 팩터리를 이용하는게 좋다.'
  • 배열은, clone 메서드 방식이 가장 깔끔하다.

아이템 14. Comparable을 구현할지 고려하라

  • Comparable 인터페이스의 메서드 compareTo
  • equals와의 차이점은?
    • compareTo는 단순 동치성 비교에 더해 순서 비교도 가능하고, 제너릭하다.
  • compareTo는 equals와 다르게, 타입이 다른 객체를 신경쓰지 않아도 된다.
  • 타입이 다르면 ClassCastException을 던져도 된다.
  • 비교를 활용하는 클래스의 예시
    • 정렬된 컬렉션 : TreeSet, TreeMap
    • 검색과 정렬 알고리즘을 활용하는 유틸리티 클래스 : Collections, Arrays
  • compareTo 메서드 작성 요령
    • Comparable은 타입을 인수로 받는 제네릭 인터페이스 이기 때문에, compareTo 메서드의 인수 타입은 컴파일 타임에 정해진다.
    • null을 인수로 넣어 호출하면 NullPointerException을 던져야 한다.
    • 각 필드가 동치인지 비교하는 게 아니라 순서를 비교한다.
    • compareTo 메서드에서 필드 값을 비교할 때 <와 > 연산자는 쓰지 말아야 한다.
    • 그 대신 박싱된 기본 타입 클래스가 제공하는 정적 compareTo 메서드나 Comparator 인터페이스가 제공하는 비교자 생성 메서드를 사용한다.
    • 핵심 필드가 여러개인 경우, 중요 필드부터 비교한다.
public int compareTo(PhoneNumber pn) {
  int result = Short.compare(areaCode, pn.areaCode); // 가장 중요
  if (result = 0) {
    result = Short.compare(prefix, pn.prefix); // 두 번째로 중요
    if (result = 0)
      result = Short.compare(lineNumf pn.lineNum); // 세 번째로 중요
  }
  return result; 
}
  • 해시코드 값의 차를 기준으로 하는 비교 : 추이성 위배
static Comparator<Object> hashCodeOrder = new Comparatero() { 
  public int compare(Object ol, Object o2) {
    return ol.hashCode() - o2.hashCode(); 
    }
};
  • 정적 compare 메서드를 활용한 비교자
static Comparator<0bject> hashCodeOrder = new Comparatoro() { 
  public int compare(Object ol, Object o2) {
    return Integer.compare(ol.hashCode(), o2.hashCode()); 
    }
};
  • 비교자 생성 메서드를 활용한 비교자
static Comparator<Object> hashCodeOrder = 
  Comparator.comparinglnt(o -> o.hashCode());

📌 1장 : 들어가기

  • 컴포넌트 : 개별 메서드부터 여러 패키지로 이뤄진 복잡한 프레임워크까지 재사용 가능한 모든 소프트웨어 요소
  • 자바 자료형(type)
    • 참조 타입(reference type) : 인터페이스, 클래스, 배열
    • 기본 타입(primitive type)
  • annotation : 인터페이스의 일종
  • enum : 클래스의 일종

📌 2장 : 객체 생성과 파괴

아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라

  • 생성자 : 클라이언트가 클래스의 인스턴스를 얻는 수단
  • 클래스는 생성자 대신에 정적 팩토리 메소드(static factory method)를 제공할 수 있다.
  • 정적 팩토리 메소드의 장점
    1. 이름을 가질 수 있다.
      • 생성자보다 객체의 특성을 이름으로 묘사할 수 있다.
    2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
      • 인스턴스 통제 클래스 : 반복되는 요청에 같은 객체를 반환하는 식으로 정적 팩토리 방식의 클래스는 언제 어느 인스턴스를 살아 있게 할지 통제할 수 있다.
      • 인스턴스를 통제하면, 클래스를 싱글톤 패턴 or 인스턴스화 불가로 만들 수 있다.
      • 또한, 불변 값 클래스에서 동치 인스턴스가 단 하나임을 보장할 수 있다.
      • ex. a == b 일 때, a.equals(b)가 성립
    3. 반환 타입의 하위 타입 객체를 반화할 수 있는 능력이 있다.
      • 구현 클래스를 공개하지 않아도, 그 객체를 반환할 수 있어 API 생성을 할 때 작게 유지 가능하다.
    4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
    5. 정적 팩토리 메소드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
      • 서비스 제공자 프레임워크 3개의 핵심 컴포넌트
        • 서비스 인터페이스(service interface) : 구현체의 동작을 정의
        • 제공자 등록 API(provider registration API) : 제공자가 구현체를 등록할 때 사용
        • 서비스 접근 API(service access API) : 클라이언트가 서비스의 인스턴스를 얻을 때 사용
      • 종종 서비스 제공자 인터페이스(service provider interface)라는 네 번째 컴포넌트도 쓰인다.
        • 서비스 인터페이스의 인스턴스를 생성하는 팩토리 객체를 설명해준다.
      • ex. JDBC
        • 서비스 인터페이스 : Connection
        • 제공자 등록 API : DriverManager.registerDriver
        • 서비스 접근 API : DriverManager.getConnection
        • 서비스 제공자 인터페이스 : Driver
  • 정적 팩토리 메소드 패턴의 단점
    1. 상속을 하려면 public, protected 생성자가 필요하다. 정적 팩토리 메소드만 제공하면 하위 클래스를 만들 수 없다.
    2. 정적 팩토리 메소드는 프로그래머가 찾기 어렵다.
      • 알려진 규약을 따라 이름을 짓는 것으로 문제점을 완화한다.
      • from : 매개 변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메소드
        Date d = Date.from(instant);
      • of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메소드
        Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
      • valueOf : from과 of의 자세한 버전
        BigInteger prime = BingInteger.valueOf(Integer.MAX_VALUE);
      • instance 혹은 getInstance : (매개 변수를 받으면) 매개 변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않는다.
        StackWalker luke = StackWalker.getInstance(options);
      • create 혹은 newInstance : instance, getInstance 와 같지만 매번 새로운 인스턴스를 생성해 반환한다.
        Object newArray = Array.newInstance(classObject, arrayLen);
      • getType : getInstance 와 같지만, 생성할 클래스가 아닌 다른 클래스에 팩토리 메소드를 정의할 때 쓴다.
        FileStore fs = Files.getFilesStore(path);
      • newType : newInstance 와 같지만, 생성할 클래스가 아닌 다른 클래스에 팩토리 메소드를 정의할 때 쓴다.
        BufferReader br = Files.newBufferReader(path);
      • type : getType 과 newType 의 간결한 버전
        List<Complaint> litany = Collections.list(legacyLitany);

아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라

  • 정적 팩토리, 생성자는 둘 다 선택적 매개변수가 많으면 적절히 대응하기 힘들다.
  • 점층적 생성자 패턴(telescoping constructor pattern)
    • 필수 매개변수만 받는 생성자, 필수 매개변수와 선택 매개변수 1개만 받는 생성자, ..., 형태들로 선택 매개변수를 전부 다 받는 생성자 까지 늘려가는 방식
  • 점층적 패턴을 사용할 수 있지만, 매개변수의 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어려워진다.
  • 선택 매개 변수가 많은 경우, 두 번째 대안 -> 자바빈즈 패턴(JavaBeans pattern)
    • 매개변수가 없는 생성자로 객체를 만들고, setter 으로 원하는 매개변수 값을 설정하는 방식
    • 하지만, 객체 하나를 만들기 위한 여러 메소드 호출의 필요성과 객체 생성 전까지 일관성이 무너진다.
  • 세번째 대안, 빌더 패턴(Builder pattern)
    • 점층적 생성자 패턴의 안전성 + 자바빈즈 패턴의 가독성
    • 클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수 만으로 생성자(혹은 정적 팩토리)를 호출해 빌더 객체를 얻는다.
    • 그 후에 빌더 객체가 제공하는 setter 으로 원하는 선택 매개변수를 설정
    • 마지막으로, 매개 변수가 없는 build 를 호출해서 필요한 객체를 얻는다.
    • 플루언트(fluent) API 혹은 메소드 연쇄(method chaining)
      • 빌더의 setter 들은 빌더 자신을 반환하기 때문에 연쇄적으로 호출할 수 있다.
      • 메소드 호출이 흐르듯 연결된다는 의미
public class NutritionFacts { 
    private final int servingSize; 
    private final int servings; 
    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 servings;

        // 선택 매개변수 - 기본값으로 초기화한다.
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;
    
        public Builder(int servingSize, int servings) { 
            this.servingSize = servingSize; 
            this.servings = servings;
        }
        
        public Builder calories(int val)
            { calories = val; return this; }
            
        public Builder fatdnt val)
            { fat = val; return this; }
        public Builder sodium(int val)
            { sodium = val; return this; }
        public Builder carbohydrate(int val)
            { carbohydrate = 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 ();
  • 잘못된 매개변수는 빌더의 생성자, 메소드에서 검사하는 것이 좋다.
  • 불변성을 위해서는 빌더로부터 매개변수를 복사하고 해당 객체 필드들을 검사해야 한다.
  • 어떤 매개 변수가 어떻게 잘못되었는지는 IllegalArgumentException를 사용한다.
  • 빌더 패턴은 계층적으로 설계된 클래스와 함께 사용하기 좋다.
public abstract class Pizza {
    public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE } 
    final Set<Topping> toppings;

    abstract static class Builder<T extends Builder<T» { 
        EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class); 
        public T addTopping(Topping topping) {
            toppings.add(Objects.requireNonNull(topping));
            return self();
        }
        
        abstract Pizza build();
        // 하위 클래스는 이 메서드를 재정의(overriding)하여 
        // "this"를 반환하도록 해야 한다.
        protected abstract T self();
    }
 
    Pizza(Builder<?> builder) {
        toppings = builder.toppings.clone(); // 아이템 50 참조
    }   
}
 

아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라

  • 싱글톤(singleton) : 인스턴스를 오직 하나만 생성할 수 있는 클래스
    • 전형적인 예시 : 무상태(stateless) 객체, 설계상 유일해야 하는 시스템 컴포넌트
    • 클래스를 싱글톤으로 만들면 클라이언트가 테스트하기 어려울 수 있다.
  • 싱글톤을 만드는 방식 : 생성자는 private 으로 감추고, 유일하게 인스턴스에 접근할 수 있는 수단으로 public static 멤버를 하나 만든다.
  • 방식1 : public static 멤버가 final 필드
  • 방식2 : 정적 팩토리 메소드를 public static 멤버로 제공
    • 장점 : API를 바꾸지 않아도 싱글톤이 아니게 변경 가능하다. 정적 팩토리를 제너릭 싱글톤 팩토리로 만들 수 있다. 정적 팩토리 메소드 참조를 공급자(supplier)로 사용할 수 있다.
    • 장점이 필요하지 않으면 방법1이 더 좋다.
  • 싱글톤 클래스 직렬화
    • Serializable 을 구현하는 것 만으로는 부족하다.
    • 모든 인스턴스 필드를 일시적(transient)으로 선언하고, readResolve 메소드를 제공해야 한다.
    • 이렇게 하지 않으면, 직렬화된 인스턴스를 역직렬화할 때마다 새로운 인스턴스가 만들어진다.
public class Elvis {
    public static final Elvis INSTANCE = new Elvis();
    private Elvis() { ... }
    public void leaveTheBuilding() { ... } 
}
public class Elvis {
    private static final Elvis INSTANCE = new Elvis(); private Elvis() { ... }
    public static Elvis getlnstance() { return INSTANCE; }
    public void leaveTheBuilding() { ... } 
}

아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용하라

  • 생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자를 만든다.
  • 추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없다.
  • 컴파일러가 기본 생성자를 만드는 경우는 명시된 생성자가 없을 때 뿐이기 때문에
  • private 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다.

아이템 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

  • 정적 유틸리티를 잘못 사용한 예시
public class Spellchecker {
  private static final Lexicon dictionary = ...;
  private SpellChecker() {} // 객체 생성 방지
  public static boolean isValid(String word) { ... }
  public static List<String> suggestions(String typo) { ... } 
}
  • 싱글톤을 잘못 사용한 예시
public class Spellchecker {
  private final Lexicon dictionary = ...;
  private Spellchecker(...) {}
  public static Spellchecker INSTANCE = new SpellChecker(••.);
  public boolean isValid(String word) { ... }
  public List<String> suggestions(String typo) { ... } 
}
  • 사용하는 자원에 따라 동작이 달라지는 클래스에는 유틸리티 클래스나 싱글톤 방식이 적합하지 않다.
  • 인스턴스를 생성할 때 생성자가 필요한 자원 넘겨주는 방식
  • 의존 객체 주입의 한 형태 : 유연성과 테스트 용이성을 높여준다.
public class Spellchecker {
  private final Lexicon dictionary;
  public Spellchecker(Lexicon dictionary) {
    this.dictionary = Objects.requireNonNull(dictionary); 
  }
  public boolean isValid(String word) { ... }
  public List<String> suggestions(String typo) { ... } 
}

아이템 6. 불필요한 객체 생성을 피하라

  • 생성자 대신 정적 팩토리 메소드를 제공하는 불변 클래스는 정적 패곹리 메소드를 사용해 불필요한 객체 생성을 피할 수 있다.
  • ex. Boolean(String) 생성자 대신에 Boolean.valueOf(String) 팩토리 메소드를 사용하는 것이 좋다.
String s = new StringC'bikini"); // 따라 하지 말 것!
String s = "bikini";
  • 생성 비용이 아주 비싼 객체도 있다. 자신이 만드는 객체가 비싼 객체인지 알 방법이 명확하게 없기 때문에
  • 정규 표현식을 사용해 성능을 끌어올릴 수 있다.
  • 하지만, String.matches 메소드를 사용하기 때문에 성능이 중요한 상황에서 반복해 사용하기 적합하지 않다.
  • Pattern 인스턴스는 한 번 쓰고 버려지기 때문에 가비지 컬렉션 대상이 된다.
  • Pattern 은 입력받은 정규표현식에 해당하는 유한 상태 머신(finite state machine)을 만들기 때문에 인스턴스 생성 비용이 높다.
static boolean isRomanNumeraI(String s) { 
  return s.matchesC'^?#. ()*CM[MD] |D?C{0,3})"
    + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
  • 성능을 개선하기 위해서, 필요한 정규 표현식을 표현하는 Pattern 인스턴스를 클래스 초기화 과정에서 직접 생성해 캐싱하고
  • 나중에 메소드가 호출될 때마다 이 인스턴스를 재사용한다.
public class RomanNumerals {
  private static final Pattern ROMAN = Pattern.compile(
    u("*C(?[=M.)DM]|D?C{0,3})"
      + "(X[C니 |L?X{0,3})(I[XV]|V?I{0,3})$");
  static boolean isRomanNumeral(String s) { 
   return ROMAN.matcher(s).matches();
  } 
}
private static long s니m() { 
  Long sum = 0L; // Long으로 선언해서 불필요한 Long 인스턴스가 2^31개 만들어진다.
  for (long i = 0; i <= Integer.MAX_VALUE; i++) 
    sum += i;
  return sum; 
}
  • 불필요한 객체를 만드는 또 다른 예시 : 오토박싱(auto boxing)
  • 박싱된 기본 타입보다 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하기

아이템 7. 다 쓴 객체 참조를 해제하라

  • 메모리 누수가 일어나는 위치
  • stack은 element 배열로 저장소 풀을 만들고 원소들을 관리하기 때문에
  • 가비지 컬렉션은 비활성 영역에 대해 알 수 없다.
  • 비활성 영역이 되는 순간 null 처리 해준다.
public Object pop() { 
  if (size = 0)
  throw new EmptyStackException(); 
  return elements[—size];
}

// 메모리 누수 개선
public Object pop() { 
  if (size = 0)
  throw new EmptyStackException(); 
  Object result = elements[—size]; 
  elements[size] = null; // 다 쓴 참조 해제 
  return result;
}
  • 객체 참조를 null 처리하는 일은 예외적이어야 한다.
  • 다 쓴 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수를 유효 범위(scope) 밖으로 밀어내는 것이다.
  • 자기 메모리를 직접 관리하는 클래스는 항시 메모리 누수에 주의한다.
  • 캐시도 메모리 누수를 일으키기 쉽다. 객체 참조를 캐시에 넣고 놔두는 경우가 있다.
    • weakHashMap 사용 : 다 쓴 entry는 즉시 자동 제거
    • Scheduled ThreadPoolExecutor 같은 백그라운드 스레드를 사용하거나 LinkedHashMap은 removeEldestEnry 메소드를 사용

아이템 8. finalizer 와 cleaner 사용을 피하라

  • 자바의 두 가지 객체 소멸자
    • finalizer : 예측 불가능, 상황에 따라 위험할 수 있어 일반적으로 불필요
    • cleaner : finalizer보다 덜 위험하지만, 예측 불가능, 느리고 일반적으로 불필요하다.
  • finalizer, cleaner 로는 제때 실행되어야 하는 작업은 절대 할 수
  • 상태를 영구적으로 수정하는 작업에서는 절대 finalizer, cleaner 에 의존해서는 안된다.
    • ex. 데이터베이스 같은 공유 자원의 lock 해제를 맡기면 분산 시스템 전체가 서서히 멈추게 된다.
  • finalizer 는 가비지 컬렉터의 효율을 약 50배 떨어뜨리기 때문에 성능 저하 문제가 있다.
  • finalizer, cleaner 대신에 사용할 방법 : AutoCloseable
  • AutoCloseable
    • 클라이언트에서 인스턴스를 다 쓰고, close 메소드를 호출하면 된다.

아이템 9. try-finally 보다는 try-with-resources 를 사용하라

  • 자바에서는 close 메소드를 호출해서 직접 닫아줘야하는 경우가 있다.
  • try-finally 를 사용해서 자원을 안전하게 닫을 수 있지만, 자원이 둘 이상이면 지저분해진다.
  • 자바 7에서 try-with-resources 덕에 문제 해결
    • 해당 자원이 AutoCloseable 인터페이스를 구현해야 한다.
static void copy(String src, String dst) throws lOException { 
  try (Inputstream in = new FilelnputStream(src);
    Outputstream out = new FileOutputStream(dst)) { 
      byte[] buf = new byte[BUFFER_SIZE];
      int n;
      while ((n = in.read(buf)) >= 0)
        out.write(buf, 0, n);
  } 
}

+ Recent posts