정적 팩터리와 생성자에 선택적 매개변수가 많을 때 고려할 수 있는 방안
대안1: 점층적 생성자 패턴 또는 생성자 체이닝
매개변수가 늘어나면 클라이언트 코드를 작성하거나 읽기 어렵다
// 코드 2-1 점층적 생성자 패턴 - 확장하기 어렵다! (14~15쪽)
public class NutritionFacts {
private final int servingSize; // (mL, 1회 제공량) 필수
private final int servings; // (회, 총 n회 제공량) 필수
private final int calories; // (1회 제공량당) 선택
private final int fat; // (g/1회 제공량) 선택
private final int sodium; // (mg/1회 제공량) 선택
private final int carbohydrate; // (g/1회 제공량) 선택
// chaining - calories ... carbohydrate 까지 는 calories = 0 으로 대입하여 끝
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) {
NutritionFacts cocaCola =
new NutritionFacts(10, 10);
}
}
대안2: 자바빈즈 패턴
완전한 객체를 만들려면 메서드를 여러번 호출해야 한다. (일관성이 무너진 상태가 될 수도 있다.)
// 단점 - 문서화 방법 뿐이다.
public NutritionFacts() { }
+
Getter, Setter
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);
}
클래스를 불변으로 만들 수 없다
권장하는 방법: 빌더 패턴
플루언트 API 또는 메서드 체이닝을 한다.
public static void main(String[] args) {
NutritionFacts cocaCola = new Builder(240, 8)
.calories(100)
.sodium(35)
.carbohydrate(27).build();
}
===
// lombok 에서는 필수적인 값들은 생성자로 받고 나머지는 builder 가 현재는 불가능하다.
// 위에 처럼 lombok 을 사용한다면 가능하다.
@Builder
@AllargsConstructor(access = AccessLevel.private) -> 내부의 builder 만 사용할 수 있음(극복 방법)
계층적으로 설계된 클래스와 함께 사용하기 좋다.
점층적 생성자보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고, 자바빈즈보다 훨씬 안전하다
// 코드 2-4 계층적으로 설계된 클래스와 잘 어울리는 빌더 패턴 (19쪽)
// 참고: 여기서 사용한 '시뮬레이트한 셀프 타입(simulated self-type)' 관용구는
// 빌더뿐 아니라 임의의 유동적인 계층구조를 허용한다.
public abstract 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(); // 아이템 50 참조
}
}
===
// 계층적 빌더 사용 (21쪽)
public class PizzaTest {
public static void main(String[] args) {
NyPizza pizza = new NyPizza.Builder(SMALL)
.addTopping(SAUSAGE)
.addTopping(ONION).build();
Calzone calzone = new Calzone.Builder()
.addTopping(HAM).sauceInside().build();
System.out.println(pizza);
System.out.println(calzone);
}
}
===
public abstract 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(); // 아이템 50 참조
}
}
완벽 공략
p15, 자바빈즈, 게터, 세터
p17, 객체 얼리기 (freezing)
Java 에서는 final 키워드로 인한 가변과 불변 객체를 만들 수 있다.
Object.freeze()에 전달한 객체는 그뒤로 변경될 수 없다.
strict 모드에서만 동작함
const keesun = {
'name': 'Keesun',
'age': 40
};
Object.freeze(keesun);
keesun.kids = ["서연"];
console.info(keesun.name);
p17, 빌더 패턴

p19, IllegalArgumentException
잘못된 인자를 넘겨 받았을 때 사용할 수 있는 기본 런타임 예외
RuntimeException 을 상속받은 예외
어떤 Argument 가 잘못되었는지, 왜 잘못되었는지 표시해 주는 것이 좋다.
public class Order {
public void updateDeliveryDate(LocalDate deliveryDate) {
if (deliveryDate == null) {
throw new NullPointerException("deliveryDate can't be null");
}
if (deliveryDate.isBefore(LocalDate.now())) {
//TODO 과거로 배송 해달라고??
throw new IllegalArgumentException("deliveryDate can't be earlier than " + LocalDate.now());
}
// 배송 날짜 업데이트
}
}
간혹 메소드 선언부에 unchecked exception을 선언하는 이유는?
커스텀 예외보다는 기존의 예외를 먼저 사용하는 것이 좋다.
P21, 가변인수 (varargs) 매개변수를 여러 개 사용할 수 있다
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(1, 20, 20, 39, 59);
}
}