Post

Item 1 - 생성자 대신 정적 팩터리 메서드를 고려하라

정적 팩터리 메서드의 장점 👍

(1) 이름을 가질 수 있다.

  • 생성자에 넘기는 매개변수와 생성자 자체만으론 반환될 객체의 특성을 제대로 설명하지 못한다.
  • 반면, 정적 팩터리 메서드는 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사할 수 있다.

[생성자] : BigInteger(int, int, Random)

BigInteger_생성자

[정적 팩터리 메서드] : BigInteger.probablePrime

BigInteger_정적팩터리

  • ‘값이 소수인 BigInteger를 반환한다’는 의미를 더 잘 나타내고 있다.

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

정적 팩터리 메서드를 활용하여 인스턴스 캐싱 및 통제가 가능하다.

인스턴스 캐싱

  • 인스턴스를 미리 생성해 놓고, 필요할 때마다 이를 가져다 쓰는 방식을 뜻한다.
  • 불필요한 객체 생성을 피하고, 객체 생성 비용을 줄여 준다.

인스턴스 통제

  • 해당 인스턴스의 생명주기를 통제하는 것을 뜻한다.
  • 인스턴스를 통제하는 이유
    • 싱글턴으로 만들기 위해
    • 인스턴스화 불가로 만들기 위해
    • 불변 값 클래스에서 동치인 인스턴스가 단 하나뿐임을 보장한다.

Boolean

Boolean_valueOf

  • BooleanTRUEFALSE는 각각 true, false 값을 가지는 인스턴스를 캐싱하고 있다.
  • Boolean.valueOf(boolean) 메서드는 캐싱된 인스턴스를 반환한다.

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

다음은 Collections 클래스의 정적 팩터리 메서드 unmodifiableList이다.

1
2
3
4
5
public static <T> List<T> unmodifiableList(List<? extends T> list) {
    return (list instanceof RandomAccess ?
        new UnmodifiableRandomAccessList<>(list) :
        new UnmodifiableList<>(list));
}
  • 이와 같이 정적 팩터리 메서드를 사용하면, 메소드 반환 타입은 List이지만 실제로는 List의 하위 객체를 반환 시킬 수 있다.
  • 정적 팩터리 메서드를 사용하는 클라이언트는 얻은 객체를 인터페이스만으로 다루게 되고, UnmodifiableRandomAccessList, UnmodifiableList 클래스를 알 필요가 없어지게 된다.

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

다음은 EnumSet의 정적 팩터리 메서드이다.

메서드 내부에서 universe.length 따라 리턴 타입을 다르게 반환 한다.

1
2
3
4
5
6
7
8
9
10
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
    Enum<?>[] universe = getUniverse(elementType);
    if (universe == null)
        throw new ClassCastException(elementType + " not an enum");
    
    if (universe.length <= 64)
        return new RegularEnumSet<>(elementType, universe);
    else
        return new JumboEnumSet<>(elementType, universe);
}
  • 정적 팩터리 메서드를 통해 사용자는 내부 구현에 대해 알 필요 없이 원하는 반환 값을 전달 받을 수 있다.
  • 또한 메서드의 내부 구현이 변경되어도, 반환타입만 같다면 사용자에게 영향을 끼치지 않는다.

정적 팩터리 메서드의 단점 👎

(1) 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.

  • 상속을 하려면 public이나 protected 생성자가 필요하다.
  • 따라서 정적 팩터리 메서드만을 사용해 인스턴스를 반환할 경우, 하위 클래스를 만들 수 없다.

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

  • 생성자처럼 API 문서에 명확히 드러나지 않으므로 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 알아내야 한다.
  • 따라서 널리 알려진 규약을 따라 메서드 이름을 명명하여 문제를 완화하자.

정적 팩토리에서 흔히 사용하는 명명 방식 👀

명명 규칙설명
from매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드 ex) Date d = Date.from(instant);
of여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드 ex) Set faceCards = Enum.of(JACK, QUEEN, KING);
valueOffrom 과 of 의 더 자세한 버전 ex) BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
instance 혹은 getInstance매개변수로 명시한 인스턴스를 반환하지만 같은 인스턴스임을 보장하지는 않는다. ex) StackWalker luke = StackWalker.getInstance(options);
create 혹은 newInstanceinstance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장한다. ex) Object newArray = Array.newInstance(classObject, arrayLen);
getTypegetInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 쓴다. ex) FileStore fs = Files.getFileStore(path);
newTypenewInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메서드를 정의할 때 쓴다. ex) BufferedReader br = Files.newBufferedReader(path);
typegetType과 newType의 간결한 버전 ex) List litany = Collections.list(legacyLitany);

핵심정리 💡

  • 정적 팩터리 메서드와 public 생성자의 장단점을 고려해서 사용하자.
  • 정적 팩터리를 사용하는 게 유리한 경우가 더 많으므로, 무작정 public 생성자를 제공하던 습관은 고치자.

참고

java.util.ServiceLoader

This post is licensed under CC BY 4.0 by the author.