2장 — 객체의 생성과 삭제

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

  • 클래스를 통해 객체를 만드는 일반적인 방법은 public으로 선언된 생성자 (constructor)를 이용하는 것이다.
  • Design Patterns(디자인 패턴)에 등장하는 팩터리 메서드(Factory Method) 개념과 다르다는 점에 유의하자.
public class Sample {
    public static Boolean valueOf(boolean b) {
      return b ? Boolean.TRUE : Boolean.FALSE;
    }
}
public class Sample {
    public static BigInteger probablePrime(int bitLength, Random rnd) {
      if (bitLength < 2)
          throw new ArithmeticException("bitLength < 2");

      return (bitLength < SMALL_PRIME_THRESHOLD ? smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) : largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd));
    }
}
  • 장점
    • 생성자와는 달리 정적 팩터리 메서드에는 이름이 있다는 것이다.
      • 정적 팩터리 메서드는 이름을 잘 짓기만 한다면 시용하기도 쉽움
      • 클라이언트 코드의 가독성(readability)도 높아짐
    • 생성자와는 달리 호출할 때마다 새로운 객체를 생성할 필요는 없다는 것이다.
    • 개체 통제 클래스를 작성하는 이유
      • 개체 수를 제어하면 싱글턴(singleton) 패턴을 따르도록 할 수 있다.
      • 객체 생성이 불기능한 클래스를 만들 수도 있다.
      • 변경이 불가능한 클래스의 경우 두 개의같은 객체가 존재하지 못하도록 할 수도 있다.
    • 정적 팩터리 메서드를 사용하면 같은 객체를 반복해서 반환할 수 있으므로 어떤 시점에 어떤 객체가 얼마나 존재할지를 정밀하게 제어할 수 있다.
    • 생성자와는 달리 변환값 자료형의 하위 자료형 객체를 반환할 수 있다든 것이다.
      • 반환되는 객체의 클래스를 훨씬 유연하게 결정할 수 있다.
      • 서비스 제공자 프레임워크 : 다영한 서비스 제공자들이 하나의 서비스를 구성하는 시스템 (JDBC)
        • 서비스 인터페이스(service interface)
        • 제공자 등록 API (provider registration API)
        • 서비스 접근 API (service access API) : 클라이언트에게 실제 서비스 구현체를 제공한다.
    • 형인자 자료형(parameterized type) 객체를 만들 때 편하다는 점이다.
  public static <K, V> HashMap<K, V> new newInstance() {
    return new HashMap<K, V>();
  }
import java.sql.*;

  public class JavaDatabaseConnectivity {

    public static void main(String[] args) throws SQLException {
      // 서비스 제공자 프레임워크는 다양한 서비스 제공자들이 하나의 서비스를 구성하는 시스템
      // 1. 서비스 인터페이스 (java.sql.Connection)
      // 4. 서비스 제공자 인터페이스 (Driver)
      Driver driver = new com.mysql.cj.jdbc.Driver();
      // 2. 제공자 등록 API (구현체를 시스템에 등록하여 클라이언트가 쓸 수 있도록 함)
      DriverManager.registerDriver(driver);
      // 3. 서비스 접근 API (구현체에 대한 접근이 가능하도록 한다)
      // getConnection() '유연한 정적 팩터라'
      Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306", null);
      Statement statement = connection.createStatement();
      statement.execute("");
  }
}
  • 단점
    • public나 protected로 선언된 생성자가 없으므로 하위 클래스를 만들 수 없다는 것이다.
public class Foo {

  public static Foo newInstance() {
    return new Weakness();
  }

  private Foo() {

  }
}

public class FooSub extends Foo {

  private WeaknessSub() {
    // 상위클래스 생성자 호출을 할 수 없음
    super();
	}
}

  • 정적 팩터리 메서드가 다른 정적 메서드와 확연히 구분되지 않는다는 것이다.
메서드명 설명
valueOf 인자로 주어진 값과 같은 값을 갖는 객체를 반환한다는 뜻이다.
of valueOf를 더 간단하게 쓴 것이다.
getInstance 인자에 기술된 객체를 반환하지만, 인자와 같은 값을 갖지 않을 수도 있다.
newInstance getInstance와 같지만 호출할 때마다 다른 객체를 반환한다.
getType getInstance와 같지만, 반환될 객체의 클래스와 다른 클래스에 팩터리 메서드가 있을 때 사용한다.
newType newInstance와 같지만, 반환될 객체의 클래스와 다른 클래스에 팩터리 메서드가 있을 때 시용한다.

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

  • 정적 팩터리나 생성자는 같은 문제를 갖고 있다. 선택적 인자가 많은 상황에 적응하지 못한다는 것.
// 점층적 생성자 패턴 - 더 많은 인자 개수에 잘 적응하지 못한다
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 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;
  }
}

  • 선택적 인자가 많은 상황
    • 점층적 생성자 패턴(telescoping constructor pattern)
      • 인자 수가 늘어나면 클라이언트 코드를 작성하기가 어려워지고, 무엇보다 읽기 어려운 코드가 되고 만다.
      • 클라이언트가 두 개 인자의 순서를 실수로 뒤집어도 컴파일러는 알지 못하며, 프로그램 실행 도중에 문제가 생기게 되는 것이다.
    • 자바빈 패턴
      • 1회의 함수 호출로 객체 생성을 끝낼 수 없으므로, 객체 일관성(consistency)이 일시적으로 깨질 수 있음
      • 생성자의 인자가 유효한지 검사하여 일관성을 보장하는 단순한 방법을 여기서는 사용할 수 없음
      • 변경 불가능(immutable) 클래스를 만들 수 없다는 것
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;}
}

  • 빌더 패턴은 인자가 많은 생성자나 정적 팩터리가 필요한 클래스를 설계할 때, 특히 대부분의 인자가 선택적 인자인 상황에 유용
  • 생성자와 마찬가지로, 빌더 패턴을 사용하면 인자에 불변식(invariant)을 적용할 수 있음
  • 빌더 객체는 여러 개의 varargs 인자를 받을 수 있다는 것
  • 빌더 패턴은 하나의 빌더 객체로 여러 객체를 만들 수 있음
  • 빌더 객체는 어떤 필드의 값은 자동으로 채울 수도 있음
  • 단점
    • 객체를 생성하려면 우선 빌더 객체를 생성
    • 실무에서 빌더 객체를 만드는 오버헤드가 문제가 될 소지는 없어 보이지만, 성능이 중요한 상황에선 그렇지 않을 수도 있음
  • 빌더 패턴은 인자가 많은 생성자나 정적 팩터리가 필요한 클래스를 설계할 때, 특히 대부분의 인자가 선택적 인자인 상황에 유용

  • Q : 불변식이란(Invariant)?
  • A : an invariant is a condition that can be relied upon to be true during execution of a program, or during some portion of it. It is a logical assertion that is held to always be true during a certain phase of execution.
  • R : https://en.wikipedia.org/wiki/Invariant_(computer_science)

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

// public final 필드를 이용한 싱글턴
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 getInstance() { return INSTANCE; }

  public void leaveTheBuilding( ) { }
}
// Enum 싱글턴 - 이렇게 하는 쪽이 더 낫다
public enum Elvis {
  INSTANCE;

  public void leaveTheBuilding() { }
}
  • 클래스를 싱글턴으로 만들면 클라이언트를 테스트하기가 어려워질 수가 있음
  • serialize된 객체가 역직렬화(deserialize)될 때마다 새로운 객체가 생기게 된다.
  • 원소가 하나뿐인 enum 자료형이야말로 싱글턴을 구현하는 가장 좋은 방법

  • 참고 싱글턴

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

  • 원소가 하나뿐인 enum 자료형이야말로 싱글턴을 구현하는 가장 좋은 방법
  • 객체를 만들 수 없도록 하려고 클래스를 abstract로 선언해 봤자 소용
  • private 생성자를 클래스에 넣어서 객체 생성을 방지하자는 것
// 객쳬룰 만둘 수 없는 유틸리티 클래스
public class UtilityClass {
  // 기본 생성자가 자동 생성되지 못하도록 하여 객체 생성 방지
  private UtilityClass() {
    throw new AssertionError();
  }
}

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

  • JDK 1.5부터는 쓸데없이 객체를 만들 새로운 방법이 더 생겼다. 자동 객체화 (autoboxing)라는 것인데, 프로그래머들이 자바의 기본 자료형(primitive type) 그 객체 표현형을 섞어 사용할 수 있도록 해 준다.
  • 객체 표현형 대신 기본 자료형을 사용하고, 생각지도 못한 자동 객체화가 발생하지 않도록 유의하라는 것이다.
  • 직접 관리하는 객체 풀(Object pool)을 만들어 객체 생성을 피하는 기법은 객체 생성 비용이 극단적으로 높지 않다면 사용하지 않는 것이 좋다.
    • 코드가 어지러워짐
    • 메모리 요구량이 증가 하며, 성능도 떨어짐
    • 최신 JVM은 고도로 최적화된 쓰레기 수집기를 갖고 있어서, 가벼운(lightweight) 객체라면 객체 풀보다 월등한 성능을 보여줌
  • 방어적 복사가 요구되는 상황에서 객체를 재사용하는 데 드는 비용은 쓸데없이 같은 객체를 여러 벌 만드는 비용보다 훨씬 높다는 것에 유의하자.
private static long sum() {
  Long sum = 0L;
  for (long i = 0; i <= Integer.MAX_VALUE; i++) {
    sum += i;
  }
  return sum;
}

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

  • 만기 참조란, 다시 이용되지 않을 참조
  • 자동적으로 쓰레기 객체를 수집하는 언어에서 발생하는 메모니 누수 문제 (의도치 않은 객체 보유)
  • 쓸 일 없는 객체 참조는 무조건 null
  • 만기 참조가 몇 개라도 있으면 굉장히 많은 객체가 쓰레기 수집에서 제외될 가능성이 있음
  • 객체 참조를 null 처리하는 것은 규범(norm)이라기보단 예외적인 조치가 되어야 한다.
  • 자체적으로 관리하는 메모리가 있는 클래스를 만들 때는 메모리 누수가 발생하지 않도록 주의해아 한다.
  • 더 이상 사용되지 않는 원소 안에 있는 객체 참조는 반드시 null로 바꿔 주어야 한다.
  • 캐시도 메모리 누수가 흔히 발생하는 장소다.
  • WeakHashMap은 캐시 안에 보관되는 항목의 수명이 키에 대한 외부 참조의 수명에 따라 결정되는 상황에서만 적용 가능하다는 것을 기억하자.
  • 메모리 누수가 흔히 발견되는 또 한 곳은 리스너 등의 역호출자다.

Q : ‘더 이상 사용되지 않는 원소 안에 있는 객체 참조는 반드시 null로 바꿔 주어야 한다.’ 잘못된 사례는 ?

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

  • 종료자(finalizer)는 예측 불가능하며, 대체로 위험하고, 일반적으로 불필요
  • 긴급한(time-critical) 작업을 종료자 안에서 처리하면 안됨
  • 중요 상태 정보(critical persistent state)는 종료자로 갱신하면 안됨
  • 종료자는 그런 자원을 발견하게 될 경우 반드시 경고 메시지를 로그(log)