일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 생성자 주입
- 바이너리 카운팅
- redis
- DI
- 열 속성
- jpa
- 인덱스
- 조합
- Test
- static
- jwt
- DDL
- hashcode
- 재정의
- KEVISS
- MSA
- 필드 주입
- AOP
- 테스트 코드
- StringBuilder
- java
- SQL
- VUE
- Spring
- equals
- lambda
- stream
- cache
- select_type
- docker
- Today
- Total
백엔드 개발자 블로그
[Effective Java] Item1. 생성자 대신 팩토리 메서드를 고려하라 본문
일반적으로 클래스의 인스턴스를 만들 떄 public 생성자를 통해 만들 것입니다. 하지만 다른 방법도 있습니다. 그것이 바로 오늘 다룰 주제인 정적 팩터리 메서드(static factory method) 입니다.
정적 팩토리 메서드란
정적 팩토리 메서드(static factory method)는 인스턴스를 반환하는 정적 메소드입니다.
java의 박싱 클래스 중 하나인 Boolean은 다음과 같은 정적 팩토리 메서드를 제공합니다.
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
public static void main(String args[]){
Boolean bool1 = new Boolean(true); // new 연산자를 사용한 방식
Boolean bool2 = Boolean.valueOf(true); // 정적 팩토리 메서드를 이용한 방식
}
정적 팩토리 메서드 방식이 new와 비교해 어떤 장단점이 있는지 살펴봅시다.
장점
1. 이름을 가질 수 있다.
이름을 통해 함수 설명이 가능해집니다. 생성자가 여러개인 경우에 사용하면 실수를 줄일 수 있습니다.
// BigInteger.java
// 두 메서드 모두 양의 소수를 반환하는 메서드
// public 생성자
public BigInteger(int bitLength, int certainty, Random rnd) {
BigInteger prime;
if (bitLength < 2)
throw new ArithmeticException("bitLength < 2");
prime = (bitLength < SMALL_PRIME_THRESHOLD
? smallPrime(bitLength, certainty, rnd)
: largePrime(bitLength, certainty, rnd));
signum = 1;
mag = prime.mag;
}
// 정적 팩터리 메서드
public static BigInteger probablePrime(int bitLength, Random rnd) {
if (bitLength < 2)
throw new ArithmeticException("bitLength < 2");
return (bitLength < SMALL_PRIME_THRESHOLD ?
smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) :
largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd));
}
2. 호출할 때마다 인스턴스를 생성하지 않아도 된다.
미리 생성해둔 인스턴스를 재활용하여 요청 때마다 인스턴스를 생성하지 않아도 됩니다. 같은 객체가 자주 요청되는 상황이라면 성능을 향상시켜줍니다.
그리고 인스턴스가 하나만 만들어짐을 보장할 수 있습니다.
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);
}
}
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
해당 능력은 자바의 다형성의 특징을 이용하여 코드의 유연성이 늘어납니다.
public interface CarType {
static CarType getSuvType() {
return new SuvType(); // 하위 타입 객체 반환
}
static CarType getSedanType() {
return new SedanType(); // 하위 타입 객체 반환
}
}
class SuvType implements CarType {}
class SedanType implements CarType {}
자바 8 이후부터는 인터페이스에 정적 메서드를 선언할 수 있게 되어 위와 같이 구현 클래스 인스턴스를 생성하여 반환할 수 있게 되었습니다. 이러한 방법을 사용한다면 구현 클래스를 공개하지 않고도 해당 객체를 반환할 수 있어 API를 작게 유지할 수 있습니다.
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); // 하위 타입 인스턴스 반환
}
...
}
원소 갯수에 따라 EnumSet하위 클래스의 인스턴스(RegularEnumSet, JumboEnumSet)를 반환하고 있습니다.
5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
때때로 컴파일 시점에 알려지지 않은 클래스 객체를 생성해야할 때도 존재하는데, 이런 경우 정적 팩터리 메서드를 사용하면 쉽게 구현할 수 있습니다.
단점
1. 상속을 하려면 public 혹은 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스를 만들 수 없다.
정적 팩토리 메서드만 하는 경우는 생성자를 private로 설정하여 인스턴스가 하나만 만들어짐을 보장합니다. 하지만 상속을 하려면 public이나 protected생성자가 필요하기에 상속을 할 수 없게 됩니다.
하지만 이러한 제약은 상속보다 컴포지션을 사용하도록 유도하고 불변타입으로 만들려면 이 제약으르 지켜야한다는 점에서 오히려 장점이 될 수도 있습니다.
2. 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.
생성자처럼 인스턴스에 API 설명에 명확히 드러나지 않으니 사용자가 찾기 어렵습니다. (요즘은 IDE가 잘 처리해주지만...) 그래서 혼란을 줄이기 위해 메서드 이름을 널리 알려진 규약을 따라 짓는 식으로 문제를 완화해줘야합니다.
메서드명
|
내용
|
용례
|
from
|
매개 변수를 하나 받아서 해당 타입의 인스턴스를 반환한다.
|
Date date = Date.from(instant);
|
of
|
여러 매개 변수를 받아 적합한 타입의 인스턴스를 반환한다.
|
Set<Rank> faceCards = Set.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 = FileStore.getFileStore(path);
|
newType
|
newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스의 팩토리 메서드를 정의할 때 쓴다. "Type"은 반환할 타입의 클래스를 적는 것이다.
|
BufferedReader br = Files.newBufferedReader(path);
|
type
|
getType과 newType의 간단한 버전
|
List<Complaint> litany = Collections.list(legacyLitany);
|
마지막 정리
정적 팩터리 메서드와 public 생성자(new)는 각자의 쓰임새가 있으므로 상대적인 장단점을 이해하고 사용하자. 하지만 정적 팩터리 메소드의 장점이 많기 때문에 무조건 public 생성자를 제공하던 습관이 있다면 고치자.
용어 정리
한글명
|
영어명
|
정적 팩토리 메서드
|
static factory method
|
인스턴스
|
instance
|
API
|
Application Programming Interface
|
박싱 클래스
|
boxed class
|
팩토리 메서드 패턴
|
factory method pattern
|
싱글턴
|
singleton
|
불변 클래스
|
immutable class
|
동반 클래스
|
companion class
|
서비스 제공자 프레임워크
|
service provider framework
|
상속
|
inheritance
|
'Java' 카테고리의 다른 글
[Effective Java] Item5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2024.03.21 |
---|---|
[Effective Java] Item4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2024.03.21 |
[Effective Java] Item3. private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2024.03.20 |
[Effective Java] Item2. 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2024.03.19 |
Effective Java 3/E 정리 (0) | 2024.03.19 |