第二章_创建和销毁对象_effective java

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

静态构造方法相比如构造器的优势:

  1. 可以自主命名

  2. 不必每次调用的时候都创建一个新对象。(flyweight模式):

    1
    2
    3
    4
    5
    public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
    }
    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);
  3. 可以返回源类型的任何子类。比如EnumSet会按照元素个数分别返回RegalarEumnset和JumboenumSet。这些都是对用户透明的

  4. 创建参数化类型实例时使代码变得简介(1.8中jvm已经可以使用类型推断做这件事了)

    1
    2
    3
    4
    Map<String, List<String>> m = new HashMap<String, List<String>>
    vs
    public static <K,V> HashMap<K,V> newinstance() { new newHahMap<K,V>()};
    Map<String, List<String>> m = Hashmap.newInstance();

缺点:

  1. 如果类不含有共有的或者受保护的构造器,就不能被子例化
  2. 他们与其他的静态方法实质上没有区别

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

生成类需要使用多个参数并且每个参数都可能出现也可能不出现时通常有3类方法:

  1. 重叠构造器模式:写多个构造器,参数少的调用参数多的,不存在的参数传默认值。缺点,参数太多难以编写,并且参数顺序错误不好被发现

  2. javaBeans模式:无参构造器+setter。不是线程安全,参数无法设置成final类型

  3. builder模式:先生成一个Builder对象,在Builder上调用setter,然后客户端调用build方法生成对象。同时后期动态添加参数也很容易。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;
    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;
    public Builder(int servingSize, int servings) {
    this.servingSize = servingSize;
    this.servings = servings;
    }
    public Builder calories(int val) {
    calories = val;
    return this;
    }
    public Builder fat(int val) {
    fat = val;
    return this;
    }
    public Builder carbohydrate(int val) {
    carbohydrate = val;
    return this;
    }
    }
    private NutritionFacts(Builder builder) {
    servingSize = builder.servingSize;
    servings = builder.servings;
    calories = builder.calories;
    fat = builder.fat;
    }
    public static void main(String[] args) {
    NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
    .calories(100).build();
    }
    }

    缺点:为了创建对象先创建了构造器,一定程度上损失了性能

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

单例还是推荐用这种,不知道为啥书上没有写

1
2
3
4
5
6
7
8
9
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

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

有些工具类,比如java.lang.Math或者Java.util.Collections没有必要被实例化,这时如果:

  1. 不提供构造器:编译器会生成一个默认无参构造器,用户会在不经意间使用
  2. 做成抽象类:可被继承,而且会误导用户以为他就是要被继承而被设计的
  3. 将构造器声明为private:可以达到目的,因为外界无法调用构造器。缺点是该类不可以被子类化了。

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

最简单例子就是

1
2
3
String s = new String("string");
//vs
String s ="string";

如果在循环中的话1会生成多个string对象,而3只会生成一个。

map的keyset()函数也是类似的思想,看起来每次调用都是创建了一个新的set,但是其实每次返回的keyset都是同样的set实例(需要注意的是这样当一个keyset返回值变化时,所有的都会变化)。这里看一下源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
transient volatile Set<K> keySet;//transient:实现Serilizable接口时,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
public Set<K> keySet() {
Set<K> ks;
return (ks = keySet) == null ? (keySet = new KeySet()) : ks;
}
final class KeySet extends AbstractSet<K> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<K> iterator() { return new KeyIterator(); }
public final boolean contains(Object o) { return containsKey(o); }
public final boolean remove(Object key) {
return removeNode(hash(key), key, null, false, true) != null;
}
public final Spliterator<K> spliterator() {
return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}

另外在运算时,要优先使用基本类型而不是装箱基本类型,要当心无意识的使用自动装箱,比如long能干的事使用了Long。自动装箱简介

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

书里举的是一个stack的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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];
}
/**
* Ensure space for at least one more element, roughly doubling the capacity
* each time the array needs to grow.
*/
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}

泄漏的原因是elements里面大于size的元素都没有用,但是因为elements本身指向他,所以不会被删除。这种永远不会被解除的引用叫做过期引用

解决这个问题的芳芳很简单,就是pop的时候加上elements[size]=null解除elements和对象的引用关系即可。一般而言只要类是自己管理内存,程序就应该警惕内存泄漏问题

另外常见的内存泄漏会出现在缓存,监听器和其它回调中,这时最佳方法是使用弱引用,比如保存在weakHashMap中。

第七条:避免使用finalizer方法

finalizer方法不能保证一定被执行,并且有性能损失,所以不建议使用。通常使用try-finally是更好的选择


本文采用创作共用保留署名-非商业-禁止演绎4.0国际许可证,欢迎转载,但转载请注明来自http://thousandhu.github.io,并保持转载后文章内容的完整。本人保留所有版权相关权利。

本文链接:http://thousandhu.github.io/2016/10/01/第二章-创建和销毁对象-effective-java/