Effective Java

创建和销毁对象

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

让客户端获取自身的一个实例,一个返回类的实例的静态方法。

类可以通过静态工厂方法提供自身的客户端,相比于公有的构造器具有以下优势:

1、他们有名称

    运用-- 当一个类需要多个带有相同签名的构造器时

2、不必每次调用它们的时候都创建一个新对象

   静态工厂方法能够为重复的调用返回相同的对象,有助于类严格控制某个时刻哪些实例应该存在,此类称为实力受控的类。

  编写实力受控的类主要有以下几个原因:一是确保是Singleton或是不可实例化;二是使不可变的类不会存在两个相等的实例。

3、返回原返回类型的任何子类型的对象

4、创建参数化类型实例的时候,使代码变得简洁

     有了静态工厂方法,编译器可以替你找到类型参数,叫做类型推导。

调用参数化构造器时声明:
Map<String,List<String>> m=new HashMap<String,List<String>>;
HashMap提供了静态工厂:
public static <K,V>  HashMap<K,V> newInstance(){
    return new  HashMap<K,V>();
}
则可以简单的声明为:
Map<String,List<String>> m=HashMap.newInstance();

缺点:

1、类如果不含有公有的或者受保护的构造器,就不能被子类化;

2、与其他静态方法没有区别;

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

静态工厂和构造器有共同的局限性:不能很好的扩展大量的可选参数。

三种方式:

第一种:重叠构造器模式

参数多的时候,客户端很难写,难于阅读。

第二种:JavaBean模式

调用无参构造器创建对象,然后调用setter方法设置每个必要的参数以及可选的参数。

虽然创建实例容易,阅读方便,但缺点很明显--在构造过程中JavaBean可能处于不一致的状态,JavaBean模式阻止了把类做成不可变的可能。

第三种:Builder模式

不直接生成想要的对象,二是让客户端利用必要的参数调用构造器(或静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的builder方法来生成不可变的对象

缺点:

创建对象,就必须创建构建器,注重性能时会成为问题;

类需要多个参数时才需要构建器,参数少时用比重叠构建器更冗余。

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

Singleton指仅仅被实例化一次的类,通常代表本质上唯一的系统组件,比如窗口管理器或文件系统。使类成为Singleton会使它的客户端测试变得很困难。

Java 1.5之前,实现Singleton有两种方法。这两种方法都把构造器保持为私有的,并导出公有的静态成员,以允许客户端访问该类的唯一实例。

第一种:公有静态成员是个final域

很清楚的声明了这个类是一个Singleton。

注意:享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。若要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。

第二种:公有的成员是个静态工厂方法

提供了灵活性,不改变API的前提下,可以改变该类是否应该为Singleton的想法。但很容易被修改。

利用一种方法实现的Singleton类变成是可序列化的,仅仅在声明中加上implements Serializable是不够的。为了保证Singleton,必须声明所有实力域都是瞬时的(transient),并提供一个readResolve方法。否则,每次反序列化一个序列化的实例时,都会创建一个新的实例。

Java1.5起,出现第三种方法:编写一个包含单个元素的枚举类型

无偿提供了序列化机制,绝对防止多次实例化,面对序列化或反射攻击时也是如此。虽没有广泛采用,但是实现Singleton的最佳方法。

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

工具类(utility class,像java.util.Math、java.util.Array、java.util.Collections)不需要实例化,实例化没有意义。

但是,在缺少显式构造器的情况下,编译器会默认创建公有的、无参的构造器。针对这样的问题,只要创建一个私有的构造器,这样编译器就不会默认创建缺省构造器,这样的类也不能实例化。

私有的构造器在类的外部不可访问,AssertionError知识用来防止类内部不小心调用它。

这样做有一个副作用,类不能被子类化,所有的构造器必须显式或隐式的调用超类构造器,在这种情况下,子类就没有可访问的超类构造器可调用了。

子类化--在不修改现有代码的前提下,扩展现有窗口的功能。

超类化--读取现有窗口类的数据,保存窗口过程函数地址。

其中,企图通过将类做成抽象类来强制该类不可实例化,是行不通的,因为该类可以被子类化,并且该子类可以被实例化。

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

一般来说,最好能重用对象,而不是在需要的时候创建一个相同功能的新对象。

考虑如下:

该语句每次执行都会创建一个新的String实例,而stringstte本身就是一个String实例。

改进如下:

处理重用不可变的对象之外,也可以重用那些已知不会被修改的可变对象。

创建多余对象的新方法--自动装箱。要优先使用基本类型而不是装箱基本类型,当心无意识的自动装箱(将long写成Long)。

在提倡使用保护性拷贝的时候,重用对象付出的代价远远大于创建重复对象而付出的代价。

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

内存泄漏—>过期引用—>修复方法—>清除过期引用好处—>何时清空—>来源

内存泄漏

随着垃圾回收器活动增加或者内存占用增加,程序性能会降低,极端情况下,会导致磁盘交换,甚至程序失败(很少见)。

哪里发生了泄漏

如果一个栈先增长后收缩,那从栈中弹出来的对象将不会当作垃圾回收,即使使用栈的程序不再引用这些对象,也不会被回收。因为在栈的内部维护着对这些对象的过期引用。

过期引用

永远不会被解除的引用。

修复

一旦对象引用过期,清空这些引用即可。对于Stack类,只要一个单元被弹出栈,指向它的引用就过期了。

清除过期引用好处

一是防止内存泄漏;二是若是以后被错误的解除引用,程序会立即抛出NullPointrException异常,而不是悄悄的错误运行下去。

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

消除过期引用最好的办法是让包含该引用的变量结束生命周期。

何时清空过期引用?Stack类哪方面特性易受内存泄漏影响/

问题在于,Stack类自己管理内存。存储池包含了数组元素(对象引用单元,而不是对象本身),数组活动区域的元素是已分配的,数组其余部分元素是自由的,但立即回收器不知道这一点,但是程序员知道非活动部分不重要。这个时候做法:一旦元素变成了非活动部分的一部分,程序员就手动清空这些数组元素。

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

内存泄漏另一个来源

缓存

只要在缓存之外存在对某个项的键的引用,该项就有意义,可以用WeakHashMap代表缓存;当缓存中的项过期之后,他们就会自动删除。只有当所要缓存项的生命周期是由该键的外部引用而不是由值决定时,WeakHashMap才有用处。

常见的情形,缓存项的生命周期是否有意义并不是很明确,缓存需要时不时的清除掉没用项。清除工作可以由一个后台线程(可能是Timer或者ScheduledThreadPoolExecutor)来完成,或者在给缓存添加新条目的时候顺便清理。

监听器和其他回调

如果实现了一个API,客户端在这个API中注册回调,却没有显式的取消注册,除非采取某些动作,否则他们就会积聚。

确保回调被立即当作垃圾回收的最佳方法是只保存他们的弱引用,例如,值只将它们保存成WeakHashMap中的键。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值