클라이언트가 클래스의 인스턴스를 얻는 전통적인 수단은 public 생성자이지만, 정적 팩터리 매서드(static factory method)를 제공하는 방법도 있다
정적 팩터리 메서드는 디자인 패턴에서의 팩터리 메서드(Factory Method)와 다르다. 디자인 패턴 중에는 이와 일치하는 패턴은 없다.
장점 1: 이름을 가질 수 있다
- 정적 팩터리 메서드는 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사할 수 있다.
- 시그니처가 같은 생성자는 여러 개 만들 수 없으므로, 필요하다면 정적 팩터리 메서드로 바꾸고 각각의 차이를 잘 드러내는 이름을 지어주자.
// 생성자: 반환될 객체의 특성 불분명
BigInteger bigInteger1 = new BigInteger(3, 1, new Random());
// 정적 팩터리 메소드: 소수인 BigInteger를 반환 한다는 의미 설명
BigInteger bigInteger2 = BigInteger.probablePrime(3, new Random());
장점 2: 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다
- 불변 클래스는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 개체 생성을 피할 수 있다.
- 플라이웨이트 패턴(Flyweight pattern)도 이와 비슷한 기법이라 할 수 있다.
- 불편 값 클래스에서 동치인 인스턴스가 단 하나뿐임을 보장할 수 있다.(a == b)
public class Settings {
private static final Settings SETTINGS = new Settings();
private boolean autoSteering;
private boolean abs;
private Settings() {
}
public static Settings getInstance() {
return SETTINGS;
}
}
public static void main(String[] args) {
Settings settings1 = Settings.getInstance();
Settings settings2 = Settings.getInstance();
System.out.println("(settings1 == settings2) = " + (settings1 == settings2));
}
장점 3: 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다
- 인터페이스 기반 프레임워크를 만드는 핵심 기술이 된다.
- 자바 8 전에는 인터페이스에 정적 메서드를 선언할 수 없어서 (인스턴스화 불가인) 동반 클래스를 만들어 그 안에 정의하는 것이 관례였다.
- 자바 8부터는 인터페이스가 정적 메서드를 가질 수 있게 되면서, 정적 멤버를 인터페이스 자체에 둘 수 있게 되었다. 이로 인해 인터페이스에서 다양한 기능들을 제공하게 되었다.
// 동반 클래스
List<Integer> integers1 = Arrays.asList(1, 2, 3);
// 인터페이스의 정적 멤버
List<Integer> integers2 = List.of(1, 2, 3);
장점 4: 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다
- 반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다.
public interface HelloService {
String hello();
static HelloService of(String lang) {
if (Objects.equals(lang, "ko")) {
return new KoreanHelloService();
}
return new EnglishHelloService();
}
}
public static void main(String[] args) {
HelloService helloService1 = HelloService.of("ko");
System.out.println("helloService1.hello() = " + helloService1.hello());
HelloService helloService2 = HelloService.of("en");
System.out.println("helloService2.hello() = " + helloService2.hello());
}
장점 5: 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다
- 이런 유연함은 서비스 제공자 프레임워크를 만드는 근간이 된다.
- 서비스 인터페이스(service interface): 구현체의 동작 정의
- 제공자 등록 API(provider registration API): 제공자가 구현체를 등록할 때 사용
- 서비스 접근 API(service access API): 클라이언트가 서비스의 인스턴스를 얻을 때 사용
- 서비스 제공자 인터페이스(service provider interface): 서비스 인터페이스의 인스턴스를 생성하는 팩터리 객체 설명
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class);
Optional<HelloService> helloServiceOptional = loader.findFirst();
helloServiceOptional.ifPresent(h -> {
System.out.println(h.hello());
});
}
단점 1: 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다
- 상속보다 컴포지션을 사용하도록 유도하고 불변 타입으로 만들려면 이 제약을 지켜야 한다는 점에서 오히려 장점으로 받아들일 수도 있다.
단점 2: 정적 팩터리 메서드는 프로그래머가 찾기 어렵다
- 생성자처럼 API 설명에 명확하게 드러나지 않으니 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 알아내야 한다.
- 정적 팩터리 메서드에 흔히 사용하는 명명 방식들이다.
// from: 매개변수를 하나 받아서 해당 타입의 인스턴스 반환
Date d = Date.from(instance);
// of: 여러 매개변수를 받아 적합한 타입의 인스턴스 반환
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
// valueOf: from과 of의 더 상세한 버전
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
// instance, getInstance: (매개변수를 받는다면) 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않음
StackWalker luke = StackWalker.getInstance(option);
// create, newInstance: instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장
Object newArray = Array.newInstance(classObject, arrayLen);
// getType: getInstance 와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용
FileStore fs = Files.getFileStore(path);
// newType: newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 사용
BufferedReader br = Files.newBufferedReader(path);
// type: getType과 newType의 간결한 버전
List<Complaint> litany = Collections.list(legacyLitany);
완벽 공략
열거 타입
- 상수 목록을 담을 수 있는 데이터 타입
- 특정한 변수가 가질 수 있는 값을 제한할 수 있다. 타입-세이프티 (Type-Safety)를 보장할 수 있다.
- 싱글톤 패턴을 구현할 때 사용하기도 한다.
플라이웨이트 패턴
- 객체를 가볍게 만들어 메모리 사용을 줄이는 패턴
- 2022.02.10 - [GoF의 디자인 패턴] - 플라이웨이트(Flyweight) 패턴
인터페이스의 정적 메서드
- 기본 메서드 (default method)와 정적 메서드를 가질 수 있다.
- 기본 메서드
- 인터페이스에서 메서드 선언뿐 아니라, 기본적인 구현체까지 제공할 수 있다.
- 기존의 인터페이스를 구현하는 클래스에 새로운 기능을 추가할 수 있다.
- 정적 메서드
- 자바 9부터 private static 메서드도 가질 수 있다.
- 단, private 필드는 아직도 선언할 수 없다.
서비스 제공자 프레임워크
- 주요 구성 요소
- 서비스 제공자 인터페이스 (SPI)와 서비스 제공자 (서비스 구현체)
- 서비스 제공자 등록 API (서비스 인터페이스의 구현체를 등록하는 방법)
- 서비스 접근 API (서비스의 클라이언트가 서비스 인터페이스의 인스턴스를 가져올 때 사용하는 API)
- 다양한 변형
- 브릿지 패턴
- 의존 객체 주입 프레임워크
- java.util.ServiceLoader
리플렉션
- 클래스로더를 통해 읽어온 클래스 정보를 사용하는 기술
- 리플렉션을 사용해 클래스를 읽어오거나, 인스턴스를 만들거나, 메서드를 실행하거나, 필드의 값을 가져오거나 변경하는 것이 가능하다.
테스트 코드
https://github.com/jsyang-dev/study-effective-java/tree/master/src/item01
GitHub - jsyang-dev/study-effective-java
Contribute to jsyang-dev/study-effective-java development by creating an account on GitHub.
github.com
'이펙티브 자바' 카테고리의 다른 글
아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2022.05.17 |
---|---|
아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2022.05.16 |
아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2022.04.21 |
아이템 14. Comparable을 구현할지 고려하라 (0) | 2022.03.09 |
아이템 13. clone 재정의는 주의해서 진행하라 (0) | 2022.02.12 |
댓글