博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Effective Java学习笔记

Posted on 2016-11-01 17:53  Mr.Victor  阅读(212)  评论(0编辑  收藏  举报

创建和销毁对象

第一条:考虑用静态工厂方法替代构造器

For example:

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

优势:

  • 有名称
  • 不必在每次调用它们的时候都创建一个新对象
  • 它们可以返回原返回类型的任何子类型的对象
  • 在创建参数化类型实例的时候,他们使代码变得更加简洁

缺点:

  • 类如果不含公有的或者受保护的构造器,就不能被子类化
  • 它们与其他的静态方法实际上没有任何区别

第二条:遇到多个构造器参数时要考虑用构造器

对于多个参数的构造器或者静态方法,一般习惯采用重叠构造器(telescoping constructor)模式

//Telescoping constructor pattern
public class PizzaIngredients{
    private final int salt; //optional
    private final int chess; //o
    private final int sausage; //o
    private final int flour; // required
    private final int water; //r
    ...
    
    public PizzaIngredients(int flour, int water){
        this(flour, water, 0);
    }
    public PizzaIngredients(int flour, int water, int salt){
        this(flour, water, salt, 0);
    }
    public PizzaIngredients(int flour, int water, int salt, int chess){
        this(flour, water, salt, chess, 0);
    }
    public PizzaIngredients(int flour, int water, int salt, int chess, int sausage){
        this.flour = flour;
        this.water= water;
        this.salt= salt;
        this.chess= chess;
        this.sausage= sausage;       
    }
}

如果构造参数太多这种方式代码也会变得很难编写,且不易阅读。

第二种方式是采用JavaBeans模式,即设置每个参数的默认值,并给每个field加上setter方法,要设置某个值时直接调用其对应的setter方法。但是JavaBean在构造过程中可能会处于不一致状态,并且JavaBeans模式阻止了把类做成不可变的可能,这就需要付出额外的努力来确保它的线程安全。

第三种替代方法是Builder模式。客户端利用必要的参数得到一个builder对象,在builder对象上调用类似setter的方法,来设置每个相关的可选参数,最后调用无参的build方法来生成不可变对象。

public class PizzaIngredients{
    private final int salt; //optional
    private final int chess; //o
    private final int sausage; //o
    private final int flour; // required
    private final int water; //r
    ...
    
    public static class Builder{
      // required parameters
      private final int flour;
      private final int water;
      // optional
      private int salt = 0;
      private int chess = 0;
      private int sausage = 0;
      
      public Builder(int flour, int water){
          this.flour = flour;
          this.water= water;
      }
      public Builder salt(int val){
          salt = val;
          return this;
      }
      public Builder chess(int val){
          chess = val;
          return this;
      }
      public Builder sausage (int val){
          sausage = val;
          return this;       
      }
      public PizzaIngredients build(){
         return new PizzaIngredients (this)
      }
  }
  
  private
PizzaIngredients(Builder builder){
    flour = builder.flour;
    water = builder.water;
    salt = builder.salt;
    chess = builder.chess;
    sausage = builder.sausage;
  }
}

// 客户端代码
PizzaIngredients pizza = new PizzaIngredients.Builder(200, 150).salt(5).chess(20).sausage(25).build();

Builder模式优势在于参数可变,而且不需要考虑顺序。但是Builder模式比重叠构造器模式更加冗长,最好在很对参数是使用。

第三条:用私有构造器或者枚举类型强化Singleton属性

 三种方式实现Singleton:

1. 公有静态成员是final域

//Singleton with public final field
public class Wills{
    public static final Wills INSTANCE = new Wills();
    private Wills(){
    };
    ...
}

2. 公有成员是静态工厂方法

//Singleton with static factory
public class Wills{
    private static final Wills INSTANCE = new Wills();
    private Wills(){
    };
    public static Wills getInstance(){
        return INSTANCE;
}
    ...
}

这两种方式在存在借助AccessibleObject.setAccessible方法,通过方式机制调用私有方法的可能。为抵御这种攻击,可以修改构造器,在实例被第二次创建时抛出异常。

并且每次反序列化一个序列化的实例时,都会创建一个新的实例,这需要声明所有实例域都是transient,并提供一个readResolve方法。

3. 单元素枚举类型

// Enum singleton - the preferred approach
public enum Wills{
    INSTANCE;
    ...
}

这种方法在功能上类似公有域方法,但它更加简洁而且无偿的提供了序列化机制,绝对防止多次实例化。单元素枚举类型已经成为实现Singleton的最佳方法。

第四条:通过私有构造器强化不可实例化的能力

不需要被实例化的类,可以通过私有构造器使其不能被实例化。

Public class UtilityClass {
    private UtilityClass(){
        throw new AssertionError();
    }
}

 

第五条:避免创建不必要的对象

除了不可变的对象,也可以重用已知不会修改的对象。
可以使用静态初始化器(initializer)来初始化代码块。

Public class Person{
    private final static String name;
    private final static String gender;

    static {
        Calendar cal = Calendar.getInstance(Timezone.getTimeZone("GMT"));
        ...
        ...
}

第六条:消除过期的对象引用

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 * DEFAULT_INITIAL_CAPACITY + 1);
        }
    }
}

这段代码中并没有明显的错误,但是这个程序中隐藏了一个问题,有一个“内存泄漏”,随着垃圾回回收器活动的增加,或者由于内存占用的不断增加,程序性能的降低会逐渐表现出来。

如果一个栈先是增长,然后再收缩,那么从栈中弹出来的对象将不会被当做垃圾回收,即使使用栈的程序不再引用这些对象,他们也不会被回收。这是因为,栈内部维护着对这些引用的过期引用(obsolete reference)。所谓过期引用,是指永远也不会再被解除的引用。修复这类问题的方法很简单,一旦对象引用已经过期,只需清空这些引用即可。对于本例,如下修改:

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

清空对象引用应该是一种例外,而不是一种规范行为。

一般而言,只要是自己管理内存,程序员就应该警惕内存泄漏问题。

内存泄漏的另一个常见来源是缓存。

内存泄漏的第三个常见来源是监听器和其他回调。