《Effetive Java》和 《Effective C++》、《More Effective C++》保持了一致的
写作风格,使用条款的方式对使用该语言的应该注意和极易出现问题的地方,提出了错误的做法,并讲述了应该怎么做,java之父James Gosling都需要的书,我们更应该看看,看了一遍收获颇多,简单记录下来一些要点。
第二章 创建和销毁对象
条款一: 考虑用静态工厂方法来替代构造函数
优点:1、静态工厂方法有更具有描述力的名字,对于有多个构造方法的类来说,使得程序更清晰。
2、对于创建不变对象,约束实例创建如単例,不必要每次都创建一个对象,提高性能。
3、不像构造子,它还可以创建任何子类的对象。
java.util.Collections.通过私有的Collection的静态实现类不暴露Collection的实现类而
创建子类的对象,返回的类型是Collection接口类型。不仅可以减少API,而且可以减少
Conceptual weight
java.util.EnumSet根据enum type的大小来创建两种实例:RegularEnumSet,JumboEnumSet。
Service provider frameworks
4、当创建具有类型参数的实例时,可以减少类型参数的使用(泛型)
Map<String, List<String>> m = new HashMap<String, List<String>>();
需要写两次参数,加上工厂方法后:
public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}
只需要一次:
Map<String, List<String>> m = HashMap.newInstance();
缺点: 1、如果没有public或者protected构造函数就不能子类化。
2、工厂方法不是标准的创建实例的方法,有时很难和其他方法区分:
命名约定:valueOf of getInstance newInstance getType newType
条款二:当构造函数有很多参数时,考虑使用builder.
Telescoping constructor pattern:
Hard to write client code, and harder to read.
JavaBean pattern:
可能导致不一致的状态,例如没有必选的参数要set的约束。
Builder pattern:
// Builder Pattern
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;
private int carbohydrate = 0;
private int sodium = 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;
}
public Builder sodium(int val){
sodium = val;
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;
}
}
Class的newInstance破坏了编译的异常检查,错误越早发现越好,特别是在编译的时候发现最 好。
条款三、通过私有化构造子和使用枚举类型来实现単例
方法一、直接公开一个静态属性:public static final Elvis INSTANCE = new Elvis();
方法二、使用工厂方法
方法三、使用枚举方法
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}
前两种方法,用户可以通过反射来调用私有构造子创建新的实例:可以在私有构造子中抛出异常来解决。
如果实现了Serializable接口,需要把所有的成员声明为transit,并提供
// readResolve method to preserve singleton property
private Object readResolve() {
// Return the one true Elvis and let the garbage collector
// take care of the Elvis impersonator.
return INSTANCE;
}
使用枚举方法可以避免上述两种问题,所以是最佳的方式。
単例的缺点是难于测试,很难去mock,不可子类化。
条款四、通过私有化构造子来实现不可实例化
Utility类(工具类)不应该去实例化的,因为实例化毫无意义,里面只是一堆静态的方法。但是编译器会
提供一个默认的构造子。我们想让它不可实例化,有几种方法:
方法一、把类写成抽象的。这种方法的缺点是容易混淆设计的意图,认为是留给别人实现用的。
方法二、私有化构造子,并在构造子中抛出访问异常。
// Noninstantiable utility class
public class UtilityClass {
// Suppress default constructor for noninstantiability
private UtilityClass() {
throw new AssertionError();
}
... // Remainder omitted
}
条款五、避免不必要的对象创建
1、避免使用不变类的构造函数,而使用提供的工厂方法:
String s = new String("stringette");
使用String s = "stringette";
使用工厂方法Boolean.valueOf(String)而不是Boolean的构造函数Boolean(String)
2、把可变的对象,但在你的应用中不变的对象的创建作为常量放在静态块中。
3、使用懒初始化的方法,在必要的时候做初始化。例如単例中的lazy方法。
4、autoboxing(自动装箱)容易导致不必要的实例创建:
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
代码中由于使用了Long,导致了2^31个Long对象的不必要的创建。所以优先使用primitives而不是
boxed primitives。
5、只对重量级别的对象做Object pool,不要对轻量级的对象做Object pool,因为jvm创建和回收一个轻
量级的对象性能非常的好。
条款六、清除已经没用的对象引用
Stack的pop方法的实现:
public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}
这个方法的实现导致了当pop出来的对象,没有被垃圾回收站回收,从而导致内存泄露。具有垃圾回收功能的
语言,无意的对象驻留会导致内存泄露,即使是很少的这种无意的对象驻留也可能影响大量对象的回收,因为
这些对象组成了一个图,他们之间相互引用。
上面代码的正确实现:
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // Eliminate obsolete reference
return result;
}
最好的清除没用对象引用的方法是让对象定义在它真正的作用范围内,也就是把变量定义在最窄的可能范围内。
另一种内存泄露来源与Cache。使用WeakHashMap可以保证当外部的key的引用超过生命周期的时候,可
以自动把WeakHashMap对应的Entry清除。一个好的生命周期控制的cache,应该有一个后台的线程定期
清除不活跃的元素。LinkedHashMap具有removeEldestEntry可以给予帮助。
第三种内存泄露来源于监听器和其他的回调。
注册监听器或回调如果没有deregister,他们就会不断的积聚。最好的方式是只保存他们的Weak
references.例如在WeakHashMap只保存他们的Keys
条款7: 避免使用finalizers
finalizers是不可预测的,往往会很危险,一般没有必要使用。
finalizers的
第一个缺点是不能保证立即回收。
第二个缺点finalizer不同的jvm实现的算法不同,依赖与finlizer的程序具有不可以移植性。
第三个缺点是使用finalizer会有严重的性能影响
不要使用System.gc and System.runFinalization
一般提供一个显示的中止函数,例如InputStream, OutputStream, and java.sql.Connection都有
close方法。java.util.Timer的cancel
显示的中止函数往往和try finally一起使用来回收资源。
使用finalizers的两种情况:
一、可以在用户忘记调用显式的中止函数时,作为安全网发挥点作用,因为晚点释放资源要比不释放的
好。这时候要log一下警告发现资源没有被中止,以便发现bug的存在。
二、在native peers中使用来终止不关键的资源,因为native的东西JVM并不知道它,也不会负责回收
它。如果native方法持有关键的资源,java对应端应该有显式的中止函数。
使用finalizers要显式的调用父类的finalizers
// Manual finalizer chaining
@Override protected void finalize() throws Throwable {
try {
... // Finalize subclass state
} finally {
super.finalize();
}
}
如果子类覆写的实现忘了被调用,那么finalizer将永远不会被调用。一个解决方法是用内部类创建一
个成员对象finalizerGuardian
// Finalizer Guardian idiom
public class Foo {
// Sole purpose of this object is to finalize outer Foo object
private final Object finalizerGuardian = new Object() {
@Override protected void finalize() throws Throwable {
... // Finalize outer Foo object
}
};
... // Remainder omitted
}