EffectiveJava 2장 - 객체 작성 및 제거

11623 단어

제1조: 구조기 대신 정적 공장 방법을 고려하다


클래스 실례를 얻는 두 가지 방법: 공유의 구조기, 공유의 정적 공장 방법은 클래스의 실례를 되돌려줍니다.
정적 공장 방법의 장점: 1.그것들은 하나의 종류가 같은 서명을 가진 여러 개의 구조기를 필요로 할 때 정적 공장 방법으로 구조기를 대체하고 그들 사이의 차이를 돋보이게 하기 위해 신중하게 이름을 선택한다.
2. 이들을 호출할 때마다 새 대상을 만들지 않아도 불필요한 중복 대상을 만들 수 있다.이런 방법은 향원 모드와 유사하며 프로그램이 자주 같은 대상을 만들기를 요청하면 정적 공장 방법을 사용할 수 있다.단례를 만들 때도 정적 공장 방법을 사용한다.
3. 원래 되돌아오는 유형의 하위 유형의 대상을 되돌릴 수 있다. 이것은 인터페이스 기반 프레임워크에 비교적 적합하다.API는 각 시드 객체를 반환하는 동시에 객체의 클래스를 공유로 만들지 않습니다.이런 API는 매우 깔끔하게 변했다.예를 들면java.util.Collections.클라이언트는 공장 방법에서 얻은 대상의 종류에 대해 영원히 모르고 관심을 가지지 않을 뿐만 아니라 후속 버전에서 변화가 발생하더라도 클라이언트에게 영향을 주지 않을 것이다.
4. 매개 변수화 유형의 실례를 만들 때 코드를 더욱 간결하게 만든다
// , 
Map> m = new HashMap>();

// , 
public static  HashMap newInstance(){
      return new HashMap();
}

Map> m = HashMap.newInstance();

정적 공장 방법의 단점: 1.클래스가 공유되거나 보호된 구조기를 포함하지 않으면 이불을 분류할 수 없다.이불로 분류할 수 없으면 계승할 수 없다.
2. 정적 공장 방법은 다른 정적 방법과 사실상 아무런 차이가 없다.

제2조: 여러 개의 구조기 파라미터를 만났을 때 구조기를 사용하는 것을 고려해야 한다


파라미터가 비교적 많을 때 일반적인 구조기(중첩구조기)는 먼저 필요한 파라미터만 있는 구조기를 제공하고 두 번째 구조기는 선택할 수 있는 파라미터가 하나 있으며 세 번째는 선택할 수 있는 파라미터가 두 개 있다. 이런 식으로 추론하면...중첩구조기 모델은 가능하지만 많은 파라미터가 있을 때 클라이언트 코드는 쓰기 어렵고 읽기 어렵다.
// 
public class NutritionFacts {
   private final int servingSize;  //(ml)
   private final int servings;     //(per container)
   private final int calories;     //
   private final int fat;          //(g)
   private final int sodium;       //(mg)
   private final int carbohydrate; //(g)

   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;
   }
}

두 번째 방법은 JavaBeans 모드입니다.무참한 구조 함수만 제공합니다.그리고 setter 방법을 사용해서 파라미터를 설정합니다.구조 과정에서 자바빈은 일치하지 않는 상태에 있을 수 있으며 클래스는 파라미터의 유효성을 검사함으로써 일치성을 확보할 수 없다.
public class NutritionFacts {
    private  int servingSize=-1;  //(ml)
    private  int servings=-1;     //(per container)
    private  int calories=0;     //
    private  int fat=0;          //(g)
    private  int sodium=0;       //(mg)
    private  int carbohydrate=0; //(g)

    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;}
}

세 번째 방법은 Builder 모드입니다.builder는 구조기처럼 매개 변수에 제약 조건을 강요할 수 있습니다.build 방법은 이 제약 조건을 검사할 수 있습니다.사실 Builder는 자바빈입니다. 매개 변수의 유효성을 검사하기 위해build 방법을 추가했을 뿐입니다.
public class NutritionFacts {
    private  int servingSize=-1;  //(ml)
    private  int servings=-1;     //(per container)
    private  int calories=0;     //
    private  int fat=0;          //(g)
    private  int sodium=0;       //(mg)
    private  int carbohydrate=0; //(g)

    public static  class Builder{
        //Required parameters
        private final int servingSize;
        private final int servings;
        //Optional parameters - initialized to default values
        private int calories        =0;
        private int fat             =0;
        private int carbohydrate    =0;
        private int sodium          =0;

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

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

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

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

        public Builder sodium(int sodium) {
            this.sodium = sodium;
            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;
    }

}

제3조: 개인 구조기나 매거 유형으로singleton 속성 강화


1. 아귀식(클래스 로드 시 인스턴스 생성)
//final 
public class Elvis01 {  
    public static final Elvis01 INSTANCE = new Elvis01();  
    private Elvis01(){  // 
        if(INSTANCE != NULL){
            throw new SomeException();
        }
        ......
    }  
    public void leaveTheBuilding(){  
        System.out.println("Elvis01 leaveTheBuilding");  
    }  
} 

클라이언트는 반사 메커니즘을 통해 개인 구조기를 호출하여 구조기를 접근할 수 있는 것으로 수정하여 다른 실례를 만듭니다.그래서 두 번째 실례를 만들 때 이상을 던질 수 있다.
2.아사자 변종(정태적 방법 제공 실례 획득)
// 
public class Elvis02 implements Serializable{  
    private static final Elvis02 INSTANCE = new Elvis02();  
    private Elvis02(){  
  
    }  
    public void leaveTheBuilding(){  
        System.out.println("Elvis02 leaveTheBuilding");  
    }  
    public static Elvis02 getInstance(){  
        return INSTANCE;  
    }  
}

3. 게으름뱅이
public class Elvis04 implements Serializable{  
    private Elvis04 instance;  
  
    private Elvis04(){  
  
    }  
    public Elvis04 getInstance(){  
        if(instance == null){  
            instance = new Elvis04();  
        }  
        return instance;  
    }  
} 

이것은 노정이 안전하지 않다.
상술한 세 가지 방식은 서열화할 때 모두 추가적인 작업을 한다. 그렇지 않으면 하나의 예를 보장할 수 없다. 왜냐하면 매번 서열화된 실례를 반서열화할 때마다 새로운 실례를 만들 수 있기 때문이다.모든 실례역이 순간적(transient)이라는 것을 설명하고readResolve 방법을 제공해야 합니다.
private Object readResolve(){
    return INSTANCE;
}

4. 열거 예제
public enum  Elvis03 implements Serializable{  
    INSTANCE;  
    public void leaveTheBuilding(){  
        System.out.println("Elvis03 leaveTheBuilding");  
    }  
}  

5. 정적 내부류는 게으름을 피우고 라인이 안전합니다.클래스 로딩 메커니즘을 이용하여 라인 안전을 실현했다. 클라이언트 코드가 getInstance를 호출했을 때 JVM은 SingletonHolder를 로딩하고 정적 구성원을 초기화하여 instance를 실례화했다.
public class Elvis05 implements Serializable{  
    private Elvis05(){  
  
    }  
    public Elvis05 getInstance(){  
        return SingletonHolder.instance;  
    }  
    private static class SingletonHolder{  
        private static Elvis05 instance = new Elvis05();  
    }  
    public void leaveTheBuilding(){  
        System.out.println("Elvis05 leaveTheBuilding");  
    }  
} 

제4조: 사유구조기를 통해 실례화할 수 없는 능력을 강화


일부 클래스는 실례화되기를 원하지 않는다. 예를 들어 일부 도구 클래스:java.util.Collections. 현식 구조기가 부족한 경우, 컴파일러는 자동으로 공유되지 않은 무참여 구조기를 제공할 것이다.이런 종류는 여전히 실례화될 수 있다.쓰기 클래스는 실례화될 수 없습니다. 이 클래스가 하나의 개인 구조기 (private) 만 제공하면 외부에서 실례화될 수 없습니다.이런 방법으로 인해 이 종류는 이불류화할 수 없다. 왜냐하면 모든 구조기는 현식 또는 은식으로 초류 구조기를 호출하기 때문이다. 이런 상황에서 하위 클래스는 접근할 수 있는 초류 구조기가 호출할 수 없기 때문이다.

제5조: 불필요한 대상을 만드는 것을 피한다


필요할 때마다 같은 기능을 가진 새로운 대상을 만드는 것이 아니라 대상을 다시 사용하는 것이 좋다.만약 대상이 변할 수 없다면, 그것은 시종 다시 사용될 수 있다.
String s = new String("stringette")// 
String s = “stringette” // 

변하지 않는 대상을 중용하는 것 외에도 수정되지 않은 가변 대상을 중용할 수 있다.
// 
public static void main(String[] args){
   Long sum = 0L;
   for(long i = 0 ; i < Integer.MAX_VALUE; i++){
       sum += i;
   }
   System.out.println(sum);
}


변수 sum은 Long으로 선언되고 sum + = i를 실행할 때마다 자동으로 포장되며 불필요한 Long 객체가 많이 만들어집니다.컨테이너 기본 유형이 아닌 기본 유형을 우선적으로 사용해야 하며, 무의식적으로 자동 컨테이너를 조심해야 한다.

제6조: 기한이 지난 대상 인용 제거


자바에는 쓰레기 회수 메커니즘이 있지만 수동으로 메모리를 관리하지 않아도 된다는 뜻은 아니다.클래스가 스스로 메모리를 관리한다면 메모리 유출을 경계해야 한다.다음은 메모리 유출의 흔한 세 가지 상황입니다.
// , 
public Object pop(){
    if(size == 0){
           throw new EmptyStackException();
     }
     return elements[--size];
}

이 프로그램에는 메모리 유출이 존재한다.만약 창고가 먼저 증가한 다음에 수축된다면.그러면 창고에서 튀어나온 대상은 쓰레기 수거로 취급되지 않는다.창고 내부에서 이 대상에 대한 '만료 인용' 을 유지하고 있기 때문이다.
public Object pop(){
   if(size == 0){
           throw new EmptyStackException();
     }
     Object result = elements[--size];
     elements[size] = null;
     return result;
}

메모리 유출의 또 다른 흔한 출처는 캐시다.일단 대상을 캐시에 인용하면, 그는 쉽게 잊혀져서, 더 이상 쓸모없게 오랫동안 캐시에 남게 된다.WeakHashMap을 사용하여 외부에서 참조되지 않은 키 값을 자동으로 지울 수 있습니다.(캐시 자체는 데이터의 읽기 속도를 높이기 위해 캐시에서 데이터를 읽지 못하면 메모리에서 찾는다. 프로그램의 정확성에 영향을 주지 않기 때문에 WeakHashMap은 데이터의 읽기 속도를 높일 수 있고 메모리 유출도 피할 수 있다. 약한 인용과 관련된 대상은 다음 쓰레기 수집이 발생하기 전까지만 생존할 수 있기 때문이다.)LinkedHashMap은 LRU 캐시를 구축합니다.
세 번째 흔히 볼 수 있는 출처는 감청기와 다른 리셋이다.클라이언트가 API를 통해 콜백을 등록했지만 명시적으로 등록을 취소하지 않아 대상이 축적되었다.가장 좋은 방법은 리셋의 약한 인용만 저장하는 것이다.

제7조: 종결 방법의 사용을 피한다


종결 방법은 한 번만 호출된다.
대략적으로finalize 프로세스를 설명합니다: 대상이 (GC Roots)로 변할 때 GC는 대상이finalize 방법을 덮어썼는지 판단합니다. 덮어쓰지 않았거나finalize 방법이 실행되었을 경우 직접 회수합니다.그렇지 않으면, 대상이finalize 방법을 실행하지 않았다면 F-Queue 대기열에 넣고, 낮은 우선순위 라인에서 대상의finalize 방법을 실행합니다.finalize 방법을 실행한 후 GC는 이 대상이 도달할 수 있는지 다시 판단하고 도달할 수 없으면 회수한다. 그렇지 않으면 대상이 부활한다.(아래 코드를 볼 수 있다)
상술한 절차에 심각한 문제가 존재하는 것은fianlize 방법을 낮은 우선순위 라인에 맡겨 집행하는 것이다. 따라서 제때에 집행될 수 없고 심지어 집행될 수도 없다.
대체 방법은 현저한 종지 방법을 제공하는 것이다.예를 들어 InputStream, OutputStream의close 방법과 Timer의cancel 방법이다.현식의 종지 방법은try-finally 구조와 결합하여 사용하여 제때에 종지를 확보한다.이상 투매가 있어도finally 블록은 항상 실행되기 때문에 종료 방법은 반드시 실행될 것입니다.
대상 재생 문제:finalize 방법에서 회수 대상 값을 GC Roots가 도달할 수 있는 대상에 인용하여 대상 재생의 목적을 달성할 수 있습니다.
public class GC {  
 
   public static GC SAVE_HOOK = null;  
 
   public static void main(String[] args) throws InterruptedException {  
       SAVE_HOOK = new GC();  
       SAVE_HOOK = null;  
       System.gc();  
       Thread.sleep(500);  
       if (null != SAVE_HOOK) { // (reachable, finalized)   
           System.out.println("Yes , I am still alive");  
       } else {  
           System.out.println("No , I am dead");  
       }  
       SAVE_HOOK = null;  
       System.gc();  
       Thread.sleep(500);  
       if (null != SAVE_HOOK) {  
           System.out.println("Yes , I am still alive");  
       } else {  
           System.out.println("No , I am dead");  
       }  
   }  
 
   @Override  
   protected void finalize() throws Throwable {  
       super.finalize();  
       System.out.println("execute method finalize()");  
       SAVE_HOOK = this;  
   }  
}  

 
execute method finalize()
Yes , I am still alive
No , I am dead

종결 방법은 두 가지 합리적인 용도가 있다.종결 방법은 안전망을 충당할 수 있다. 만약에 대상의 소유자가 현식의 종결 방법을 사용하지 않았다면 종결 방법은 안전망을 충당할 수 있다. 자원을 늦게 방출하는 것이 자원을 방출하지 않는 것보다 낫다.동시에 로그를 출력하면 사용자가 현식의 종료 방법을 호출할 수 있음을 알립니다.
2. 비관건적인 로컬 자원을 중지한다.만약 관건적인 자원이라면, 반드시 중지 방법을 호출해야 한다.

좋은 웹페이지 즐겨찾기