들어가기 전에
객체를 만들 때 new 생성자를 이용한다.
하지만 프로그래머는 생성자의 매개변수만을 보고 어떤 객체가 반환될 것인지 예측하기 어렵다
정적 팩토리 메서드를 사용해서 생성자 대신 사용해보자!
정적 팩토리 메서드란
객체 생성의 역할을 하는 클래스 메서드로 생성자(Constructor)를 통해서가 아닌 Static Method를 통해서 객체를 생성하는 역할을 한다. 여기서 팩토리는 객체를 생성하는 역할을 분리한 것이라고 한다.
디자인패턴인 팩토리 메서드 패턴과 관련이 없다.
예시를 들어서 먼저 확인
- new로 선언
String 객체를 선언할 때 new를 이용한다.
String str = new String("hohodu");
- valueOf와 같이 메소드 이용
String value1 = String.valueOf(1);
String value2 = String.valueOf(1.0L);
String value3 = String.valueOf(true);
String value4 = String.valueOf('a');
public static String valueOf(int i) {
return Integer.toString(i);
}
public static String valueOf(long l) {
return Long.toString(l);
}
public static String valueOf(boolean b) {
return b ? "true" : "false";
}
public static String valueOf(char c) {
if (COMPACT_STRINGS && StringLatin1.canEncode(c)) {
return new String(StringLatin1.toBytes(c), LATIN1);
}
return new String(StringUTF16.toBytes(c), UTF16);
}
public static String valueOf(char data[]) {
return new String(data);
}
String.java의 valueOf를 사용하면 int,long과 같은 매개변수 타입에 의존하여 새로운 String 객체를 생성해줘서 반환해준다.
결론 : 둘이 똑같이 String 객체를 만들어준다
그럼 왜 정적 팩토리 메서드를 사용할까?
이펙티브 자바에서 나오는 정적 팩토리 메서드의 장점은 아래와 같다
1.이름을 가질 수 있다.
2.호출 될 때마다 인스턴스를 새로 생성하지 않아도 된다.
3.반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
하나하나씩 살펴보자
1. 이름을 가질 수 있다.
메소드 이름에 객체 생성 목적을 담을 수 있다.
User라는 클래스가 있다. 생성자는 멤버변수 name,email,country를 넘겨줘야 객체를 생성해줄 수 있다.
public class User {
private final String name;
private final String email;
private final String country;
public User(String name, String email, String country) {
this.name = name;
this.email = email;
this.country = country;
}
}
createWithDefaultCountry라는 이름의 메소드는 korea라는 나라를 디폴트로 User 객체를 생성해주기 때문에 의미를 담을 수 있다.
public static User createWithDefaultCountry(String name, String email) {
return new User(name, email, "Korea");
}
그러므로 반환될 객체의 특성을 한번에 유추할 수 있어 가독성이 좋아진다
2. 호출 될 때마다 인스턴스를 새로 생성하지 않아도 된다.
private 생성자를 통하지 않고 정적 팩토리 메소드를 이용해 반환하기 때문에 멤버변수가 쉽게 바뀌지 못하도록 한다.
Boolean 클래스로 예를 들어보자.
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);
}
......
}
Boolean클래스는 static final로 Boolean 객체를 미리 생성하여 상수로 사용하고 있다.
valueOf 메소드를 호출하여 새로 값을 생성해주는 것이 아니라 미리 생성해 놓은 것을 반환하고 있다.
여기서 중요한 것은 미리 생성된 TRUE와 FALSE의 캐싱을 통해 새로운 객체 생성의 부담을 덜 수 있다.
또한 객체 생성을 정적 팩토리 메서드로만 가능하게 하여 제한할 수 있다. 이를 통해 잘못된 Boolean 값이 생성되는 것을 막을 수 있는 장점이 있다.
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
즉, 하위 자료형 객체를 반환할 수 있다.
생성자의 역할을 하는 정적 팩토리 메서드가 반환값을 가지고 있기 때문에 가능하다.
아래의 예시는 Basic,Intermediate, Advances 클래스가 Level 이라는 상위 클래스를 상속받고 있는 구조이다.
점수에 따라 등급을 반환하는 of 라는 정적 팩토리 메소드는 하위 클래스를 반환할 수 있다.
public class Level {
...
public static Level of(int score) {
if (score < 50) {
return new Basic();
} else if (score < 80) {
return new Intermediate();
} else {
return new Advanced();
}
}
...
}
단점
정적 팩토리 메서드만 존재하는 클래스를 생성할 경우 상속이 불가하다.
상속을 하려면 public이나 protected 생성자가 필요하기 때문이다.
마지막으로 정적 팩터리 메서드는 프로그래머가 찾기 어려우므로 메서드 이름을 널리 알려진 규약을 따라 짓도록 하자!
네이밍 컨벤션
from: 하나의 매개 변수를 받아서 객체를 생성
of: 여러 매개 변수를 받아서 객체를 생성
valueOf: from과 of의 더 자세한 버전
instance 혹은 getInstance: (매개변수를 받는다면) 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않는다.
create 혹은 newInstance: 인스턴스 생성, 매번 새로운 인스턴스임을 보장한다.
get[type] : 타입에 해당하는 인스턴스 반환(같은 인스턴스임을 보장하지 않음)
FileStore fs = Files.getFilesStore(path)
new[type]: 타입에 해당하는 인스턴스 반환(새로운 인스턴스 보장)
Reference
https://www.baeldung.com/java-constructors-vs-static-factory-methods
https://tecoble.techcourse.co.kr/post/2020-05-26-static-factory-method/
이펙티브 자바 Effective Java 3/E (조슈아블로크)
'IT 도서 > 이펙티브 자바' 카테고리의 다른 글
[클래스와 인터페이스] 추상 클래스보다는 인터페이스를 우선하라 (0) | 2023.08.01 |
---|---|
[클래스와 인터페이스] 상속보다는 컴포지션을 사용하라 (0) | 2023.08.01 |
생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2023.07.02 |