BottleH Blog

이펙티브자바 3판 2장 정리

2장 객체생성과 파괴목차아이템1. 생성자 대신 정적 팩터리 메서드를 고려하라아이템2. 매개변수가 많다면 빌더를 고려하라아이템3. private 생성자나 열거 타입으로 싱글턴임을 보증하라아이템4. 인스턴스화를 막으려거든 private 생성자를 사용하라아이템5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라아이템6. 불필요한 객체 생성을 피하라아이템7. 다 쓴 객체 참조를 해제하라아이템8. finalizer와 cleaner 사용을 피하라아이템9. try finally보다는 try with resources를 사용하라

    Tags

  • Java
이펙티브자바 3판 2장 정리 thumbnail

2장 객체생성과 파괴

목차


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


1-1. 정적 팩터리 메서드의 장점

  • 이름을 가질 수 있다.
  • 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.
  • 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
  • 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
  • 정적 팩터리 매서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

1-2. 정적 팩터리 메서드의 단점

  • 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.
  • 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.
    • from : 매개변수 하나 받아서 인스턴스화 ex. Date.from()
    • of : 여러 매개변수를 받아서 인스턴스화 ex. Enumset.of()
    • valueOf : from과 of의 자세한 버전 ex. BigInteger.valueOf()
    • getInstance : 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않는다.
    • create, newInstance : 매번 새로운 인스턴스를 생성해 반환한다.

결론: 정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 이해하고 사용하며, 무작정 생성자를 제공하는 습관을 고칠 것.

아이템2. 매개변수가 많다면 빌더를 고려하라


2-1. 점층적 생성자 패턴

NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);

매개변수가 많아질 수록 많은 생성자를 작성해야하고 복잡해진다.

2-2. 자바빈즈 패턴

NutritionFacts cocaCola = new NutritionFacts(); cocaCola.setServingSize(240);

클래스를 불변으로 만들 수 없다.

2-3. 빌더 패턴

NutritionFacts salmon = new NutritionFacts.Builder() .calories(123) .sodium(2) .carbohydrate(5555) .build();

점층적 생성자와 자바빈즈 패턴의 장점만 모아둔 가장 좋은 방법임. 필요한 객체를 직접 만드는 대신 Builder 객체를 얻은 후 setter 들을 호출한 뒤 build()를 통해 필요한 객체를 얻는다.

결론: 생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 게 더 낫다.

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


싱글턴(singleton)이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다.

3-1. public static final 필드 방식의 싱글턴

public static final Hwan INSTANCE = new Hwan();

static 영역이 로딩될 때 딱 1번만 호출된다.

3-2. 정적 팩터리 방식의 싱글턴

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

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

public enum Hwan { INSTANCE; public void leaveTheBuilding(){...} }

간결하고, 추가 노력없이 직렬화가 가능하다. 하지만 싱글턴이 Enum 외의 클래스를 상속해야 한다면 열거 타입방식은 사용할 수 없다.

아이템4. 인스턴스화를 막으려거든 private 생성자를 사용하라


public class UtilityClass { // 기본 생성자가 만들어지는 것을 막는다(인스턴스화 방지용) / private UtilityClass() { throw new AssertionError(); } ... // 나머지 코드는 생략 }

private 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다.

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


많은 클래스가 하나 이상의 자원(bean)에 의존한다. 이럴 때 정적 유틸성 클래스나 싱글턴을 사용하게 되면 유연하지 않고 테스트하기 어렵다.

5-1. 의존 객체 주입방식

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

사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글톤 방식이 적합하지 않다! 이럴 때는 인스턴스를 생성할 때 생성자, static factory method, builder로 필요한 자원을 넘겨주는 방식이 좋다. 이것을 의존객체 주입 패턴 이라 한다.

결론: 클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다.

아이템6. 불필요한 객체 생성을 피하라


예를들어, new String("hello"); 보단 "hello"; 가 좋다. 전자는 새로운 인스턴스가 만들어져 heap영역에 올라가고, 후자의 문자열리터럴은 상수풀에 올라가기 때문이다.

6-1. Pattern과 같은 값비싼 객체는 재사용하라

6-2. 오토박싱

오토박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐려주지만, 완전히 없애주는 것은 아니다.

Long sum = 0l; for (long i = 0 ; i <= Integer.MAX_VALUE ; i++) { sum += i; }

이 코드는 매우 느리며, 쓸데없이 Long 객체를 2의 31제곱개나 만든다. 단순히 기본타입 long으로 바꾸기만해도 매우 개선된다. 따라서 박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자.

아이템7. 다 쓴 객체 참조를 해제하라


java는 GC가 자동으로 다 쓴 객체를 회수해주긴 하지만, 몇몇 경우에 개발자가 직접 메모리를 관리함으로써 GC가 회수를 하지않아 문제가 발생한다.

7-1. 직접 메모리 관리

public class Stack { private Object[] elements; public Object pop() { if (size == 0) throw new EmptyStackException(); return elements[--size]; // 메모리 누수 원인 } }

Size만 줄이고, 값은 그대로 보존하여 메모리 누수 발생

// 해결코드 Object result = elements[--size]; elements[size] = null; return result;

7-2. 캐시

직접 구현한 캐시 역시 메모리 누수를 일으키는 주범임.

해결방법: WeakHashMap, LinkedHashMap.removeEldestEntry, 백그라운드 쓰레드를 돌리며 캐시 해제 등

  • Weak Reference
    • Strong Reference : Integer value = 1; GC대상이 아니다.
    • Soft Reference : SoftReference<Integer> key = new SoftReference<Integer>(value); value 가 null 이 되어 참조되지 않을때 GC대상이 된다. 그러나 Weak Reference와 다르게 메모리가 부족하지않으면 굳이 GC하지 않는다.
    • Weak Reference : WeakReference<Integer> key = new WeakReference<Integer>(value); value 가 null 이 되어 참조되지 않을때 GC대상이 된다. 무조건 다음 GC 때 사라진다.

7-3. 콜백

해지하지 않는다면 계속 쌓인다.

해결방법: weak reference로 저장

아이템8. finalizer와 cleaner 사용을 피하라


Finalizer는 예측불가능하고 위험하며, 대부분 불필요하다. 성능도 안좋아진다. cleaner 또한 finalizer보다 덜 위험하지만 여전히 여전히 예측할 수 없고, 느리고, 일반적으로 불필요하다.

아이템9. try finally보다는 try with resources를 사용하라


전통적으로 자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰였다. 그러나 2개 이상의 자원을 사용할 때 이것은 복잡해진다.

InputStream in = new FileInputStream(src); try { OutputStream out = new FileOutputStream(dst); try { ... } finally { out.close(); } } finally { in.close(); }

try-with-resources는 AutoCloseable를 구현하고 자동으로 닫아준다. 자원이 여러개여도 가능하다.

try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst)) { ... } catch (..) {}
Written by@BottleH
Back-End Developer

GitHub