[effective java] 아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라.
클래스의 인스턴스를 얻는 방법은 두 가지가 있다.
첫 번째는 public 생성자를 이용하는 전통적인 수단, 두 번째는 정적 팩터리 메서드를 이용하는 방법이 있다.
cf) 정적 팩터리 메서드는 해당 클래스의 인스턴스를 반환하는 정적 메서드를 뜻한다.
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
이번 포스트에서는 정적 팩터리 메서드의 장단점에 대해 알아볼 것이다.
장점 1. 이름을 가질 수 있다.
생성자에 제공하는 매개변수가 반환될 객체의 특성을 제대로 설명하지 못하는 경우, 정적 팩터리 메서드를 사용하는 것을 고려하자. 예시는 아래와 같다.
public class Student {
String name;
public Student(String name) { // public 생성자를 이용한 경우
this.name = name;
}
public static Student withName(String name) { // 정적 팩터리 메서드를 이용한 경우
return new Student(name);
}
public static void main(String[] args) {
Student student1 = new Student("name1");
Student student2 = Student.withName("name2");
}
}
또, 생성자는 시그니처에 제약이 있다. 똑같은 타입을 파라미터로 받는 생성자 두개를 만들 수 없으므로 정적 팩터리 메서드를 고려하자. 따라서 클래스에 시그니처가 같은 생성자가 여러 개 필요하다면 생성자를 정적 팩터리 메서드로 바꾸어 각각의 차이를 잘 드러내는 이름을 지어주자. 예시는 아래와 같다.
cf) 매개변수의 순서를 바꾸어 제한을 피할 수는 있으나, 가독성이 떨어진다.
public class Student {
String name;
String address;
public Student() {
}
public Student(String name) {
this.name = name;
}
/* 불가능
public Student(String address) {
this.address = address;
}
*/
public static Student withName(String name) {
return new Student(name);
}
public static Student withAddress(String address) {
Student student = new Student();
student.address = address;
return student;
}
public static void main(String[] args) {
Student student1 = new Student("name1");
Student student2 = Student.withName("name2");
}
}
장점 2. 반드시 새로운 객체를 만들 필요가 없다.
즉, 불필요한 객체 생성을 피할 수 있으므로 성능이 향상된다. 불변 클래스인 경우 또는 매번 새로운 객체를 만들 필요가 없는 경우, 미리 만들어둔 인스턴스 또는 캐시해둔 인스턴스를 반환할 수 있다. 예시는 아래와 같다.
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
public class Student {
String name;
String address;
public Student() {
}
private static final Student student = new Student();
...
public static Student getStudent() {
return student;
}
public static void main(String[] args) {
Student student = Student.getStudent();
}
}
인스턴스의 생명주기를 통제할 수 있으므로 이러한 클래스를 인스턴스 통제 클래스라고 한다. 인스턴스를 통제하는 이유는 아래와 같다.
1. 싱글턴으로 만들 수 있음
2. 인스턴스화 불가로 만들 수 있음
3. 불변 값 클래스에서 동치인 인스턴스가 단 하나뿐임을 보장할 수 있음
4. 플라이웨이트 패턴의 근간이 되며, 열거 타입은 인스턴스가 하나만 만들어짐을 보장한다.
👉 이해 못함(p.9)
* 불변 클래스
말 그대로 변경이 불가능한 클래스이다. 대표적인 예로 String, Boolean, Integer, Float, Long 등이 있다. 객체가 안전하다는 장점이 있으나, 새로운 객체를 생성해야 하므로 성능 저하가 발생한다는 단점이 있다.
[Java] Immutable Class (불변 클래스)
안녕하세요 이번시간은 Immutable Class(불변 클래스)에 대해서 알아보겠습니다. Immutable 란? Immutable을 사전적으로 찾아보면, 불변의, 변경할 수 없는 이라는 뜻임을 알 수 있습니다. 사전적인 의미에
limkydev.tistory.com
* 플라이웨이트 패턴
어떤 클래스의 인스턴스 한 개만 가지고 여러 개의 "가상 인스턴스"를 제공하고 싶을 때 사용하는 패턴이다. 즉, 인스턴스를 공유하여 메모리 낭비를 줄이는 방식이다.
[디자인패턴/Design Pattern] Flyweight Pattern / 플라이웨이트 패턴
관련 내용은 [자바 언어로 배우는 디자인 패턴 입문],[Head First Design Pattern]의 내용을 참고해서 정리한 글입니다. 잘못된 부분은 댓글로 피드백 부탁드립니다. 1. Flyweight 패턴이란? 어떤 클래스의
lee1535.tistory.com
장점 3. 리턴 타입의 하위 타입 인스턴스를 만들 수 있다.⭐
얘는 다음에 더 자세히 공부하는 걸로..헷갈령
다시 말해 반환 타입의 하위 타입 객체를 반환할 수 있다. 이를 이용해 인터페이스를 정적 팩터리 메소드의 반환 타입으로 사용하는 인터페이스 기반 프레임워크를 만드는 핵심 기술이기도 하다.(p.10)
예를 들어, Collection 인터페이스는 여러 하위 인터페이스 및 클래스를 가지고 있다.

따라서 Collection 인터페이스를 구현한 클래스의 정적 팩터리 메소드가 해당 인터페이스의 하위 클래스를 반환하면, 더 구체적인 클래스를 선택적으로 생성할 수 있다. 이를 통해, 클라이언트 코드에서는 생성할 클래스를 명시적으로 지정할 필요 없이, 해당 인터페이스를 통해 구현된 여러 클래스 중에서 적합한 클래스를 선택할 수 있다.
더 구체적인 예를 통해 이해해보자.
Collections 클래스에서는 List, Set, Queue 등 다양한 컬렉션 인터페이스를 구현한 클래스들의 인스턴스를 생성하는 정적 팩터리 메소드를 제공한다. 이러한 팩터리 메소드들은 해당 인터페이스의 하위 클래스를 반환한다.
public class Collections {
public static <T> List<T> emptyList() {
return (List<T>) EmptyList.INSTANCE;
}
public static <T> Set<T> emptySet() {
return (Set<T>) EmptySet.INSTANCE;
}
public static <T> Queue<T> emptyQueue() {
return (Queue<T>) EmptyQueue.INSTANCE;
}
// ...
}
위 코드에서 emptyList(), emptySet(), emptyQueue() 메소드들은 각각 List, Set, Queue 인터페이스의 하위 클래스를 반환한다.
이제 클라이언트 코드에서는 이러한 정적 팩터리 메소드를 사용하여 컬렉션 인스턴스를 생성할 수 있다. 예를 들어, 아래 코드에서는 Collections.emptyList() 메소드를 사용하여 List 인스턴스를 생성한다.
List<String> emptyList = Collections.emptyList();
이렇게 하면 클라이언트 코드에서는 List 인터페이스를 통해 생성된 emptyList 변수를 사용할 수 있다. 이때 실제로 생성된 객체는 List 인터페이스의 하위 클래스 중 하나일 것이다. 즉, 명시적으로 특정 클래스를 지정할 필요 없이, 해당 인터페이스를 통해 구현된 여러 클래스 중에서 적합한 클래스를 선택할 수 있다.
다음으로 자바 버전에 따른 특징를 알아보자.
자바 8 이전 | static 메소드 추가 불가능 |
자바 8 | 인터페이스에 public static 메소드 추가 가능, private static 메소드 추가 불가능 |
자바 9 | 인터페이스에 public static 메소드, private static 메소드 추가 가능 |
따라서,
자바 8 이전 | Type인 인터페이스를 반환하는 정적 메소드가 필요하면 Types라는 (인스턴스화 불가인) 동반 클래스를 만들어 그 안에 정의한다. (대표적인 예 : java.util.Collections) |
자바 8 | 인터페이스에 public static 메소드를 사용해서 그 인터페이스의 구현체를 메소드로 제공할 수도 있지만, private static 메소드가 필요한 경우 기존처럼 별도의 클래스를 사용한다. |
자바 9 | 인터페이스에 public static 메소드, private static 메소드를 사용해서 그 인터페이스의 구현체를 메소드로 제공할 수 있다. |
그 예시로 아래 코드를 확인하자.
public interface StudentInterface {
public static Student getStudent() {
return new Student();
}
}
장점 4. 리턴하는 객체의 클래스가 입력 매개변수에 따라 매번 다를 수 있다.
그래서 왜 좋은건지 설명이 부족한 것 같아 gpt에게 물어봤다.

장점 5. 리턴하는 객체의 클래스가 public static 팩토리 메소드를 작성할 시점에 반드시 존재하지 않아도 된다.⭐
얘도 다시 공부..
인터페이스나 클래스가 만들어지는 시점에서 하위 타입의 클래스가 존재하지 않아도 나중에 만들 클래스가 기존의 인터페이스나 클래스를 상속 받으면 언제든지 의존성을 주입 받아서 사용이 가능하다. 반환값이 인터페이스가 되며 정적 팩터리 메소드의 변경 없이 구현체를 바꿔 끼울 수 있다.
public class School {
/* School는 인터페이스이고 구현체가 없음에도 아래와 같은 메서드 작성이 가능하다.*/
public static List<School> getStudents(){
return new ArrayList<>();
}
}
출처 : https://a1010100z.tistory.com/entry/아이템-1-생성자-대신-정적-팩터리-메서드를-고려하라
단점 1. public 또는 protected 생성자 없이 static public 메소드만 제공하는 클래스는 상속할 수 없다.
당연한 말이다. 자바에서 상속이 가능한 클래스는 부모 클래스의 생성자를 호출하여 객체를 생성하게 되는데, 이때 생성자는 부모 클래스 내에서만 호출 가능하다. 하지만 public 또는 protected 생성자가 없는 클래스의 경우, 자식 클래스에서 이 생성자를 호출할 수 없으므로 상속이 불가능하다.
책에서는 불변 타입인 경우 상속 대신 컴포지션을 권장하기 때문에 단점이 아니라 장점일지도 모른다고 설명한다.

단점 2. 프로그래머가 static 팩토리 메소드를 찾는게 어렵다.
생성자는 Javadoc 상단에 모아서 보여주지만 static 팩토리 메소드는 API 문서에서 특별히 다뤄주지 않는다. 따라서 팩토리 메소드에 대한 문서를 제공하고, 이름도 널리 알려진 규약을 따라 짓는 것을 권장한다.
요약
static 팩토리 메소드와 public 생성자의 장단점을 고려해서 사용하자.
정적 팩토리가 유리한 경우가 많으므로 public 생성자만 무작정 사용한다면 습관을 고치자.