EffectiveJava

[EffectiveJava] 2장 객체 생성과 파괴

01. 생성자 대신 정적 팩터리 메서드를 고려하라

public static Boolean valuOf(boolean b) {
        return b ? Boolean.TRUE : Boolean.FALSE;
}
  • from : 매개 변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
Date d = Date.from(instant);
  • of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf : from과 of의 더 자세한 버전
BigInteger prime = BigInteger.valueOf(Integer.MAX\_VALUE);
  • instance 혹은 getInstance : (매개변수를 받는다면) 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않는다.
StackWalker luke = StackWalker.getInstance(option); 
  • create 혹은 newInstance : instance 혹은 getInstance와 같지만, 매번 새로운 인스턴스를 생성해 반환함을 보장한다.
Object newArray = Array.newInstance(classObject, arrayLen);
  • getType : getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다. “Type”은 메서드가 반환할 객체의 타입이다.
FileStore fs = Files.getFileStore(path);
  • newType : newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다. “Type”은 메서드가 반환할 객체의 타입이다.
BufferedReader br = Files.newBufferedReader(path);
  • type : getType과 newType의 간결한 버전
List litany = Collections.list(legacyLitany);

 

02. 생성자에 매개변수가 많다면 빌더를 고려하라

// 2-1 점층적 생성자 패턴 - 확장하기 어렵다!
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회 제공량)      선택

    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; 
    }
}
NutritionFacts cocaCola = new NutritionFacts (240,8,100,0,35,27);
// 2-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() { }
  // Setters
  public void setServingSize(int val)  { servingSize = val; }
  public void setServings(int val)     { servings = val; }
  public void setCalories(int val)     { calories = val; }
  public void setFat(int val)          { fat = val; }
  public void setSodium(int val)       { sodium = val; }
  public void setCarbohydrate(int val) { carbohydrate = val; }
}
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize(240);
cocaCola.setServings(8);
cocaCola.setCalories(100);
cocaCola.setSodium(35);
cocaCola.setCarbohydrate(27);
// 코드 2-3 빌더 패턴 - 점층적 생성자 패턴과 자바빈즈 패턴의 장점만 취했다. (17~18쪽)
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) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
                .calories(100).sodium(35).carbohydrate(27).build();
    }
}
// 2-4 계층적으로 설계된 클래스와 잘 어울리는 빌더 패턴
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 참조
   }
}
// 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;
   }
}
NyPizza pizza =  new NyPizza.Builder(SMALL)
      .addTopping(SAUSAGE).addTopping(ONION).build();
Calzone calzone = new Calzone.Builder()
      .addTopping(HAM).sauceInside().build();

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

// 3-1 public static final 필드 방식의 싱글턴
public class Elvis {
        **public static final Elvis INSTANCE = new Elvis();**
        private Elvis() {...}
        public void leaveTheBuilding() {...}
}
// 3-2 정적 팩터리 방식의 싱글턴
public class Elvis {
        **private** static final Elvis INSTANCE = new Elvis();
        private Elvis() {...}
        **public static Elvis getInstance()** { return INSTANCE; }

        public void leaveTheBuilding(){...}
}
// 싱글턴임을 보장해주는 readResolve 메서드
private Object readResolve(){
    // '진짜' Elvis를 반환하고, 가짜 Elvis는 가비지 컬렉터에 맡긴다.
    return INSTANCE;
}
// 3-3 열거 타입 방식의 싱글턴 - 바람직한 방법
public enum Elvis{
    INSTANCE;

    public void leaveTheBuilding(){...}
}

04. 인스턴스화를 막으려거든 private 생성자를 사용하라

// 4-1 인스턴스를 만들 수 없는 유틸리티 클래스
public class UtillityClass{
        // 기본 생성자가 만들어지는 것을 막는다 (인스턴스 방지용)
        private UtilityClass(){
                throw new AssertionError();
        }
        ... // 나머지 코드는 생략
}

05. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

// 5-1 정적 유틸리티를 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다.
public class SpellChecker {
    private static final Lexicon dictionary = ...;

    private SpellChecker() {} // 객체 생성 방지

    public static boolean isValid(String word) {...}
        public List<String> suggestions(String typo){...}
}
// 5-2 싱글턴을 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다.
public class SpellChecker {
    private final Lexicon dictionary = ...;

    private SpellChecker(...) {}
    public static SpellChecker INSTANCE = new SpellChecker(...);

    public static boolean isValid(String word) {...}
        public List<String> suggestions(String typo){...}
}
// 5-3 의존 객체 주입은 유연성과 테스트 용이성을 높여준다.
public class SpellChecker {
    private final Lexicon dictionary;

    **public SpellChecker(Lexicon dictionary) {**
        this.dictionary = Objects.requireNonNull(dictionary);
    }

    public boolean isValid(String word) {...}
        public List<String> suggestions(String typo){...}
}
Mosaic create(Supplier<? extneds Tile> tileFactory {...}

06. 불필요한 객체 생성을 피하라

String s = new String("bikini"); // 따라 하지 말 것!
String s = "bikini"; // 하나의 인스턴스를 사용하는 방법
Boolean b = new Boolean("true"); // 비권장 API
Boolean b = Boolean.valueOf("true"); // 권장 API
// 6-1 성능을 훨씬 더 끌어 올릴 수 있다!
static boolean isRomanNumeral(String s) {
    return s.matches("^(?-.)M*(C[MD]|D?C{0,3})"
            + "(X[CL]|L?X{0,3}) (I[XV]|V?I{0,3|)$");
}
// 6-2 값비싼 객체를 재사용해 성능을 개선한다.
public class RomanNumerals {
    private static final Pattern ROMAN = Pattern.compole(
            "^(?-.)M*(C[MD]|D?C{0,3})"
            + "(X[CL]|L?X{0,3}) (I[XV]|V?I{0,3|)$");

    static boolean isRomanNumeral(String s) {
        return ROMAN.matcher(s).matches();
    }
}
// 6-3 끔찍이 느리다! 객체가 만들어지는 위치를 찾았는가?
private static long sum() {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++) {
        sum += i;
    }

    return sum;
}

07. 다 쓴 객체 참조를 해제하라

// 7-1 메모리 누수가 일어나는 위치는 어디인가?
public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }

    /**
     * 원소를 위한 공간을 적어도 하나 이상 확보한다.
     * 배열 크기를 늘려야 할 때마다 대략 두 배씩 늘린다.
     */
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}
// 7-2 제대로 구현한 pop 메서드
public Object pop() {
    if (size == 0) throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] == null; // 다 쓴 참조 해제
    return result;
}

08. finalizer와 cleaner 사용을 피하라

// 8-1 cleaner를 안전망으로 활용하는 AutoCloseable 클래스
public class Room implements AutoCloseable {
    private static final Cleaner cleaner = Cleaner.create();

    // 청소가 필요한 자원. 절대 Room을 참조해서는 안 된다!
    private static class State implements Runnable {
        int numJunkPiles; // 방(Room) 안의 쓰레기 수

        State(int numJunkPiles) {
          this.numJunkPiles = numJunkPiles;
        }

        // close 메서드나 cleaner가 호출한다.
        @Override public void run() {
          System.out.println("방 청소");
          numJunkPiles = 0;
        }
    }

    // 방의 상태. cleanable과 공유한다.
    private final State state;

    // cleanable 객체. 수거 대상이 되면 방을 청소한다.
    private final Cleaner.Cleanable cleanable;

    public Room (int numJunkPiles) {
        state = new State(numJunkPiles);
        cleanable = cleaner.register(this, state);
    }

    @Override
    public void close() {
        cleanable.clean();
    }

}
public class Adult{
        public static void main(String[] args) {
                try (Room myRoom = new Room(7)) {
                  System.out.println("안녕~");
                }
        }
}
public class Teenager{
        public static void main(String[] args){
                new Room(99);
                System.out.println("아무렴");
        }
}

09. try-finally보다는 try-with-resources를 사용하라

// 9-1 try-finally 더 이상 자원을 회수하는 최선의 방법이 아니다!
****static String firstLineOfFile(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        br.close();
    }
}
// 9-2 자원이 둘 이상이면 try-finally 방식은 너무 지저분하다!
static void copy(String src, String dst) throws IOException {
          InputStream in = new FileInputStream(src);
          try {
              OuputStream out = new FileOutputStream(dst);
              try {
                  byte[] buf = new byte[BUFFER_SIZE];
                  int n;
                  while ((n = in.read(buf)) >= 0) {
                      out.write(buf, 0, n);
                  }
              } finally {
                  out.close();
              }
          } finally {
              in.close();
          }
      }
// 9-3 try-with-resources 자원을 회수하는 최선책
static String firstLineOfFile(String path) throws IOException {
      try (BufferedReader br = new BufferedReader(
                        new FileReader(path))) {
          return br.readLine();
      }
  }
// 9-4 복수의 자원을 처리하는 try-with-resources 짧고 매혹적이다.
static void copy(String src, String dst) throws IOException {
          try (InputStream in = new FileInputStream(src); 
               OutputStream out = new FileOutputStream(dst)) {
              byte[] buf = new byte[BUFFER_SIZE];
              int n;
              while ((n = in.read(buf)) >= 0)
                  out.write(buf, 0, n);
          }
      }
// 9-5 try-with-resources를 catch절과 함께 쓰는 모습
static String firstLineOfFile(String path, String defaultVal) {
    try (BufferedReader br = new BufferedReader(
                        new FileReader(path))) {
        return br.readLine();
    } catch (IOException e) {
        return defaultVal;
    }
}

Leave a Reply