클래스의 인스턴스를 얻을 때 일반적으로 public 생성자를 사용한다. 하지만 개발자는 클래스에 생성자 대신(혹은 함께) 정적 팩토리 메소드를 제공해 클래스의 인스턴스를 반환할 수 있다.
public class Person {
private String name;
private int age;
// 생성자를 통한 인스턴스 생성
private Person(String name, int age) {
this.name = name;
this.age = age;
}
// 정적 팩토리 메소드: 이름과 나이를 받아 Person 객체 생성
public static Person createPerson(String name, int age) {
return new Person(name, age);
}
// 정적 팩토리 메소드: 이름만 받고 나이를 기본값으로 설정해 Person 객체 생성
public static Person createPersonWithDefaultAge(String name) {
return new Person(name, 30); // 기본 나이를 30으로 설정
}
// 정적 팩토리 메소드: 나이만 받고 이름을 기본값으로 설정해 Person 객체 생성
public static Person createPersonWithDefaultName(int age) {
return new Person("John Doe", age); // 기본 이름을 "John Doe"로 설정
}
}
해당 예제 코드에서 Person 클래스는 생성자를 통해서 인스턴스를 만들 수도 있고 여러개의 정적 팩토리 메소드를 통해서도 인스턴스를 만들 수 있다.
장점 1. 이름을 가질 수 있다. (반환될 객체 특성 설명 가능)
생성자의 이름은 클래스의 이름을 그대로 사용한다. 매개변수의 순서를 다르게하여 여러 개의 생성자를 만들 수 있지만, 각각의 생성자가 어떤 역할을 하는지 명확히 알 수 없다. 하지만 정적 팩토리 메소드는 메소드의 이름을 지을 수 있어 그 용도를 명확히 표현할 수 있다.
// probablePrime를 생성자로 만들 경우 소수를 만든다는 사실을 쉽게 알 수 없다.
public BigInteger(int bitLength, Random rnd)
// 메소드의 이름으로 소수를 반환한다는 사실을 알 수 있다.
public static BigInteger probablePrime(int bitLength, Random rnd)
장점 2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.
정적 팩토리 메소드를 사용하면 인스턴스를 미리 만들어 두거나 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다. 클래스의 생성자를 사용할 수 없도록 만들고 정적 팩토리 메소드를 통해서만 인스턴스를 만들 수 있도록 하면, 프로그램 전체에서 사용되는 클래스의 인스턴스를 통제할 수 있다. 이를 통해 클래스를 싱글톤으로 만들거나 인스턴스화 불가로 만들 수 있다. 또한 불변 값 클래스에서 동치인 인스턴스가 단 하나뿐임을 보장할 수 있다.(a == b 일 때만 a.equals(b)가 성립)
// Boolean 클래스는 valueOf 메소드에서 새로 인스턴스를 생성하지 않고 미리 만들어둔 인스턴스를 재활용한다.
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
장점 3. 반환 타입의 하위타입 객체를 반환할 수 있다.
클래스의 생성자는 해당 클래스의 인스턴스만 만들 수 있다. 하지만 정적 팩토리 메소드를 사용하면 하위 클래스의 인스턴스까지 반환할 수 있어 반환할 객체의 클래스를 자유롭게 선택할 수 있는 엄청난 유연성을 제공한다. 새로운 인터페이스와 수많은 구현 클래스가 있을 때, 구현 클래스의 생성자로 인스턴스를 만드는 게 아니라 인터페이스의 정적 팩토리 메소드로 인스턴스를 만들어서 개발자가 수많은 구현 클래스들을 이해하지 않고도 인터페이스를 사용할 수 있다.
// List 인터페이스로 구현 클래스의 인스턴스를 만들었다.
List<Integer> list = List.of(1, 2, 3, 4, 5);
장점 4. 입력 매개변수에 따라 다른 클래스의 객체를 반환할 수 있다.
클래스의 생성자는 해당 클래스의 인스턴스만 만들 수 있다. 하지만 정적 팩토리 메소드를 사용하면 상황에 따라 다른 클래스의 객체를 반환할 수 있다. 예를 들어 적은 메모리를 사용해야 하는 경우와 그 반대의 경우에 따라 다른 클래스 인스턴스를 반환함으로써 자원을 효율적으로 사용할 수 있다.
// EnumSet의 정적 팩토리 메소드는 경우에 따라 RegularEnumSet, JumboEnumSet 두개의 클래스의 인스턴스를 반환한다.
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과 JomboEnumSet 클래스의 존재를 모르고 다음 개발자는 릴리즈 때 이를 삭제해도, 성능을 개선한 또 다른 클래스를 추가할 수 있다.
장점 5. 정적 팩토리 메소드를 작성하는 시점에 반환할 객체의 클래스가 존재하지 않아도 된다.
생성자를 이용해 인스턴스를 만들었을 때는 클래스가 존재해야 생성자가 존재할 수 있다. 정적 팩토리 메소드는 메소드와 반환할 타입만 정해두고 실제 반환될 클래스는 나중에 구현하는 것이 가능하다.
public class JdbcExample {
public static Connection createConnection(String jdbcUrl, String username, String password) throws SQLException {
String jdbcDriver = "com.mysql.cj.jdbc.Driver";
try {
// 드라이버 클래스 동적으로 로드
Class.forName(jdbcDriver);
} catch (ClassNotFoundException e) {
throw new SQLException("JDBC driver not found");
}
...
}
}
단점 1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메소드만 제공하면 하위 클래스를 만들 수 없다.
정적 팩토리 메소드만을 사용하게 하려면 기존 생성자는 private으로 해야 하고 그렇게 되면 상속을 할 수 없게 된다. 하지만 이 단점은 상속보다 컴포지션 사용을 유도하고 불변 타입으로 만들려면 이 제약을 지켜야 한다는 점에서 장점으로 받아들일 수도 있다.
단점 2.정적 팩토리 메소드는 프로그래머가 찾기 어렵다.
생성자는 기본 문법이기 때문에 개발자가 새로운 공부 없이 바로 사용할 수 있지만 정적 팩토리 메소드는 개발자가 해당 클래스에 정적 팩토리 메소드가 있다는 사실을 알아야 한다. 이 단점을 완화하기 위해서는 알려진 규약을 사용하여 정적 팩토리 메소드를 만들어야 한다.
// from: 한개의 매개변수를 받아 해당 타입의 인스턴스를 반환
Date d = Date.from(param);
// of: 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환
Set<Rank> faceCards = EnumSet.of(param, param, param);
// valueOf: from, of와 유사. 더 자세한 버전
BigInteger bi = BigInteger.valueOf(param)
// instance, getInstnce: 매개변수로 명시한 인스턴스를 반환, 같은 인스턴스 보장 X
StackWalker luke = StackWalker.getInstance(param);
// create, newInstance: 매번 새로운 인스턴스를 생성해 반환
Object newArray = Array.newInstance(param)
// getType: getInstnce와 같으나 다른 클래스의 인스턴스를 반환
FileStore fs = Files.getFileStore(param);
// newType: newInstnce와 같으나 다른 클래스의 인스턴스를 반환
BufferedReader br = Files.newBufferedReader(param);
// type: getType, newType와 유사. 더 간결한 버전
List list = Collections.list(param);
'Study > 이펙티브 자바' 카테고리의 다른 글
[effectice java] item 4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2023.09.26 |
---|---|
[effectice java] item 3. private 생성자나 열거 타입으로 싱글톤임을 보장하라 (0) | 2023.09.24 |
[effectice java] item 2. 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2023.09.22 |