effective java中文版 第二章 创建和销毁对象

第1条:考虑用静态工厂方法替代构造器
如下方法将boolean基本类型值转换为了一个Boolean对象引用

public static Boolean valueOf(boolean b){
  return b ?Boolean.TRUE:Boolean.FALSE;
}

静态工厂方法与构造器相比的第一大优势:它们会有名称
第二大优势(重点):不必在每次调用它们的时候都创建一个新对象。静态工厂方法能够为重复的调用返回相同的对象,这样有助于类总能严格控制在某个时刻哪些实例应该存在
第三大优势:它们可以返回原返回类型的任何子类型的对象
第四大优势在于:在创建参数化类型实例的时候,它们让代码变得更加简洁。例如HashMap提供了这个静态工厂

  public static <K,V> HashMap<K,V> newInstance(){
     return new HashMap<K,V>();
  }
    

如下为静态工厂方法的一些惯用名称
1,valueOf 它实际上是类型转换方法
2,getInstance 返回的实例通过方法的参数来描述
3,newInstance 确保返回的每个实例与其他实例不同

第2条 遇到多个构造器参数时要考虑用构建器
Bulilder模式:不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后调用无参的build方法来生成不可变的对象。这个builder是它构建的类的静态成员类。
下面为实例

public class Demo{
  private final int a;
  private final int b;
public static class Builder{
  private final int a;
  private final int b;
  
  public Builder(int a,int b){
     this.a = a;
     this.b = b;
}
public Demo bulid(){
  return new Demo(this);
}   
 }
 private Demo(Builder builder){
    a = builder.a;
    b = builder.b;
 }

}

如果类的构造器或者静态工厂中有多个参数,设计这种类时Builder模式是个不错的选择。

第3条 用枚举类强化Singleton属性

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

这种方法更加简洁,无偿提供了序列化机制,防止多次实例化。

第4条 通过私有构造器强化不可实例化的能力
在我们日常开发工具类时,不希望被实例化,这样的实例对它没有任何意义。然而,在缺少显示构造器的情况下,编译器会自动提供一个公有的,无参的缺省构造器。这种情况我们只要让这个类包含私有构造器,它就不能被实例化了。
public class UtilityClass{
private UtilityClass(){

}
}

第5条 避免创建不必要的对象
直接上例子。。。

public class Person {
  private final Date birthDate;
  
  public boolean isBabyBoomer(){
   //Unnecessary allocation of expensive object 
    Calendar a = Calendar.getInstance(TimeZone.get("GMT"));
    a.set(....);
    Date startDate = a.getTime();
    a.set(...);
    Date endDate = a.getTime();
    return birthDate.compareTo(startDate) >= 0 &&
    birthDate.compareTo(boomEnd) < 0;    
  }
}

这个实例每次被调用的时候都会新建一个Calendar,一个TimeZone和两个Date。这是不必要的。下面的版本用静态的初始化器提升了效率

class Person{
   private final Date birthDate;
    
   private static final Date START_DATE;
   private static final Date END_DATE;
   
   static{
      Calendar a = Calendar.getInstance(TimeZone.get("GMT"));
      a.set(...);
      START_DATE = a.getTime();
      a.set(...);
      END_DATE = a.getTime();
   }

  public boolean isBabyBoomer(){
    return birthDate.compareTo(START_DATE) >= 0 &&
    birthDate.compareTo(END_DATE) < 0; 
  }
}

改进后的代码只在初始化的时候创建这些类的时候实例化一次,而不是每次调用isBabyBoomer的时候都创建这些实例。如果方法被频繁的调用,将会显著地提高性能。

java1.5发行版本中,有一种创建多余对象的新方法,称作自动装箱,它允许程序员将基本类型和装箱基本类型混用,按需要自动装箱和拆箱。
上例子,哈哈哈

public static void main(String[] args){
  Long sum = 0L;
  for(long i = 0;i < Integer.MAX_VALUE;i++){
     sum += i;
  }
}

这段程序算出的答案是正确的,但是效率低。就因为变量sum被声明为Long而不是long.
结论:要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。

第6条 消除过期对象的引用 (阿西吧。。这章没怎么看懂)
先上一个简单的栈实现的例子

public class Stack{
   private object[] elements;
   private ine 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 void pop(){
    if(size == 0){
       throw new EmptyStackException();
    }
    return elements[--size];
  }

  public void ensureCapacity(){
    if(elements.length == size){
       elements = Arrays.copyOf(elements,2*size + 1);
    }
  }
}

这段程序哪里发生了内存泄漏呢?如果一个栈先是增长然后再收缩,那么栈中弹出来的对象将不会被当作垃圾回收,即使程序不再使用这些对象。because栈内部维护着对这些对象的过期引用。如果一个对象引用被无意识的保留起来了,垃圾回收机制不仅不会回收这个对象,也不会处理被这个对象的所有其他对象。
修复方法:一旦对象引用过期,清空这些引用即可。
一般而言,只要类是自己管理内存, 程序员就该警惕内存泄漏问题。
。。。。。。(这章不好理解啊。。。。。先略。。。)

第7条 避免使用终结方法(阿西吧。。。只是理解了一部分。。。心塞)
终结方法的一个缺点在于不能保证会被及时的执行。
及时的执行终结方法正是垃圾回收算法的一个主要功能。
结论:不应该依赖终结方法来更新重要的持久状态。
使用终结方法有一个非常严重的性能损失。
如果类的对象中封装的资源确实需要终止,这个时候只需要提供一个显示的终结方法。并要求该类的客户端在每个实例不再有用的时候调用这个方法。其中,该实例必须记录下自己是否已经被终止。

猜你喜欢

转载自blog.csdn.net/lc138544/article/details/82750465