2장. 객체 생성과 파괴

GOAL
1. 객체를 만들어야 할 때와 만들지 말아야 할 때를 구분하기
2. 올바른 객체 생성 방법과 불필요한 생성을 피하는 방법
3. 제때 파괴됨을 보장하고, 파괴 전에 수행해야 할 정리 작업을 관리하기

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

public static Boolean valueOf(boolean b) {
    return b? Boolean.TRUE : Boolean.FALSE;
}

클래스는 클라이언트에 public 생성자 대신 정적 팩터리 메서드를 제공할 수 있다.

장점.

1. 이름을 가질 수 있다.

정적 팩터리는 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사 가능하다.
코드를 보면 기존에 static메서드를 쓰지 않으면 클래스이름인 Foo로 생성자를 만든다. 그러나 정적 팩터리 메서드로 withName이라는 이름을 지어줬다.

public Foo(String name) {
    this.name = name;
}

public static Foo withName(String name) {
   return new Foo(name);
}

public static void main(String[] args){
    Foo foo = new Foo("Hye Yeon"); // 기존 public 생성자 만들기
    foo.withName("Hye Yeon"); // 이 코드를 읽는게 반환될 객체의 특성을 알 수 있음.    
}

한 클래스에 시그니처가 같은 생성자가 여러개 필요할 것 같으면, 생성자를 정적 팩터리 메서드로 바꾸고 각각의 차이를 잘 드러내는 이름을 지어주자.
아래 코드는 오류다. 그 이유는 똑같은 시그니처가 있기 때문이다. 이럴때 정적 팩터리 메서드는 언제든지 이름을 지어줄 수있기 때문에 아래코드를 오류없이 구현할 수 있다.

public Foo(String name){
    this.name = name;
}
public Foo(String address){
    this.address = address;
}
public static Foo withName(String name){
    return new Foo(name);
}
public static Foo withAddress(String address){
    Foo foo = new Foo();
    foo.address = address;
    return foo;
}

2. 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.

불변 클래스는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.
Boolean.valueOf(booelan)도 이경우에 해당한다.

Foo foo = new Foo("hyeyeon"); //이는 매번 새로운 인스턴스를 생성한다. 

private static final Foo GOOD_NIGHT = new Foo();
private static Foo getFoo() {
    return GOOD_NIGHT;
}

public static void main(String[] args){
    foo.getFoo(); // 이미 만들어놓은 인스턴스 리턴
}

반복되는 요청에 같은 객체를 반환하는 식으로 정적 팩터리 방식의 클래스는 언제 어느 인스턴스를 살아 있게 할지를 철저히 통제할 수 있다.
인스턴스를 통제하는 이유

  1. 클래스를 싱클턴으로 만들 수 있다.
  2. 인스턴스화 불가로 만들 수 있다.
  3. 불변 값 클래스에서 동치인 인스턴스가 단 하나뿐임을 보장할수 있다.
  4. 인스턴스 통제는 플라이웨이트 패턴의 근간이 되며, 열거타입은 인스턴스가 하나만 만들어짐을 보장한다.

3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.

반환할 객체의클래스를 자유롭게 선택할 수 있는 '엄청난 유연성'.
리턴타입에는 Interface만 노출시키고, 클라이언트는 실제 구현체는 뭔지 몰라도 된다.

예시로 Collections는 45개 클래스를 공개하지 않기 때문에 API가 작아진 것은 물론 프로그래머가 API를 사용하기 위해 익혀야 하는 개념의 수와 난이도도 낮췄다. 즉, 프로그래머는 명시한 인터페이스대로 동작할 것임을 알아서 실제 구현 클래스를 찾아보지 않아도 된다.

Collections.Sort(list); //우리는 list를 sort시킬 것임을 알아서 굳이 구현체 안찾아봐도 된다.

4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

반환 타입의 하위 타입이기만 하면, 어떤 클래스의 객체를 반환하든 상관없다.
아래 첫번째 예제에서 반환타입이 Foo라고 해서 반드시 Foo를 리턴할 필요는 없다.
BarFoo의 경우 Foo를 상속받는데, 이떄는 하위 타입이기 때문에 반환가능하다.

public static Foo withName(String name) {
     return new Foo(name);
}

public static Foo getFoo(boolean flag) {
    return flag ? new Foo() : new BarFoo();
}

static class BarFoo extends Foo {

}

EnumSet클래스의 경우 오직 정적 팩터리만 제공하는데, 우너소의 수에따라 하위 클래스 중 하나의 인스턴스를 반환한다.
원소가 64개이하면 RegularEnumSet, 65개 이상이면 JumboEnumSet.
하지만 클래이언트는 이 두클래스의 존재를 모르겠지! 클라이언트한테는 그냥 EnumSet의 하위 클래스이기만 하면 되는 것이다.

EnumSet<Color> colors = EnumSet.allof(Color.class);
EnumSet.of(RED, WHITE); // 원소가 2개

enum Color {
    RED, BLUE, WHITE
}

5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

장점 3,4와 비슷한 개념이다. 유연함에 대해서 자랑 중!
이러한 유연함은 서비스 제공자 프레임워크를 만드는 근간이 된다. 대표적으로 JDBC
JDBC에서는 connetcion이 서비스 인터페이스 역할

public static Foo getFoo(boolean flag) {
    Foo foo = new Foo();
    // TODO 어떤 특정 약속되어 있는 텍스트 파일에서 Foo의 구현체의 Full Qulified Class Name을 읽어온다.
    // TODO FQCN에 해당하는 인스턴스를 생성한다.
    // TODO foo 변수를 해당 인스턴스를 가리키도록 수정한다.
    return foo;
}

단점

1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.

Collection 프레임워크의 유틸리티 구현 클래스들은 상속할 수 없다.
오히려 상속보다 컴포지션을 사용하도록 유도하고, 불변타입으로 만들려면 이 제약을 지켜야 한다는 점에서 오히려 장점으로!!

2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.

생성자처럼 API설명에 명확히 드러나지 않으니, 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 알아내야 한다.
자바독이 알아서 처리해 줄 날이 올때까지 아래 사진처럼 api문서 잘 써넣고 메서드 이름도 널리 알려진 규약을 따라 짓는 식으로 문제 완화해야한다.

참고

자료는 이펙티브 자바 책과 백기선님의 강의를 들으며 작성했다.
아이템1 - 생성자 대신 정적 팩터리 메서드를 고려하라