아이템1: 생성자 대신 정적 팩터리 메서드를 고려하라
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
- 디자인 패턴의 팩터리 메서드(Factory Method)와는 다름!
- 해당 클래스의 인스턴스를 반환하는 단순한 정적 메서드
정적 팩터리 메서드가 생성자보다 좋은 점
- 이름을 가질 수 있다
- 생성자:
BigInteger(int, int, Random)
에 비해
정적 팩터리 메서드BigInteger.probablePrime
이 더 직관적이다. - 생성자 오버로딩을 이용하는 데에도 한계가 있다.
- 하나의 시그니처로는 생성자를 하나만 가질 수 있다.
- 매개변수의 순서를 다르게 하는 방식
Constructor(int i, String s)
와Constructor(String s, int i)
는 서로 다르다는 점을 이용하는 방식- 하지만 이 경우 직관성이 떨어지고, 혼란만 가중시킨다.
- 반면, 정적 팩터리 메서드는 하나의 시그니처로 하나의 생성자만 가질 수 있는 제약을 받지 않는다.
- 매개변수의 순서를 다르게 하는 방식
- 하나의 시그니처로는 생성자를 하나만 가질 수 있다.
- 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.
- 인스턴스 통제(instance-controlled) 클래스
- 인스턴스 미리 만들기, 캐싱 등으로 불필요한 객체 생성 회피 -> 성능 향상
- 인스턴스 통제를 통해 객체를
- 싱글턴(singleton)
- 인스턴스화 불가(noninstantiable)
- 불변 값 인스턴스에서 동치인 인스턴스가 하나임을 보장(Leibniz's law)
- 플라이웨이트 패턴
- 열거형(인스턴스가 하나임을 보장)
만들 수 있다.
- 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
-
반환 객체의 클래스를 선택 가능한 유연성
- API를 작게 유지
- 인터페이스 기반 프레임워크: 인터페이스를 정적 팩터리 메서드의 반환 타입으로 사용
- 구현 객체를 인터페이스로 다룬다는 점에서 API 학습 난이도를 낮추며, 다형성을 활용하기 좋다.
-
Java8이전의 경우 인터페이스에서 정적 메서드 반환 불가
- 동반 클래스 사용해야:
public interface Type { ... }
이를 반환하는 정적 메서드
typeFactory()
Type 내에 생성이 불가능하기에public class Types { static Type typeFactory() { ... } }
위와 같은 방식으로 동반 클래스(companion class)를 만들어야 했다.
- 예시) java.util.Collections
-
자바8 이후로는 인터페이스가 정적 메서드를 가질 수 있다.
- 즉 동반 클래스를 가질 필요가 적다.
- 하지만 인터페이스는 public 정적 멤버만 허용하며
- 자바9에서 private 정적 메서드까지 허용하지만, 어쨌든 정적 필드와 정적 멤버 클래스는 여진히 public만 가능하다.
- 이런 이유로 package-private 클래스에 정적 메서드 구현 코드 일부를 둬야 하는 경우도 있다.
- 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
- 유연하게 반환 하위 클래스를 선택, 변경할 수 있다.
- 위와 마찬가지로, 다형성을 활용하는 데 용이하다. 클라이언트는 실제 어느 하위 클래스의 인스턴스인지를 알 필요가 없다.
- 예시:
java.util.EnumSet.java
의 경우
/**
* Creates an empty enum set with the specified element type.
*
* @param <E> The class of the elements in the set
* @param elementType the class object of the element type for this enum
* set
* @return An empty enum set of the specified type.
* @throws NullPointerException if {@code elementType} is null
*/
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);
}
원소의 수에 따라 RegularEnumSet
이나 JumboEnumSet
을 반환한다.
-
하위 클래스이기만 한다면
-
클라이언트가 하위 클래스의 존재를 모르며,
-
필요 없는 하위 클래스는 삭제해도 문제가 없으며
-
필요한 다른 클래스를 추가해도 된다는 점에 주목하라.
-
- 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
-
서비스 제공자 프레임워크(service provider framework)
-
대표적인 예로 JDBC(Java Database Connectivity)
-
제공자(provider)인 서비스 구현체들을 클라이언트에 제공하는 역할을 프레임워크가 담당 -> 클라이언트를 구현체로부터 분리!
-
구성: 3개의 핵심 컴포넌트(경우에 따라 +1)
서비스 인터페이스(service inteface): 구현체의 동작을 정의 -
제공자 등록 API(provider registration API): 제공자가 구현체를 등록할 때 사용
-
서비스 접근 API(service access API): 클라이언트가 서비스의 인스턴스를 얻을 때 사용
클라이언트가 원하는 구현체의 조건을 명시하여 반환받음, 명시하지 않으면 기본 구현체 혹은 지원 구현체 중 하나를 반환- 유연한 정적 팩터리
```java
@CallerSensitive
public static Connection getConnection(String url,
java.util.Properties info) throws SQLException {
return (getConnection(url, info, Reflection.getCallerClass()));
}
...
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
...
@CallerSensitive
public static Connection getConnection(String url)
throws SQLException {
java.util.Properties info = new java.util.Properties();
return (getConnection(url, info, Reflection.getCallerClass()));
}
...
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
if (callerCL == null || callerCL == ClassLoader.getPlatformClassLoader()) {
callerCL = Thread.currentThread().getContextClassLoader();
}
if (url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
ensureDriversInitialized();
// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for (DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if (isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.driver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
제공된 파라미터에 따라서 실제로 getConnection() 작업을 하는 메서드에 형식을 맞춰 파라미터를 넘기고, 인터페이스로서 Driver를 받고 있다.
-
그런데 왜 리플렉션을 사용할까?
-
서비스 제공자 인터페이스(service provider interface): 서비스 인터페이스의 인스턴스를 생성하는 팩터리 객체를 설명
-
리플렉션?
-
서비스 제공자 프레임워크 패턴의 변형
-
브리지 패턴: 서비스 접근 API가 공급자가 제공하는 것보다 더 풍부한 서비스 인터페이스를 클라이언트에 반환
-
DI 프레임워크
java,util.ServiceLoader
: 자바6부터 제공되는 범용 서비스 제공자 프레임워크- JDBC의 경우 자바6 이전에 등장했기에 위를 사용 안 함.
-
-
단점
- 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.
- 상속보다 컴포지션을 사용하도록 유도하고
- 불변 타입으로 만들기 위한 제약이 된다는 점에서 장점이 되기도 한다.
- 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.
- 생성자처럼 API 설명에 명확히 들어나지 않기 때문(JavaDocs)
- API 문서를 잘 작성하고 메서드명을 규약에 따라 짓는 것으로 문제를 완화해야:
- 정적 팩터리 메서드에 흔한 명명 방식
- 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: getType과 같으나, 매번 새로운 인스턴스를 생성해 반환함을 보장한다.
BufferedReader br = Files.newBufferedReader(path);
- type: getType과 newType의 간결한 버전
List<Complaint> litany = Collections.list(legacyLitany);
- from: 매개변수를 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
핵심 정리
정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋다. 그렇다고 하더라도 정적 팩터리를 사용하는 게 유리한 경우가 많으므로 무작정 public 생성자를 제공하던 습관이 있다면 고치자.
Author And Source
이 문제에 관하여(아이템1: 생성자 대신 정적 팩터리 메서드를 고려하라), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@rescogitans/아이템1-생성자-대신-정적-팩터리-메서드를-고려하라저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)