객체 생성과 파괴


1. 생성자 대신 정적 팩토리 메서드를 고려하라

클래스의 인스턴스를 얻는 전통적인 수단은 public 생성자이다.
클래스는 생성자와 별도로 static factory method를 제공하며 다음과 같은 장단점이 있다.

장점

1. 이름을 가질 수 있다.

2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
ex.Boolean.valueOf(boolean)
불변 클래스는 미리 인스턴스를 만들거나 새로 생성한 인스턴스를 캐싱해 재활용할 수 있으므로 메모리 측면에서 유리하다.
이렇게 언제 어느 인스턴스를 살아있게 할지를 철저히 통제할 수 있다. 이를 인스턴스 통제 클래스라고 한다.

인스턴스를 통제하면 다음과 같이 만들 수 있다.

3. 하위 타입 객체를 반환할 수 있다.

4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다.

5. 정적 팩토리 메서드를 작성하는 시점에 반환할 객체의 클래스가 존재하지 않아도 된다.
이러한 유연함은 Service Provider 프레임워크의 근간이 된다. 대표적인 예로 JDBC가 있다.

위에서 설명한 3개의 핵심 컴포넌트와 더불어 종종 서비스 제공자 인터페이스라는 네 번째 컴포넌트가 쓰이기도 한다.
이는 서비스 인터페이스의 인스턴스를 생성하는 팩토리 객체를 설명해준다.
서비스 제공자 인터페이스가 없다면 각 구현체를 인스턴스로 만들 때 리플렉션을 사용해야 한다.


단점

1. 상속을 하려면 public이거나 protected 생성자가 필요하므로 정적 팩토리 메서드만 제공하면 하위 클래스를 만들 수 없다.
컬렉션 프레임 워크의 유틸리티 구현 클래스는 private 생성자만 제공하므로 상속이 불가하다.
이러한 제약은 상속보다 컴포지션을 사용하도록 유도되어 오히려 더 장점으로 작용한다.

2. 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.
흔히 사용하는 명명 방식을 지켜보자.


정적 팩터리 메서드는 각각의 쓰임새가 있으니, 장단점을 잘 인식하고 무작정 public 생성자만 사용하는 습관을 고쳐보자.




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

매개변수 개수가 많을 때, 다음과 같은 방법들을 고려해볼 수 있다.

방법(1) 점층적 생성자 패턴

public class Exam {
    private final int a;
    private final int b;
    private final int c;
    private final int d;
    
    public Exam(int a, int b) {
        this(a, b, 0);
    }
    
    public Exam(int a, int b, int c) {
        this(a, b, c, 0);
    }
    
    public Exam(int a, int b, int c, int d) {
        this(a, b, c, d);
    }
}

위 코드와 같이 필수 매개변수만 받는 생성자, 필수 매개변수와 선택 매개변수 1개를 받는 생성자, … 형태로 선택 매개변수를 전부 다 받는 생성자까지 늘려가는 방식이다.
그러나 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.


방법(2) 선택 매개변수가 많다면 자바빈즈 패턴 (Java Beans)

매개변수가 없는 생성자로 객체를 만들어 Setter 메서드들을 호출해 원하는 매개변수의 값을 설정하는 방식이다.
그러나 객체를 하나 만들려면 메서드 여러 개를 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성(consistency)이 무너진 상태에 놓이게 된다.
자바빈즈 패턴에서는 클래스를 불변으로 만들 수 없으며 스레드 안전성을 얻으려면 개발자가 추가 작업을 해야한다.


방법(3) 안전성과 가독성을 겸비한 빌더 패턴 (Builder Pattern)

객체를 직접 만드는 대신 필수 매개변수만으로 생성자(혹은 정적 팩토리)를 호출해 빌더 객체를 얻는다.
빌더 객체가 제공하는 일종의 Setter 메서드로 원하는 선택 매개변수들을 설정하고 매개변수가 없는 build 메서드를 호출해 필요한 객체를 얻는다.

빌더의 Setter는 자신을 반환하기 때문에 연쇄적으로 호출할 수 있다. > 플루언트 API(fluent API) / 메서드 연쇄(method chaining)
빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다. 각 계층의 클래스에 관련 빌더를 멤버로 정의한다.

public abstract class Pizza {
    public enum Topping { ... }
    final Set<Topping> toppings;
    
    // 추상 클래스는 추상 빌더를, 구체 클래스는 구체 빌더를 갖게 한다. 
    abstract static class Builder<T extends Builder<T>> {
        . . .
        return self();
    }
    
    abstract Pizza build();
    
    // 하위 클래스는 이 메서드를 재정의(overriding)하여   
    // this를 반환하도록 해야한다. 
    protected abstract T self();
    
}


빌더 패턴은 상당히 유연하다. 빌더 하나로 여러 객체를 순회하면서 만들 수 있고, 빌더에 넘기는 매새변수에 따라 다른 객체를 만들 수도 있다.
장점만 있는 것은 아니다. 객체를 만들려면 그에 앞서 빌더부터 만들어야 한다. 빌더 생성 비용이 크지는 않지만 성능에 민감한 상황에서는 문제가 될 수 있다.
생성자나 정적 팩토리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 게 더 낫다.




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

싱글톤이란 인스턴스를 오직 하나만 생성할 수 있는 클래스이다.
예를 들어 함수와 같은 무상태 객체나 설계상 유일해야 하는 시스템 컴포넌트를 들 수 있다.

그러나 클래스를 싱글톤으로 만들면 타입을 인터페이스로 정의하지 않으면
싱글톤 인스턴스를 가짜(mock) 구현으로 대체할 수 없기 때문에 이를 사용하는 클라이언트를 테스트하기 어렵다.

싱글톤을 만드는 방식

1. 생성자 private, 유일한 인스턴스에 접근할 수 있는 수단은 public static final 멤버


2. 정적 팩토리 메서드를 public static 멤버로 제공
정적 팩토리 메서드 방식의 장점

위에서 설명한 두가지 방법으로 인스턴스가 전체 시스템에서 하나뿐임이 보장된다.
그러나 예외가 있는데 권한이 있는 클라이언트 리플렉션 API인 AccessibleObject.setAccessible을 사용해 private 생성자를 호출할 수 있다.
이 공격을 방어하고 싶다면 생성자를 수정하여 객체가 두 번 생성되려 할 때 예외를 던지게 하면 된다.

3. 원소가 하나인 열거 타입을 선언
싱글톤 클래스를 직렬화하려면 단순히 Serializable을 구현한다고 선언하는 것만으로는 부족하다.
모든 인스턴스 필드를 일시적(transient)이라고 선언하고 readResolve 메서드를 제공해야 한다.

// 싱글톤임을 보장해주는 readResolve 메서드 
private Object readResolve() {
    // 진짜 인스턴스를 반환하고 가짜는 가비지 컬렉터에 맡긴다. 
    return INSTANCE;
}

public enum Elvis {
    INSTANCE;
    
    public void leaveTheBuilding() { ... }
}


public 필드 방식과 비슷한 위 방법은 더 간결하고 추가 노력없이 직렬화할 수 있고, 리플렉션 공격도 막아준다.
대부분 상황에서 원소가 하나뿐인 열거 타입이 싱글톤을 만드는 가장 좋은 방법이다.

단, 만들려는 싱글톤이 Enum 외의 클래스를 상속해야 한다면 이 방법은 사용할 수 없다.
열거 타입이 다른 인터페이스를 구현하도록 선언할 수는 있다.




4. 인스턴스화를 막으려면 private 생성자를 사용하라.

정적 메서드와 정적 필드만 담은 클래스를 만들 때가 있을 것이다.
객체지향적으로 좋아 보이지는 않지만 분명 쓰임새가 있다.


정적 멤버만 담은 유틸리티 클래스는 인스턴스로 만들어 쓰려고 설계할 게 아니다.
그러나 생성자를 명시하지 않으면 컴파일러가 자동으로 매개변수가 없는 기본 생성자를 만들어준다.

추상 클래스는 하위 클래스를 만들어 인스턴스화하면 그만이기 때문에 인스턴스화를 막을 수 없다.
이때 인스턴스화를 막는 간단한 방법은 private 생성자를 추가하는 것이다.
이 방식은 상속을 불가능하게 하는 효과도 있다. 모든 생성자는 상위 클래스의 생성자를 호출하게 되는데
이를 private로 선언했으니 하위 클래스가 상위 클래스의 생성자에 접근할 길이 없다.




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

맞춤법 검사기 SpellChecker은 사전(Dictionary)에 의존한다.
이렇게 자원에 의존하는 클래스의 경우 정적 유틸리티 클래스로 구현한 경우를 볼 수 있다.

정적 유틸리티를 잘못 사용한 경우

public class SpellChecker {
    private static final Lexicon dictionary = ...;
    
    private SpellChecker() {} // 객체 생성 방지
    
    public static boolean isValid(String word) { ... }
    public static List<String> suggestions(String type) { ... }
}


싱글톤을 잘못 사용한 경우

public static SpellChecker INSTANCE = new SpellChecker(...);


위 예제들은 유연하지 않고 테스트하기도 어렵다.

사전은 언어별로 따로 있고 특수 어휘용 사전, 테스트용 사전도 필요하다.
SpellChecker가 여러 사전을 사용할 수 있도록 만들어보자.

필드에서 final 한정자를 제거하고 다른 사전으로 교체하는 메서드를 추가할 수 있지만
이 방식은 오류를 내기 쉽고 멀티 스레드 환경에서는 쓸 수 없다.
사용하는 자워에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글톤 방식은 적합하지 않다.

다음 조건을 만족해야 한다.

위 조건을 만족하는 패턴은 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식으로 의존 객체 주입의 한 형태이다.

private final Lexicon dictionary;

public SpellChecker(Lexicon dictionary) {
    this.dictionary = Objects.requireNonNull(dictionary);
}

위와 같은 의존 객체 주입은 불변을 보장하여 여러 클라이언트가 의존 객체들을 안심하고 공유할 수 있고
생성자, 정적 팩토리, 빌더 모두에 똑같이 응용할 수 있다.

의존 객체 주입이라 하는 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 개선해준다.




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

똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다.
재사용은 빠르고 세련되며 특히 불변 객체는 언제든 재사용할 수 있다.


객체가 불변이라면 재사용해도 안전함이 명확하다. 그러나 훨씬 덜 명확하거나 심지어 직관에 반대되는 상황도 있다.
어댑터를 생각해보자. 어댑터는 실제 작업은 뒷단 객체에 위임하고 자신은 제 2의 인터페이스 역할을 해주는 객체이다.
어댑터는 뒷단 객체만 관리하면 되기 때문에 뒷단 객체 하나당 어댑터 하나씩만 만들어지면 충분하다.

EX1) Map 인터페이스의 KeySet 메서드
Map 객체 안의 key를 전부 담은 Set 뷰를 반환한다.
매번 같은 Set 인스턴스를 반환할까?
반환된 Set 인스턴스가 일반적으로 가변이라도 반환된 인스턴스가 기능적으로 모두 똑같다.
모두 같은 Map 인스턴스를 대변하기 때문에 KeySet이 뷰 객체를 여러 개 만들어도 상관은 없지만 그럴 필요도 없고 이득도 없다.

EX2) 오토박싱(auto boxing)
오토박싱은 프로그래머가 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해주는 기술이다.
구분을 흐려주지만 완전히 없애주는 것은 아니다.

private static long sum() {
   Long sum = 0L;
   for(long i = 0; i <= Integer.MAX_VALUE; ++i) {
      sum += i;
   }
   
   return sum;
}

위 프로그램이 정확한 답을 내기는 한다. 하지만 제대로 구현했을 때보다 훨씬 느리다.
sum 변수를 long이 아닌 Long으로 선언해서 불필요한 Long 인스턴스가 약 231개나 만들어진 것이다.

박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의해야 한다.




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

메모리 누수에 취약한 상황은 다음과 같다.

1) Stack

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

스택이 커졌다가 줄어들 때 스택에서 꺼내진 객체들을 가비지 컬렉터가 회수하지 않는다.
여기서 다 쓴 참조는 elements 배열의 ‘활성 영역’ 밖의 참조들이다.

가비지 컬렉션 언어에서 메모리 누수를 찾기 까다롭다.
객체 참조 하나를 살려두면 가비지 컬렉터는 그 객체 뿐만 아니라
그 객체가 참조하는 모든 객체를 회수해가지 못한다.

해결 방법: 참조를 다 썼을 때 null 처리한다.


2) 캐시

객체 참조를 캐시에 넣고 그 객체를 다 쓴 뒤로도 한참을 놔두는 일을 자주 접할 수 있다.

해결 방법


3) 리스너(listener), 콜백(callback)

클라이언트가 콜백을 등록만하고 명확히 해지하지 않으면 콜백이 쌓인다.

해결 방법: 콜백을 약한 참조(weak reference)로 저장하면 가비지 컬렉터가 즉시 수거한다.




8. finalizer와 cleaner 사용을 피하라.

Java는 두 가지 객체 소멸자를 제공한다.
그러나 예측할 수 없고 일반적으로 불필요하기 때문에 기본적으로 쓰지 말아야 한다.


finalizer의 쓰임새

1) 자원의 소유자가 close 메서드를 호출하지 않는 것에 대비한 안전망 역할
2) 네이티브 피어(native peer): 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체
네이티브 피어는 자바 객체가 아니니 가비지 컬렉터는 그 존재를 알지 못한다.
자바 피어를 회수할 때 네이티브 객체까지 회수하지 못하므로 finalizer나 cleaner를 사용
단 성능 저하를 감당할 수 없으면 close 메서드를 사용해야 한다.


그렇다면 파일이나 스레드 등 종료해야 할 자원은 어떻게?

AutoCloseable을 구현해주고, 클라이언트에서 인스턴스를 다 쓰고 나면 close 메서드를 호출하면 된다.




9. try-finally 보다는 try-with-resource를 사용하라.

자바 라이브러리에 close 메서드를 호출해 직접 닫아줘야 하는 자원이 많다.
ex. InputStream, OutputStream, java.sql.Connection 등
자원 닫기는 클라이언트가 놓치기 쉬워서 예측할 수 없는 성능 문제로 이어질 수 있다.

이런 자원 중 안전망으로 finalizer를 활용하고는 있지만 전통적으로
자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰였다.

BufferedReader br = new BufferedReader(new FileReader(path));

try{
  return br.readLine();
} finally {
  br.close();
}

위 메서드 실행 중 기기에 물리적인 문제가 생긴다면?

1) readLine 메서드가 예외를 던진다.
2) close 메서드도 위와 같은 이유로 실패한다.

이런 상황이라면 두 번째 예외가 첫 번째 예외를 완전히 집어삼켜 버린다.
그러면 스택 추적 내역에 첫 번째 예외에 관한 정보는 남지 않게 되며,
실제 시스템에서의 디버깅을 어렵게 한다.

위 문제는 자바 7에서 등장한 try-with-resources로 해결되었다.
이 구조를 사용하려면 해당 자원이 AutoCloseable 인터페이스를 구현해야 한다.