정적 팩터리 메서드란?
public 생성자 외에 인스턴스를 반환하는 방법 중 하나로, 해당 클래스의 인스턴스를 반환하는 단순한 정적 메서드. 디자인 패턴의 팩터리 메서드(Factory Method)와는 다른 개념
- 예시
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
- boolean 값을 받아 Boolean 객체 참조로 변환하는 함수
장점
- 이름을 가질 수 있다.
- public 생성자
- 생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 제대로 설명하지 못함
- 하나의 시그니처에 하나의 생성자만 만들 수 있음
- 정적 팩터리 메서드
- 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사 가능
- BigInteger(int, int, Random) BigInteger.probablePrime -> 소수인 BigInteger 반환을 더 잘 설명함
- 동일한 시그니처의 생성자가 여러 개 필요한 경우
- 각각의 특성을 잘 나타내는 네이밍으로 함수를 만든다면 동일한 시그니처의 생성자를 만들 때 발생하는 문제 해결 가능
- public 생성자
- 호출 될 때마다 인스턴스를 새로 생성하지 않아도 된다.
- 불변 클래스는 재사용하면 되므로 불필요한 객체 생성을 피할 수 있음
- 생성 비용이 큰 동일한 객체가 자주 요청되는 상황에서 용이함 → 성능 향상
- 인스턴스 통제(instance-controlled)
- 반복되는 요청에 같은 객체를 반환하는 방식으로 언제 어느 인스턴스를 살아 있게 할지 통제 가능
- 인스턴스를 통제하는 이유?
- 클래스를 싱글턴 클래스, 인스턴스화 불가로 만들 수 있음
- 동치인 인스턴스가 단 하나뿐임을 보장 가능
- 불변 클래스는 재사용하면 되므로 불필요한 객체 생성을 피할 수 있음
- Boolean.valueOf(boolean) -> 객체를 아예 생성하지 않음
- 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
- 구현 클래스를 공개하지 않고도 객체를 반환하기에 반환할 객체의 클래스를 자유롭게 선택 가능
- API를 작게 유지 가능
- 인터페이스 기반 프레임워크를 만드는 핵심 기술
- 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
- 반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관 없음
- 예시 - EnumSet
- 생성자 없이 정적 팩터리로만 제공
- OpenJDK에서는 원소의 수에 따라 두 가지 하위 클래스 중 하나의 인스턴스 반환
- 원소 개수 < 65 : RegularEnumSet 반환
- 원소 개수 ≥ 65 : JumboEnumSet 반환
- 클라이언트는 두 클래스의 존재를 모르기에 클래스의 제거 및 추가가 용이함
- EnumSet
- 정적 팩터리 메서드를 작성하는 시점엔 반환할 객체의 클래스가 존재하지 않아도 된다.
- 서비스 제공자 프레임워크의 근간
- 방법1) 리플렉션에 동적 클래스 로딩을 적용해서 반환 타입 가져옴
- 방법2) 인터페이스만 만들어 놓고 그거를 반환하게 함
- 서비스 제공자 프레임 워크
- 서비스 인터페이스
- 구현체의 동작을 정의
- 제공자 등록 API
- 제공자가 구현체를 등록할 때 사용
- 서비스 접근 API
- 클라이언트가 서비스의 인스턴스를 얻을 때 사용
- 서비스 인터페이스
- 3개의 핵심 컴포넌트로 이루어져 있고, 예시로는 JDBC(Java Database Connectivity)가 있음
단점
- 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.
- 정적 팩터리 메서드만 사용한 경우, 생성자가 없기 때문에 해당 메서드는 상속할 수 없음
- private 접근 제어자는 불가능
- 컴포지션 + 불변 타입으로 만들 시 이 특징은 오히려 장점이 될 수 있음
- 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.
- 정적 팩터리 메서드는 생성자처럼 API 설명에 나와있지 않으므로 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 알아내야 함
- 현재로서는 이 문제는 다음 방식으로 해결할 수 있음
- API 문서를 잘 쓰기
- 메서드 이름도 널리 알려진 규약을 따라 짓기
- 예시
- from
- 입력 매개변수 : 1개
- 해당 타입의 인스턴스 반환하는 형변환 메서드
Date d = Date.from(instant);
- of
- 입력 매개변수 : 여러 개
- 적합한 타입의 인스턴스를 반환하는 집계 메서드
- Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING)0;
- valueOf
- from과 of의 더 자세한 버전
- BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
- instance/getInstance
- 인스턴스 반환
- 매개변수를 받는 경우 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스 보장하지 않음
- StackWalker luke = StackWalker.getInstance(options);
- create/newInstance
- instance/getInstance와 같음
- 매번 새로운 인스턴스를 생성 후 반환을 보장
- Object newArray = Array.newInstance(classObject, arrayLen);
- get{Type} → getType
- getInstance와 동일
- 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용
- {Type} : 팩터리 메서드가 반환할 객체의 타입
FileStore fs = Files.get{FileStore}(path) -> Files에 FileStore를 반환하는 팩터리 메서드 정의
- new{Type} → newType
- newInstance와 동일
- 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용
- {Type} : 팩터리 메서드가 반환할 객체의 타입
BufferedReader br = Files.new{BufferedReader}(path);
- type
- getType과 newType의 간결한 버전
List<Complaint> litany = Collections.list(legacyLitany);
- from
- 현재로서는 이 문제는 다음 방식으로 해결할 수 있음
- 정적 팩터리 메서드는 생성자처럼 API 설명에 나와있지 않으므로 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 알아내야 함
정리
정적 팩터리 메서드와 public 생성자는 각각 장단점이 있으므로 상황에 맞게 적절하게 사용.
그래도 정적 팩터리를 사용하는 게 유리한 경우가 더 많으므로, 무작정 public 생성자를 제공하던 습관이 있다면 고치자.
추천 함수
- Integer.valueOf() → -126 ~ 126 숫자 생성 → 성능 향상
'Dev Language > EffectiveJava' 카테고리의 다른 글
[EffectiveJava] 이왕이면 제네릭 메서드로 만들라 (0) | 2025.01.11 |
---|---|
[EffectiveJava]이왕이면 제네릭 타입으로 만들라 (0) | 2025.01.05 |
[EffectiveJava] 로 타입(raw type)은 사용하지 말라 (4) | 2024.12.29 |
[EffectiveJava] 멤버 클래스는 되도록 static으로 만들라 (0) | 2024.12.22 |
[EffectiveJava] 인터페이스는 구현하는 쪽을 생각해 설계하라 (1) | 2024.12.08 |