-
아이템2. 생성자에 매개변수가 많다면 빌더를 고려하라Java/이펙티브 자바 2022. 10. 31. 13:45
아이템2. 핵심정리1 - 생성자 체이닝과 자바빈즈
정적 팩터리와 생성자에 선택적 매개변수가 많을 때 고려할 수 있는 방안
- 대안1 : 점층적 생성자 패턴 또는 생성자 체이닝
ㆍ매개변수가 늘어나면 클라이언트 코드를 작성하거나 읽기 어렵다
ㆍ해당 값이 무엇인지, 전달 파라미터가 무엇인지 알기 힘들다
// 점층적 생성자 패턴 - 확장하기 어렵다! public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public NutritionFacts(int servingSize, int servings) { this(servingSize, servings, 0); } public NutritionFacts(int servingSize, int servings, int calories) { this(servingSize, servings, calories, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat) { this(servingSize, servings, calories, fat, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) { this(servingSize, servings, calories, fat, sodium, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) { this.servingSize = servingSize; this.servings = servings; this.calories = calories; this.fat = fat; this.sodium = sodium; this.carbohydrate = carbohydrate; } public static void main(String[] args) { // ctrl + p : 파라미터 확인 NutritionFacts nutritionFacts = new NutritionFacts(10,10); } }
- 대안2 : 자바빈즈 패턴
ㆍ완전한 객체를 만들려면 메서드를 여러번 호출해야한다.(일관성이 무너진 상태가 될 수 있다)
ㆍ클래스를 불변으로 만들 수 없다.
// 자바빈즈 패턴 - 일관성이 꺠지고, 불변으로 만들 수 없다. public class NutritionFacts { // 필드들이 (기본값이 있다면) 기본값으로 초기화된다. private int servingSize = -1; private int servings = -1; private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public NutritionFacts() { } public void setServingSize(int servingSize) { this.servingSize = servingSize; } public void setServings(int servings) { this.servings = servings; } public void setCalories(int calories) { this.calories = calories; } public void setFat(int fat) { this.fat = fat; } public void setSodium(int sodium) { this.sodium = sodium; } public void setCarbohydrate(int carbohydrate) { this.carbohydrate = carbohydrate; } public static void main(String[] args) { NutritionFacts cocaCola = new NutritionFacts(); cocaCola.setServingSize(240); cocaCola.setServings(8); cocaCola.setCalories(100); cocaCola.setSodium(35); cocaCola.setCarbohydrate(27); // 장점 : 객체생성이 간단해진다. // 단점 : 불완전한 상태로 객체가 사용될 수 있다. 불변객체로 만들기 어렵다. } }
ㆍ장점 : 객체생성이 간단해진다.
ㆍ단점 : 필수값이 세팅되지 않은 상태로 사용될 수도 있다. / 어디까지 세팅해야 완전한 객체인지 알기 어렵.
* 생성자 체이닝 + 자바빈스 패턴 *
- 단점 : 불변 객체로 만들기 힘들다.
아이템2. 핵심정리2 - 빌더
- 권장하는 방법 : 빌더 패턴
ㆍ플루언트 API 또는 메서드 체이닝을 한다
ㆍ계층적으로 설계된 클래스와 함께 사용하기 좋다
ㆍ점층적 생성자보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고, 자바빈즈보다 훨씬 간편하다
- 필수적인 필드와 필수적이지 않은 필드로 인해 생성자의 매개변수가 늘어난다.
& Immuterble하게 만들고 싶다. 할 때 사용
// 빌더 패턴 - 점층적 생성자 패턴과 자바빈즈 패턴의 장점만 취했다 public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder { // 필수 매개변수 private final int servingSize; private final int servings; // 선택 매개변수 - 기본값으로 초기화한다 private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder calories(int val) { calories = val; return this; } public Builder fat(int val) { fat = val; return this; } public Builder sodium(int val) { sodium = val; return this; } public Builder carbohydrate(int val) { carbohydrate = val; return this; } public NutritionFacts build() { return new NutritionFacts(this); } } private NutritionFacts(Builder builder) { this.servingSize = builder.servingSize; this.servings = builder.servings; this.calories = builder.calories; this.fat = builder.fat; this.sodium = builder.sodium; this.carbohydrate = builder.carbohydrate; } public static void main(String[] args) { NutritionFacts cocaCola = new Builder(240, 8) .calories(100) .sodium(35) .carbohydrate(27).build(); } }
@Builder @AllArgsConstructor(access = AccessLevel.PRIVATE) // @Builder(builderClassName = "Builder") // 빌더 네임 지정 public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static void main(String[] args) { NutritionFacts cocaCola = new NutritionFactsBuilder() // NutritionFacts cocaCola = new Builder() .calories(100) .sodium(35) .carbohydrate(27).build(); } }
- 어노테이션 사용시 단점1 : 모든 args를 이용해서 만드는 생성자가 생김
ㆍ@AllArgsConstructor(access = AccessLevel.PRIVATE) 로 해결 : 외부적으로 생성자 자체를 사용하지 못함.
- 어노테이션 사용시 단점2 : 필수 args를 지정하지 못한다.
ㆍ해결방안
// 필수 매개변수 public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public static void main(String[] args) { NutritionFacts cocaCola = new Builder(240, 8) .calories(100) .sodium(35) .carbohydrate(27).build(); }
ㆍ롬복 사용시 해결하기 어렵다.
*어노테이션 프로세서*
- 주석과 같은 어노테이션을 컴파일 시점에 읽어 코드를 조작할 수 있는 기능을 제공해준다.
아이템2. 핵심정리3 - 계층형 빌더
// 코드 2-4 계층적으로 설계된 클래스와 잘 어울리는 빌드 패턴 // 참고 : 여기서 사용한 '시뮬레이트한 셀프 타입(simulated self-type)' 관용구는 // 빌드뿐 아니라 임의의 유동적인 계층구조를 허용한다. public class Pizza { public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE } final Set<Topping> toppings; abstract static class Builder<T extends Builder<T>> { EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class); public T addTopping(Topping topping){ toppings.add(Objects.requireNonNull(topping)); return self(); } abstract Pizza build(); // 하위 클래스는 이 메서드를 재정의(overriding)하여 // "this"를 반환하도록 해야 한다. protected abstract T self(); } Pizza(Builder<?> builder) { toppings = builder.toppings.clone(); } }
// 코드 2-5 뉴욕 피자 - 계층적 빌더를 활용한 하위 클래스 public class NyPizza extends Pizza { public enum Size { SMALL, MEDIUM, LARGE } private final Size size; public static class Builder extends Pizza.Builder<Builder> { private final Size size; public Builder(Size size) { this.size = Objects.requireNonNull(size);} @Override public NyPizza build() { return new NyPizza(this); } @Override protected Builder self() { return this; } } private NyPizza(Builder builder){ super(builder); size = builder.size; } }
// 코드 2-6 칼초네 피자 - 계층적 빌더를 활용한 하위 클래스 public class Calzone extends Pizza{ private final boolean sauceInside; public static class Builder extends Pizza.Builder<Builder> { private boolean sauceInside = false; // 기본값 public Builder sauceInside() { sauceInside = true; return this; } @Override public Calzone build() { return new Calzone(this); } @Override protected Builder self() { return this; } } private Calzone(Builder builder){ super(builder); sauceInside = builder.sauceInside; } @Override public String toString() { return String.format("%s로 토핑한 칼초네 피자 (소스는 %s에)", toppings, sauceInside ? "안":"바깥"); } }
- self() 함수가 포인트!
: this를 사용하면 Pizza 클래스 자체를 반환해버림 하위클래스를 리턴해야함으로 추상클래스를 사용해서 하위 클래스 자기 자신을 리턴하도록 한다.
빌더의 상속구조
아이템2. 완벽 공략
p15, 자바빈즈, 게터, 세터
p17, 객체 얼리기 (freezing)
p17, 빌더 패턴
p19, IIlegalArgumentException
p20, 재귀적인 타입 한정을 이용하는 제네릭 타입
p21, 가변인수 (varargs) 매개변수를 여러 개 사용할 수 있다.
완벽 공략6 - 자바빈
- ( 주로 GUI에서 ) 재사용 가능한 소프트웨어 컴포넌트
- java.beans 패키지 안에 있는 모든 것
- 그 중에서도 자바빈이 지켜야 할 규약
ㆍ아규먼트 없는 기본 생성자
○ 객체를 만들기 편하기 때문이다.
ㆍgetter와 setter 메소드 이름 규약
ㆍSerializable 인터페이스 구현
○ 직렬화, 저장 후에 재사용하기 위해 사용
- 하지만 실제로 오늘날 자바빈 스팩 중에서도 getter와 setter가 주로 쓰는 이유는?
ㆍJPA나 스프링과 같은 여러 프레임워크에서 리플렉션을 통해 특정 객체의 값을 조회하거나 설정하기 때문
완벽 공략7 - 객체 얼리기 (freezing) : 임의의 객체를 불변 객체로 만들어주는 기능
- Object.freeze() 에 전달한 객체는 그뒤로 변경할 수 없다
ㆍ새 프로퍼티를 추가하지 못함
ㆍ기존 프로퍼티를 제거하지 못함
ㆍ기존 프로퍼티 값을 변경하지 못함
ㆍ프로토타입을 변경하지 못함
- strict 모드에서만 동작함
- 비슷한 류의 function으로 Object.seal() 과 Object.preventExtensions()가 있다.
- java에서는 final로 필드값변경(레퍼런스변경만), 상속을 막아준다.
완벽 공략8. 빌더 패턴 : 동일한 프로세스를 거쳐 다양한 구성의 인스턴스를 만드는 방법
- 복잡한 객체를 만드는 프로세스를 독립적으로 분리할 수 있다.
완벽 공략9. IllegalArgumentException : 잘못된 인자를 넘겨받았을때 사용할 수 있는 기본 런타임 예외
- 질문1) checked exception과 unchecked exception 의 차이?
ㆍcheked exception은 다시 예외를 던지거나 try~catch로 예외를 잡아야한다. 복구가 가능한 상황에서 예외를 던짐
- 질문2) 간혹 메소드 선언부에 unckecked exception을 선언하는 이유는?
ㆍ클라이언트에 명시적으로 알려주기 위해서
ㆍunchecked exception을 다 선언한다면 너무 많이 선언할 수 있기에 보통 checked exception만 표기한다.
- 질문3) checked exception은 왜 사용할까?
ㆍ클라이언트에게 액션을 강요할 때 사용
- 과제1) 자바의 모든 RuntimeException 클래스 이름 한번씩 읽어보기
ㆍ기존에 있다면 사용하고 없으면 만들 것!
public class Order { public void updateDeliveryDate(LocalDate deliveryDate){ if(deliveryDate ==null){ throw new NullPointerException("deliveryDate can't be null"); } if(deliveryDate.isBefore(LocalDate.now())){ throw new IllegalArgumentException("deliveryDate can't be earlier than " + LocalDate.now()); // 예외정보를 잘 적어줘라 } } }
완벽 공략10. 가변인수 : 여러 인자를 받을 수 있는 가변적인 argument(Var+args)
- 가변인수는 메소드에 오직 하나만 선언할 수 있다
- 가변인수는 메소드의 가장 마지막 매개변수가 되어야 한다.
public class VarargsSamples { public void printNumbers(int... numbers) { System.out.println(numbers.getClass().getCanonicalName()); // 타입 출력 System.out.println(numbers.getClass().getComponentType()); // 어떤 타입의 배열인지 출력 Arrays.stream(numbers).forEach(System.out::println); } public static void main(String[] args) { VarargsSamples samples =new VarargsSamples(); samples.printNumbers(5,10); } }
'Java > 이펙티브 자바' 카테고리의 다른 글
아이템5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) 2022.11.01 아이템4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) 2022.11.01 아이템3. 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) 2022.10.31 아이템1. 생성자 대신 정적 팩터리 메서드를 고려하라 (0) 2022.10.28 자바 이펙티브 - 1. 들어가기 (0) 2022.06.10