코딩은 마라톤

[이펙티브자바] 아이템1, 2, 3 요약 정리 본문

Language/Java

[이펙티브자바] 아이템1, 2, 3 요약 정리

anxi 2024. 5. 28. 22:37

아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라

정적 팩터리 메서드 : 그 클래스의 인스턴스를 반환하는 정적 메서드

 

장점(5)

1. 반환될 객체의 특성을 쉽게 묘사할 수 있는 "이름" 을 지을 수 있다.

// 생성자
new Bake(String, String, String)

// 정적 팩터리 메서드
Bake.bakeCookie(String, String, String)

 

2. 호출될 때마다 새로 인스턴스를 생성하지 않아도 된다.

public static Boolean valueOf(boolean value) {
	return value ? true : false;
}

// valueOf 메서드는 true, false를 미리 만들어놓고 반환하기 때문에 
// 자주 요청해도 요청 횟수만큼 생성되는 것이 아니라 성능 향상의 이점이 있다.

 

- 불변 클래스에서 동치인 인스턴스가 단 하나뿐임을 보장할 수 있고 (a==b -> a.equals(b)),

- 인스턴스를 캐싱하여 재활용하여 불필요한 객체 생성을 막을 수 있다.

 

3. 반환 타입의 하위 타입 객체를 반환할 수 있다.

public interface Animal {

   static Animal getFourLegs() {
    	return new fourLegsAnimal();
    }
    static Animal getTwoLegs() {
    	return new twoLegsAnimal();
    }

 

- Animal 타입의 하위 타입 객체인 fourLegsAnimal와 twoLegsAnimal을 얻을 수 있다.

 

4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

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)
        // 64개 이하면 RegularEnumSet의 인스턴스 반환
            return new RegularEnumSet<>(elementType, universe);
        else
        // 65개 이상이면 JumboEnumSet 인스턴스 반환
            return new JumboEnumSet<>(elementType, universe);
    }

 

5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

서비스 제공자 프레임워크(service provider framework)를 만드는 근간이 된다. ex) JDBC

 

단점(2)

1. 상속을 하려면 public, protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스는 만들 수 없다.

2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다. (생성자처럼 API 설명에 명확하게 드러나지 않기 때문)

 

명명 방식

  • from : 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환 (형변환 메서드)
Date date = Date.from(instant);
  • of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환 (집계 메서드)
Set<Food> foods = EnumSet.of(PIZZA, CHICKEN);
  • valueOf : from과 of의 더 자세한 버전
int x = Integer.valueOf("10");
  • Instance / getInstance : 매개변수로 명시한 인스턴스를 반환하나, 같은 인스턴스를 보장하지 않음
Speed speed = Speed.getInstance();
  • create / newInstance : 매번 새로운 인스턴스 생성을 보장 (instance / getInstance의 매번 새로운 인스턴스 생성)
Object newArray = Array.newInstance(arrLength);
  • getType : getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 정의
DarkChoco dc = Choco.getDarkChoco();
  • newType : newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 정의
DarkChoco dc = Choco.getDarkChoco();
  • type : getType과 newType의 간결한 버전
List<Food> foods = Collections.list(foods);

 

아이템 2. 생성자에 매개변수가 많다면 빌더를 고려해라

선택적 매개변수가 많을 때 대응방법(3)

1. 점층적 생성자 패턴 사용

public class SweetFood {
	private String choco;
    private String candy;
    private String jelly;
    ...
    
    public SweetFood(String choco) {
    	this(choco, "candy");
    }
    public SweetFood(String choco, String candy) {
    	this(choco, candy, "jelly");
    }
    public SweetFood(String choco, String candy, String jelly) {
    	this(choco, candy, jelly, "~~~");
    }

 

단점 : 원치 않은 매개변수에도 값 지정, 매개변수가 많아지면 클라이언트 코드를 읽기 어려움.

 

2. 자바빈즈패턴

매개변수가 없는 생성자로 객체를 만들고, 세터(setter) 메서드를 호출해 원하는 매개변수 값 설정

단점 : 객체가 완전히 만들려면 여러 번의 메서드 호출 및 완전 생성되기 전까지 일관성 깨짐 (불변 X)

 

3. 빌더 패턴

- 점층적 생성자 패턴의 안정성 + 자바 빈즈 패턴의 가독성

- 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자(정적 팩터리 메서드)를 호출해 빌더 객체를 얻는다.

- 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 선택 매개변수를 설정한다.

- build 메서드를 호출해 (보통 불변인) 객체를 갖는다.

 

생성

public class SweetFood {
    private String choco;
    private String candy;
    private String jelly;
    
    public static class Builder {
    	
        // 필수 매개변수
        private final String choco;
        
        // 선택 매개변수
        private String candy = "";
        private String jelly = "";
        
        public Builder(String choco) {
        	this.choco = choco;
        }
        
        public Builder candy(String val) {
        	this.candy = candy;
            return this;
        }
        public Builder jelly(String val) {
        	this.jelly = jelly;
            return this;
        }
        public SweetFood build() {
        	return new SweetFood(this);
        }
    }
    private SweetFood(Builder builder) {
    	this.choco = builder.choco;
        ...
    }

 

사용

SweetFood sweetFood = SweetFood.Builder("가나초콜릿").candy("추파춥스").jelly("하리보").build();

 

장점 (3) 

빌더 하나로 여러 객체를 순회하면서 만들 수 있다.

빌더에 넘기는 매개변수에 따라 다른 객체를 만들 수도 있다.

부여되는 특정 필드는 빌더가 알아서 채우도록 할 수도 있다.

 

단점 (1)

빌더 생성 비용이 크지는 않지만 성능에 민감한 상황에선 문제가 될 수 있다.

 

결론 : 생성자나 정적 팩터리가 처리할 매개변수가 많으면 그냥 빌더를 쓰자.

 

아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라

싱글턴(singleton) : 인스턴스를 오직 하나만 생성할 수 있는 클래스

ex) 함수와 같은 무상태 객체, 설계상 유일해야하는 시스템 컴포넌트

 

싱글턴 만드는 방식

공통 : 생성자는 private으로 감춰둔다.

 

1. 유일한 인스턴스에 접근할 수 있는 수단으로 public static 멤버 생성

public class Car {
	public static final Car INSTANCE = new Car();
    private Car(){}...
}

 

1번 장점(3)

- 해당 클래스가 싱글턴임이 API에 명시적으로 보여진다.

- final 객체라 절대 다른 객체를 참조할 수 없다.

- 간결하다.

 

2. 정적 팩터리 메서드를 public static 멤버로 제공

public class Car {
	private static final Car INSTANCE = new Car();
    private Car(){}..
    
    public static Car getInstance() {
    	return INSTANCE;
    }

 

2번 장점(3)

- 현재 getInsance()를 이용해 INSTANCE만 반환하던 팩터리는 추후에 변경하여 각 쓰레드 별로 다양한 객체를 반환할 수 있게 변경할 수도 있다.

- 원한다면, 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다.

- 정적 팩터리의 메서드 참조를 공급자(supplier)로 사용할 수 있다.

위 장점이 해당하지 않음 1번(필드) 방식이 좋다.

 

3. 열거 타입 방식의 싱글턴 - 바람직한 방법

public enum Car {
	INSTANCE;
    
    ...
}

 

1번 방식과 비슷하지만 더 간결하고 추가 노력 없이 직렬화할 수 있으며 제2의 인스턴스가 만들어지는 것을 완벽히 막을 수 있다.

대부분의 상황에서는 원소가 하나뿐인 Enum 타입이 싱글턴을 만드는 가장 좋은 방식이다.