본문 바로가기

프로그래밍

[이펙티브 자바] 챕터 2

CHAPTER 2.

Item 1. 팩토리 메써드

좋은 점

  • 작명이 자유로워서 리턴되는 객체가 무엇인지 명시적으로 나타낼 수 있다.
  • subclass 타입을 반환할 수 있다.

    EnumSet.of(T t) 의 경우 t의 크기에 따라서 JumboEnumSet 혹은 RegularEnumSet을 반환한다.

  • 매번 생성하지 않아도 된다.
  • generic을 사용할 때 팩토리 메써드를 사용하면, 타이핑을 줄일 수 있다. 1

단점

  1. 생성자를 private으로 처리하면 subclass를 못만듬.
  2. 문서에서 다른 메써드들과 구분할 수 있는 방법이 없음. 2

Service Provider FrameWork

service 생성함수
등록
구현
구현
생성
Service의 기능을 사용하기 위해서 Service 구현 객체를 반환하는 provider의 구현 객체를 등록한다.
provider
Service
ServiceAccessAndRegister
providerClass
ServiceClass

Item 2. 생성자 파라미터가 많을 때는 빌더 패턴

텔레스코핑 패턴

    public Class NutritionFacts{
        private final int servingSize;
        private final int servings;
        private final int calories;
        private final int fat;

        public NutritionFacts(int servingSize, int servings){
            this(servingSize, servings, 0);
        }
        public NutritionFacts(int servingSize, int servings, int calories){
            this(servingSize, servings, calories, 0);
        }
        public NutrtionFacts(int servingSize, int servings, int calories, int fat){
            this.servingSize = servingSize;
            this.servings = servings;
            this.calories = calories;
            this.fat = fat;
        }
    }

이렇게 파라미터 두개짜리 생성자 함수가 세개짜리 생성자 함수를 호출하고 세개짜리가 4개짜리 생성자 함수를 호출하는 식의 패턴을 텔레스코핑 패턴이라고 한다. 읽는 사람 화나게 할 수 있음. ㅎㅎ 특히, 위의 보기에서 calories를 입력할 필요가 없는데, fat을 입력하려면 calories도 무조건 입력해야 한다는 단점이 있음.

자바빈스 패턴

    NutrtionFacts cocacola = new NutritionFacts();
    cocacola.setServingSize(240);
    cocacola.setServings(240);
    cocacola.setCalories(240);
    cocacola.setFat(240);

파라미터 없는 생성자 함수를 통해 객체 instance를 생성 후 일일이 값을 세팅하는 방식. 텔레스코핑 패턴에서 오는 단점은 없으나, thread safe 하지 않다.

빌더 패턴

public class NutritionFacts {

    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;

    public static class Builder {
        private final int servingSize;
        private final int servings;

        private int calories = 0;
        private int fat = 0;

        public Builder(int servingSize, int servings) {
            this.servings = servings;
            this.servingSize = servingSize;
        }

        public Builder calories(int val) {
            this.calories = val;
            return this;
        }

        public Builder fat(int val) {
            this.fat = val;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    public NutritionFacts(Builder builder) {
        this.servingSize = builder.servingSize;
        this.servings = builder.servings;
        this.calories = builder.calories;
        this.fat = builder.fat;
    }

    public static void main(String[] args){
        NutritionFacts coke = new NutritionFacts.Builder(10, 10).calories(0).build();
    }
}

builder 패턴을 쓰면 코드도 읽기 좋고, 자바빈스 패턴보다 쓰레드 safe함. 또한, 유연해서 generic하고 같이 쓰면 하나의 빌더 클래스를 통해 여러 클래스의 instance를 생성할 수 있다.단점은 코드가 길어진다는 점하고 정말 performance critical한 상황에서 builder에 들어가는 cost가 아까울 수 있다.


Item3. Singleton 패턴

전통적인 싱글턴 패턴

    //필드 변수를 통해 싱글턴 객체에 접근하는 패턴
    public class Elvis{
        public static final Elvis INSTANCE = new Elvis();
        private Elvis(){
            System.out.println("I am Elvis");
        }
    }

    //메써드를 통해 싱글턴 객체에 접근하는 패턴
    public class Elvis2{
        private static final Elvis2 INSTANCE = new Elvis2();
        private Elvis2(){
            System.out.println("I am Elvis2");
        }
        public static Elvis2 getInstance(){
            return INSTANCE;
        }
    }

둘 다 각자 장점이 있는데, public field의 경우 singleton의 의도가 명확하게 드러나고, 메서드 접근의 경우 design 변경이 용이하다. 하지만, 둘 다 reflection과 synchronization에 의해서 singleton이 깨질 염려가 있다.

enum을 사용한 싱글턴 패턴

public static enum Elvis3{
        INSTANCE;

        private Elvis3(){
            System.out.println("I am Elvis3");
        }

        public void sing(){
            System.out.println("la la la");
        }

        // "I am Elvis3", "la la la" , "la la la" 이렇게 찍힌다. 한번만 생성되는 증거.
        public static void main(String[] args){
            Elvis3.INSTANCE.sing();
            Elvis3.INSTANCE.sing();
        }
    }

Item4. private 생성자를 통해 생성을 제한 가능.

static한 메서드들만 모아놓은 클래스의 경우 생성자를 private으로 만들면, 생성을 억제할 수 있다.
이런 설계로 만들어진 클래스의 경우 inheritance가 불가능하다.


Item5. 불필요한 instance 생성 자제

  • 무겁고 자주 변경되지 않는 객체의 경우 메서드 호출할 때마다 local variable로 생성하지 말고 static 변수로 올려서 메써드에서 참조하도록 함.
  • autoboxing의 경우, 성능이 저하되므로 되도록 이면 primitive type으로 처리.
  • 그렇다고 객체 생성 자체를 지양해서는 안됨. 특히나 object pool 관리의 경우, 왠만하게 무거운 객체가 아니면, 안하는 것보다 성능이 떨어짐.

Item6. 낡은 참조를 없애자.

  • stack을 예로 들어보자. 2번까지 객체가 쌓인 stack에서 pop할 때, 해당 2번 index가 가리키는 객체를 반환과 동시에 index를 1번으로 옮기게 되면, 2번 index가 여전히 같은 객체를 가리키고 있으므로 해당 stack이 소멸되기 전까지 2번 index가 가리키는 객체는 살아있다.
  • 수동적으로 null로 바꿔야 하는 케이스가 예외적인 상황
  • 메모리를 본인이 컨트롤해야 하는 클래스를 작성할 때는 이런 null 처리를 해줘야 메모리 leak을 막을 수 있다.
  • 메모리 leak이 발생할 수 있는 다른 영역은 cache와 listeners 이다. 해결 방법으로는 weak reference를 사용하는 것이다.3

Item 7. finalizer에서 뭔가 하지마라.

  • finalizer는 언제 일어날지 모르므로 절차를 따르는 코드를 안에 적지마라.
  • finalize chaining은 자동적으로 일어나지 않음.
  • finalize override 해놓고 super.finalize()를 하지 않으면 부모 클래스의 finalize는 영영 호출이 안됨.
  • finalize guardian을 통해 이런 실수를 막을 수 있음.
// 이렇게 finalize guardian을 사용하면 Foo를 상속받는 클래스에서 굳이 super.finalize();를 하지 않아도 된다.
public class Foo {
    private final Object finalizerGuardian = new Object(){
        @Override protected void finalize() throws Throwable {
            Foo.this.finalize();
        }
    };
}

  1. 1.7부터는 다이아몬드 문법을 제공해서 생성자 함수도 말을 줄일 수 있다.

  2. 주로 다음과 같은 말로 시작한다. valueOf, of, getInstance, newInstance, getType, newType

  3. weak reference