您的位置:首页 > Web前端

{Effective Java} Chap 2 创建和销毁对象

2015-09-13 02:39 423 查看


This chapter concerns creating and destroying objects: when and how to create them, when and how to avoid creating them, how to ensure they are destroyed in a timely
manner, and how to manage any cleanup actions that must precede their destruction.

第一条:consider static factory methods instead of constructors

Advantages:

1. 相比于构造器,他们有名称,方便区别不同的构造方式

如果构造器的参数无法说明意图或者多个构造器使用相同的参数,应该使用合理名称的static factory methods.

2. 不必每次调用时都创建一个新对象,不可变类可以使用预先构建好的实例。(flyweight模式,singleton模式)

Instance-controlled: 为重复的调用返回相同对象,可以考虑用 == 代替 equals提高性能。(例如 Enum)

3. 可以返回子类型对象,隐藏子类型的实现

interface-based framework : static factory methods return interfaces, and these methods are put in a noninstantiable class (e.g Collections)

4. 在创建参数化类型实例时,更加简洁,不用书写重复的参数声明。例如:

Map<String, String> m = HashMap.newInstance();


Disadvantages:

1. 类如果没有 public 或者 protected的构造器,就不能被子类化。

2. 他们与其他的静态方法没有区别。一些通过名称可以辨认:valueOf, of, getInstance, newInstance, getType, newType等

Summary:通常静态工厂方法更好。

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

对于有大量可选参数的类:

1. 考虑使用重叠构造器(telescoping constructor):

下一个构造器有更多的可选参数,最后一个包含所有可选参数。

2. JavaBeans模式:

调用无参构造器创建对象,调用setter方法来设置每个必要的参数。缺点是可能出现不一致,无法保证不可变性。

3. Builder模式:

不直接生成想要的对象,让客户端利用所有必要的参数调用构造器(或者静态工厂)得到一个builder对象(static class)

然后再调用setter设置参数

最后客户端调用build object 无参build方法,来生成不可变的对象。

public class NutritionFacts{
private final int servingSize;
private final int servings;
private final int fat;
private final int sodium;

public static class Builder{
private final int servingSize;
private final int servings;

private fat = 0;
private sodium = 0;

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

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

public static void main(String[] args)
{
NutritionFacts cocaCola = new NutritionFacts.Builder(240,8).fat(100).sodium(4).build();
}

}


Advantages:

1. 模拟了具名的可选参数,通过连续调用构造对象。

2. 在build方法中可以对参数约束检验。不符合时抛出IllegalStateException。

3. 有多个可变参数,灵活。便于添加新的构造参数。 (e.g Builder<T> in java)

Disadvantages:

additional performance.

Summary:如果类的构造器或者静态工厂中具有多个参数,考虑使用Builder.

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

Singleton(使调试困难):

1. public static final

public class Test{
public static final Test instance = new Test();
private Test(){...}
}


但是也可能利用反射生成新的实例,我们可以在构造器中抛出对应异常。

2. 利用静态工厂得到实例

public class Test{
private static final Test instance = new Test();
private Test() {...}

public static Test getInstance()
{ return instance; }
}


Advantages:

1. 很清楚的说明该类是singleton, final说明了对象的引用不能改变。

2. JVM可以把静态工厂方法的调用进行内联化。

3. 从1.5开始的第三种singleton:枚举类型

A single-element enum type is the best way to implement a singleton.

public enum Test{
INSTANCE;
}


Advantages:

类似于public static final,但是更简洁,防止多次实例化,防止反射攻击。

4. 序列化singleton类

声明所有实例域都是transient,并提供readResolve方法。否则会在反序列化时创建一个新的实例。

private Object readResolve{
return instance;
}


第四条:private constructor 强化不可实例化的能力

只包含static function, static field的utility class不希望被实例化。

1. abstract class

不能帮助组织实例化,因为子类仍可以被实例化

2. 私有构造器:

副作用是该类不能被继承,因为子类无法调用超类构造器

//Noninstantiable utility class
public class UtilityClass{
//Suppress default constructor for noninstantiability
private UtilityClass(){
throw new AssertionError();
}
}


第五条:avoid creating unnecessary objects

1. 最好重复使用对象,而不是创建新对象。

例如下面第二种方法来声明字符串更好。

String s = new String("str");

String s = "str";


优先用静态工厂方法调用不可变的对象,对于可变的对象也可以重用(初始化的时候创建这些实例)。

如下方法能减少不必要的实例化。calendar实例的创建昂贵。

//the starting and ending dates of the baby boom.
private static final Date BOOM_START;
private static final Date BOOM_END;

static{
Calendar gmtCalendar = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCalendar.set(1994, Calendar.JUNE, 29, 0 , 0 , 0);
BOOM_START = gmtCalendar.getTime();

gmtCalendar.set(2015, Calendar.JUNE, 29, 0 , 0 , 0);
BOOM_END = gmtCalendar.getTime();
}


2. 适配器

除了backing object之外没有其他的状态信息,不需要多个实例。

3. Map接口

keySet方法每次创建一个新的实例,但是所有返回的对象在功能上都是 返回所有key,因此多个实例也是不必要的。

4. Autoboxing:

要优先使用基本类型,无意识的装箱可能导致构造多余实例。

PS: 不是一直都要减少对象,小对象的创建和回收十分方便。

维护自己的object pool需要考虑,只有在对象是重量级的且昂贵的时候使用。

当你应该重用现有对象时,不要创建对象。

当你应该创建新对象时,不要重用现有对象。 --- 39条:defensive copying

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

memory leak: unintentional object retention.

1. 自己管理内存

对于active portion之外的引用,进行清空。

例如:出栈操作之后,要清空已经不再活动的引用。

Stack manage its own memory, it has allocated elements and free elements. But GC don't know. GC thinks all elements are useful.

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

//eliminate obsolete reference
elements[size] = null;
return result;
}


消除过期引用的最好方法: 让包含该引用的变量自然结束生命周期。

2. 缓存

WeakHashMap:

在缓存的项过期后,会被自动删除。缓存项的生命周期是由key的外部引用决定而不是value.

Timer/ ScheduledThreadPoolExecutor:

后台清理缓存的线程

在缓存加新条目时顺便清理: LinkedHashMap 的 removeEldestEntry()

3. listener 和 callback

注册回调后没有显式取消。

保持weak reference, 如 WeakHashMap

第八条:avoid finalize()

Never do anything time-critical in a finalizer.
Never depend on a finalizer to update critical persistent state.
There is a severe performance penalty for using finalizers.

Disadvantages:
1. 不能保证被及时执行甚至可能不被执行,可能导致资源耗尽。
2. 发生在finalize中的异常不会被打印。
3. 性能损失严重。

不要依赖终结方法来更新主要的持久状态。

显式的终止方法通常与try catch结合使用:

客户端在每个实例都不再有用的时候调用这个方法,并在private field中记录自己不再有效。
其他方法调用时先进行检查。
例如: stream的close方法,Image.flush, Timer.cancel。

显式释放native object所占用的资源

Attention: 如果子类覆盖了父类的finalize,那么必须要显式调用super.finalize。或者考虑使用finalizer guardian。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: