작성자: 박성민
작성일: 2022년 4월 9일
public class Foo {
private String name;
public Foo(String name) {
this.name = name;
}
public static Foo withName(String name) {
return new Foo(name);
}
public static void main(String[] args) {
Foo foo1 = new Foo("tom");
Foo foo2 = Foo.withName("tom");
}
}foo1 : 생성자 사용해서 인스턴스를 얻은 것.
foo2 : 정적 팩터리 메서드를 사용해서 인스턴스를 얻은 것.
-
생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 쉽게 알 수 없다.
-
잘 만든 이름을 가진 정적 팩토리를 사용하는 것이 객체의 특성을 쉽게 알 수 있고, 읽기 더 편하다.
-
생성자는 시그니처에 제약이 있다. (파라미터가 같은 똑같은 생성자 두 개를 만들 수 없으니)
-
-
public Foo(String name) { this.name = name; } public Foo(String address) { this.address = address; }
-
-
정적 팩토리 메서드에는 이런 제약이 없다.
-
-
public static Foo withName(String name) { Foo foo = new Foo(); foo.setName(name); return foo; } public static Foo withAddress(String address) { Foo foo = new Foo(); foo.setAddress(address); return foo; }
-
private static final Foo tom = new Foo();
public static Foo getFoo() {
return tom;
}-
불변 클래스(immutable class; 아이템17)는 인스턴스를 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.
-
따라서 객체 생성 비용이 큰 객체가 자주 요청되는 상황이라면 성능을 상당히 끌어올릴 수 있다.
-
정적 팩토리 방식의 클래스는 언제 어느 인스턴스를 살아있게 할지를 철저히 통제할 수 있다. 이런 클래스를 인스턴스 통제(instance-controlled)클래스라 한다.
-
- 인스턴스를 통제하면 클래스를 싱글턴(아이템 3)으로 만들 수도, 인스턴스화 불가(아이템 4)로 만들수도 있다.
- 또한 불변 값 클래스(아이템 17)에서 동치인 인스턴스가 단 하나임을 보장할 수 있다. (a==b일 때만 a.equals(b)가 성립) (값이 같은 다른 인스턴스가 없다는 말)
- 인스턴스 통제는 플라이웨이트 패턴의 근간이 되며, 열거 타입(아이템 34)은 인스턴스 하나만 만들어짐을 보장함.
-
반환할 객체의 클래스를 선택할 수 있게 하는 '유연함'이 있다.
-
- 실제 리턴하는 객체는 인터페이스의 구현체만 리턴한다. 이렇게 하면 클라이언트의 입장에서는 실제 구현체는 모르고 인터페이스만 가지고 코딩을 하게된다.
- java.util.Collections가 그 예에 해당.
- 인터페이스 기반 프레임워크(아이템 20)를 만드는 핵심 기술
-
컬렉션 프레임워크는 45개의 구현체를 제공하는데, 이 45개 클래스를 공개하지 않다.
-
- 제공해야 할 API가 줄었다.
- 개념적인 무게, 즉 프로그래머가 API를 사용하기 위해 익혀야 하는 개념의 수와 낮이도도 낮춘다.
-
자바8부터 인터페이스에 정적 메서드를 선언할 수 있기 때문에 인스턴스화 불가 동반 클래스를 둘 이유가 없다. 그냥 인터페이스에 두면 된다.
-
- 예를 들어 Collections 라는 구현 클래스를 따로 정의 할 필요가 없다. 그냥 Collection 인터페이스에 정적 메서드를 선언하면 되니까.
- Collections에 있는 sort, binarySearch같은 정적 메서드를 그냥 Collection 인터페이스에 정의하면 됨.
-
자바9부터 private 정적메서드까지 허락하지만, 정적 필드와 정적 멤버 클래스는 여전히 public이어야 한다.
-
- static 메서드가 필요한 이유 ? 외부에 노출할 필요가 없고, 현재 있는 클래스에서만 사용하기 때문에.
- private static 메서드도 위와 같다. public static 메서드에서는 static 메서드만 사용 가능하기 때문.
- 반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다.
public static Foo getFoo(boolean flag) {
return flag ? new Foo() : new BarFoo();
}
static class BarFoo extends Foo {
}-
EnumSet클래스는 생성자 없이 정적 팩터리만 제공한다.
-
- 원소의 개수에 따라 RegularEnumSet의 인스턴스 또는 JumboEnumSet의 인스턴스를 반환하는데 사용자는 이걸 알 필요가 없다.
- 사용자가 알 필요가 없기에 (RegularEnumSet < 이런 클래스의)추가/삭제가 용이하다.
- 현재 클래스만 반환할 수 있다면 저런 클래스들을 사용자가 다 알아야 한다.
-
이런 유연함은 서비스 제공자 프레임워크를 만드는 근간이 된다.
-
예를 들어 JDBC가 있다. (JDBC가 ServiceLoader를 쓰진 않는다.)
-
- JDBC의 getConnection이라는 메서드를 썼을 때 실제 리턴되어 나오는 Connection 객체는 DB(드라이버)마다 다르다.
- java.util.ServiceLoader는 범용 서비스 제공자 프레임워크.
-
정적 팩토리 메서드 안에서 반환할 객체가 정해지는 것이 아니라 외부에서 주입? 해주는 개념이다. 외부 파일이 바뀌면 반환되는 객체도 바뀐다.
- 이 제약은 상속보다 컴포지션을 사용(아이템 18)하도록 유도하고 불변 타입(아이템 17)으로 만들려면 이 제약을 지켜야 한다는 점에서 오히려 장점일수도?
- 생성자는 Javadoc (/** */) 상단에 모아서 보여주지만 정적 팩터리 메서드는 API 설명에 명확이 드러나지 않는다. 클래스 상단에 주석으로 설명을 달아주자.
- 정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋다. 그렇다고 하더라도 정적 팩터리를 사용하는 게 유리한 경우가 더 많으므로 무작정 public 생성자를 제공하던 습관이 있다면 고치자.