Effective in Java 笔记

来源:互联网 发布:网络打印机服务器软件 编辑:程序博客网 时间:2024/05/19 22:03

1、用静态工厂方法代替构造函数

 

优点:

  1. 与构造函数不同,静态工厂方法具有名字(表意好)。
  2. 每次被调用时,不要求非得创建一个新的对象。
  3. 可以返回一个原返回类型的子类型对象。

 

缺点:

  1. 类如果不含有公有的或受保护的构造函数,就不能被子类化。
  2. 它们于其他的静态方法没有任何区别(易混淆) 

 

2、使用私有函数构造强化Singleton属性

 

 

 

3、通过私有构造函数强化部可实例化的能力

 

顾名思义即可,没什么神奇的。

缺点,不能被子类化。

 

 

4、避免创建重复的对象

 

String s  = new String("silly");//每次创建一个新对象

String s =  "silly";//只要JVM中有同一个字面量,该对象被重用。

 

代码优化: 不可变类可重用,可变类选择性重用。

                 对于重量级对象,创建对象池(例如数据库连接池)

 

 

5、消除过期的对象引用。

 

过期引用,指永远也不会再被解除的引用。

 

例如:书中P28

增加数组中的项目: elements[size++] = e;

减少数组中的项目: elements[--size];

但有效范围外的数组项仍有引用,即过期引用。

所以要加上一句:     elements[size]==null;

 

不需要过分注意引用清楚,做到以下2点:

  1. 重要变量,尽量一个引用,一个对象,如果可能。
  2. 再以个最紧凑的作用域范围内定义每一个变量,如方法,方法推出引用清楚。

 

对于内存泄露,注意:

1)当自己管理内存时,注意引用清楚。

2)缓存:

  • 若再缓存之外存在对某个条目的键的引用,该条目就有意义,那么使用WeakHashMap来代表缓存,弱引用键。
  • 更为常见,写一个周期进程或在新加条目时做清理工作,使用LinkedHashMap的三个参数的构造函数,最后一个参数用true,按访问顺序排序,使用它的removeEldestEntry方法清楚老的,部常访问的条目

 

6、避免使用终结函数(finalizer)

 

1、不同JVM实现不同,终结函数不可预测。

2、如果用终结函数处理回收资源,时灾难性的。

     终结函数线程优先级较低,不一定能及时执行,使内存未被及时清理。

     一般用try-finally完成类似的工作。

 

八卦:System.gc和System.runtimalization确实增加终结函数被执行的机会,但不保证一定执行。

         唯一保证的System.runFinalizersOnExist和Runtime.runFinalizerOnExist有致命缺陷,已被废弃了。

3、终结函数中跑出未捕获异常,该终结函数将终止。

参考:若想回收资源,做收尾工作,写意个显示的终止方法;

如:connect.close();,通常与try-finally结合使用。

 

终结函数的优点:

  • 安全网,最后一道防线,迟一点释放总比永远不释放好。
  • 与对象的本地对等体有关(不知所云- -!)

 

7、再改写equals的时候请遵守通用约定。

  1. 自反性
  2. 传递性
  3. 对于空引用,返回false
  4. 对称性
  5. 一致性

以下情况无需提供equals方法:

  1. 一个类的每个实例本质上都是唯一的(比如线程类)
  2. 不关心一个类是否提供了“逻辑相等”的测试功能。
  3. 超类已经改写了equals,从超类继承过来的行为对于子类也是合适的。
  4. 一个类是私用的,或者包级私有,可以确定equals方法不会被调用。

注:要想在扩展一个可实例化的类的同时,既要+特性,又要保留equals约定,没有一个简单的办法解决。

    书中的坐标+颜色 例子 解释得非常具体。

    关键是调用父类equals方法,需对子类新特性单独处理。

    要想做到这一点,用复合,复合优于集成,保留“父类"的变量,在与“父类”equals操作时,使用该变量(详见书中例子)

 

结论:equals方法建立在同类,父子间无法忽视某一新增特性做equals操作。

Java平台库中的例子:有些类是可实例化类的子类。

                               java.sql.Timestamp 对 java.util.Data 进行子类化,加入新特性

                               Timestamp的equals实现确实违反了对称性。

                               Timestamp中有声明:不要混合使用Timestamp和Data,例如放在一个集合中。

 

 

实现高质量equals方法的处方:

  1. 使用==操作符检查实参是否指向对象的一个引用
  2. 使用instanceof检查实参是否为正确的类型
  3. 把实参转换为正确的类型。
  4. 对每个“关键域”进行匹配。最有可能不一致的域和比较开销低的域,先比较。
  5. 编写完后,检查对称、传递、一致。

告诫:

  1. 改写equals方法,总是要改写hashCode方法。
  2. 不要让equals过于细粒度,比如比较文件URL的分隔符
  3. 不要让equals依赖不可靠的资源(网络等),应该关注驻留在内存中的对象。
  4. 不要将equals声明的形参类型Object替换掉。

 

8、改写equals总是要改写hashCode

 

hashCode约定的内容:

  1. 一个应用程序执行期间,equals方法所用的信息未改变,那hashCode返回整数值不变。不同执行过程中,hashCode整数值可以不同。
  2. 两个对象equals方法回true,那么hashCode返回值相等。
  3. 两个对象equals方法回false,不要求hashCode返回值不同

理想情况:一个散列函数应把一个集合中的不相等实例均匀分布到所有可能的散列值上。

 

接近理想情况的处方:

1)把非零常量值(如17等),保存在一个叫result的int类型的变量中。作为初始值。

2)对每个关键域:(f)

 

a:计算int类型的散列码:(c)

i:balean ->(f?0:1)

ii:byte,char,short或int ->(int)f

iii:long ->(int)(f^(f>>>32))

iv:float ->Float.floatToIntBits(f)

v:double->Double.doubleToLongBits(f)  -> long -> iii

vi:对象引用,若该类equals方法递归来比较,则同样递归hashCode

                  若需要更为复杂的比较,比该域计算一个“规范标识”,对此范式调用hashCode

                   (实际情况,分析)

vii:数组,对每个元素单独处理。

b:把a中计算得到的散列码c组合到result中

result = result * 37 + c; (37是个素数,习惯用素数)

这种算法同样依赖域的顺序,更好。

 

3)返回result

4)写完检查“是否相等的实例具有相等的散列码”

注:在equals中未用的域,hashCode也不用,满足第2条约定。

如果一个类是非可变的,且计算散列码代价很大,可以考虑把散列码缓存在对象内部,在被创建或第一次需要时计算。

 

在Java的HashMap中,get方法通过hash && equals的方法作筛选,如果hashCode不一样就不必计算equals了,所以将对象分散在不同的散列码上,可以极大提高性能。

 

最不理想的情况是,大量实例分布在极少数的散列码上,那么集合的性能将是平方级的。

 

 

 

9、总是要改写toString

 

在java.lang.Object 提供的toString返回,类名@散列码(十六进制)

根据toString约定应返回一个“简洁的,但信息丰富,易于阅读”的字串。

 

 

10、谨慎得改写clone

 

所有基本类型,还有String等不可变类能实现deepClone,如果超类都提供良好的Clone方法,这样工作很简单。

  1. 实现Clonable接口
  2. 重写clone方法
  3. 在clone方法中,调用super.clone,对于类中的引用类型单独考虑。

总结条件:

1)域值:基本类型、不可变类、虽然clone的是引用,但该引用对象不可变,无影响。

2)超类都提供了良好的clone方法

3)某些域值不能是final,否则无法赋值(对于可变对象)| 即clone与指向可变对象的final域不兼容

     如 result.elements  = (object[]) elements.clone();// elements不能是final

     除非 原始对象和克隆对象可以共享此可变对象。

4)对于一些链表和集合,对其中的值要逐个克隆或new,否则只是克隆了链表头和集合的key(如HashMap)

链表克隆参考

Entry deepCopy{

  Entry result = new Entry(key,value,next);

  for(Entry p =result;p.next !=null;p=p.next)

    p.next = new Entry(p.next.key,p.next.value,p.next.next);

    return result;

}

5)与构造函数一样,clone方法不应在构造过程调用非final方法,因为其可以被重写,在子类修正新对象前,可能会导致新旧对象不一致。

     所以调用方法要么是final,要么是私有的。

 

*有时用 拷贝构造函数 或 静态方法 更适合,因为没有上面那么多限制并且更加灵活,比如用Linklist拷贝或ArrayList,除非为了一些低开销的拷贝。

 

原创粉丝点击