Effective Java 3/E 북스터디 기록
아이템1. 생성자 대신 정적 팩터리 메서드를 고려하라
클라이언트가 클래스의 인스턴스를 얻는 전통적인 수단은 public 생성자이지만, 이 방법은 매개변수 만을 보고 어떤 객체가 반환될지 예측이 잘 되지 않는 단점이 있다.
그래서 이러한 방법 말고 모든 프로그래머가 알아둬야 할 기법이 하나 더 있다. 바로 클래스는 생성자와 별도로 정적 팩토리 메소드(static factory method)를 제공할 수 있다는 것.
public final class Boolean implements java.io.Serializable,Comparable<Boolean> {
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
}
valueOf()메소드는 기본 타입인 boolean 값을 받아 Boolean 객체 참조로 변환해주는 메소드
이렇게 클래스는 public 생성자 대신 정적 팩토리 메소드를 제공할 수 있다.
1. 이름을 가질 수 있다.
일반적인 생성자로 객체를 생성한다면 매개변수가 무슨 의미를 뜻하는지 알기 어려우나, 정적 팩터리 메서드를 고려한다면 이름만 잘 지으면 반환될 객체의 특성을 쉽게 묘사할 수 있다.
Book book1 = new Book(“이펙티브 자바”);
Book book2 = Book.createByTitle(“이펙티브 자바”);
2. 인스턴스 통제 클래스
호출될 때 마다 인스턴스를 새로 생성하지 않아도 된다.
new 키워드를 사용하면, 객체는 무조건 새로 생성된다. 만약, 자주 생성될 것 같은 인스턴스는 클래스 내부에 미리 생성해 놓은 다음 반환한다면 코드를 최적화할 수 있을 것 이다.
불변 클래스는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.
예시로 Boolean.valueOf(boolean) 이 메소드는 객체를 아예 생성하지 않는다.
TRUE, FALSE를 상수로 정의해놓고 메소드에서는 이것을 반환하고 앴다.
따라서 객체 생성 비용이 큰 객체가 자주 요청되는 상황이라면 성능을 상당히 끌어올릴 수 있다.
정적 팩토리 방식의 클래스는 언제 어느 인스턴스를 살아 있게 할지를 철저히 통제할 수 있다. 이런 클래스를 인스턴스 통제 클래스 라고 한다.
인스턴스를 통제하는 이유?
플라이웨이트 패턴이란?
플라이웨이트 패턴 (Flyweight pattern) : 데이터를 공유하여 메모리를 절약하는 패턴, 공통으로 사용되는 객체는 한번만 사용되고 Pool에 의해서 관리, 사용된다.
(JVM의 String Pool에서 같은 String이 잇는지 먼저 찾는다. [불변객체 String])
3. 반환 타입의 하위 타입 객체를 반환할 수 있다.
생성자를 사용하면 생성되는 객체의 클래스가 하나로 고정된다. 정적 팩터리 메서드를 사용하면 반환할 객체의 클래스를 자유롭게 선택할 수 있게 하는 엄청난 유연성을 가지게 된다.
public interface Type {
static Type getAType() {
return new AType();
}
static Type getBType() {
return new BType();
}
}
class AType implements Type {
}
class BType implements Type {
}
getAType() 메소드와 getBType()의 메소드의 반환 타입은 인터페이스인 Type 이지만, 반환하고 있는 것은 인터페이스의 하위 클래스인 것을 볼 수 있다.
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
같은 이름의 메서드지만 매개변수의 개수에 따라 리턴받는 클래스를 아무 하위타입 클래스를 리턴 받을 수 있다는 것
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
implements Cloneable, java.io.Serializable
{
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);
}
}
noneOf() 메소드를 보면 원소가 64개 이하면 원소들을 long 변수 하나로 관리하는 RegularEnumSet의 인스턴스를, 65개 이상이면 long 배열로 관리하는 JumboEnumSet의 인스턴스를 반환하는 것을 볼 수 있다.
이 두 객체 타입은 노출되지 않고 감춰져 있기 때문에 사용자는 이에 대해 알 필요가 없으며 추후에 새로운 타입을 만들거나 기존 타입을 없애도 문제없이 사용할 수 있다. (EnumSet의 하위타입이기만 하면 되는 것이다.)
5. 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
유연함은 서비스 제공자 프레임워크를 만드는 근반이 된다. 대표적인 서비스 제공자 프레임워크로는 JDBC가 있다.
1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메소드만 제공하면 하위 클래스를 만들 수 없다.
인스턴스 통제 클래스를 구현하기 위해서는 사용자가 new 키워드를 사용하여 임의로 객체를 생성함을 막아야한다. 이를 위해 생성자의 접근 제어자를 private 로 설정해야하는데, 생성자가 private 인 클래스는 상속을 할 수 없다. 즉, 부모 클래스가 될 수 없다.
하지만, 이 제약은 상속보다 컴포지션을 사용하도록 유도하고, 불변타입으로 만들기 위해서는 이 제약을 지켜야 한다. 따라서 이 단점은 장점으로도 작용할 수 도 있다.
2. 정적 팩토리 메소드는 프로그래머가 찾기 어렵다.
일반적으로 자바 API Docs를 보면 생성자는 상단에 있기 때문에 찾기가 쉽다. 하지만 정적 팩토리 메소드는 다른 메소드와 구분 없이 함께 보여주고
사용자가 정적 팩토리 메소드 방식 클래스를 인스턴스화할 방법을 알아내야 하는데 찾기 쉽지 않다는 단점이 있다.
단점2를 보완하기 위해 널리 알려진 규약을 통해 정적 팩터리 메서드를 명명하는 것이 좋다. 이펙티브 자바에서 소개하는 정적 팩터리 메서드 네이밍 컨벤션은 아래와 같다.
from
매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형 변환 메서드
Date d = Date.from(instant);
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(options);
create 혹은 newInstance
instance 혹은 getInstance 와 비슷하지만, 매번 새로운 인스턴스를 생성하여 반환함을 보장한다.
Object newArray = Array.newInstance(classObject, arrayLen);
getType
getInstance 와 같으나, 현재 클래스가 아닌 다른 클래스의 인스턴스를 생성할 때 사용한다. Type 은 팩터리 메서드가 반환할 객체의 타입을 적는다.
FileStore fs = Files.getFileStore(path);
newType
createInstance 와 같으나, 현재 클래스가 아닌 다른 클래스의 인스턴스를 생성할 때 사용한다. Type 은 팩터리 메서드가 반환할 객체의 타입을 적는다.
BufferedReader br = Files.newBufferedReader(path);
type
getType 과 newType 의 간결한 버전
List<Complaint> litany = Collections.list(legacyLitany);