Effective java(二)

对于所有对象都通用的方法(二)

1.在改写equals的时候遵守通用约定

equals的通用约定有自反性,对称性,传递性,一致性,

自反性 : 对于任意的引用值x , x.equals(x)一定为true.

对称性 : 对于任意的引用x 和 y ,当且仅当 x.equals(y),为true时 y.equals(x)也一定为true.

传递性 : 对于任意的引用 x , y 和 z ,如果x.equals(y)为true,并且 y.equals(z)为true,那么 x.equals(z) 也一定为true.

一致性 : 对于任意的引用 x ,y 如果比较对象没有被修改,那么多次调用 x.equals(y) 要么一致返回true,要么一致返回false.

非空性 :  对于任意的非空引用值x,x.equals(null) 一定返回false. 所有对象都必须不等于null

在我们平常调用equals的不要违反这些约定,违反了这些约定会导致程序不正常.

总结一下实现高质量的equals的方法:

1.使用"=="操作符检查"实参是否是该对象的引用",是就返回true,这种呢只是一种性能的优化.如果在equals做比较的时候比较消耗性能的话,这种判断还是值得的.

2.使用instanceof操作符检查"实参是否为正确的类型",不是则返回false.

3.吧实参转换为正确的类型,如果之前已经有了instanceof 的检测,这个操作就可以确保成功.

4.对于该类中每一个关键域,检查实参中的域与当前对象中对应的的域值是否匹配.如果所有的测试成功则返回 true,否则返回false,另外,在这之前一定要确保是可访问的。注意一点的是,对于基本类型用==号就可以了(除了float和double,他们应该用Float.compare和Double.compare(a1,a2)方法,相等返回0,大于返回1,小于返回-1).对于对象引用域,调用equals方法。对于数组用Arrays.equals方法。域的比较顺序可能会影响到equals方法的性能。为了获得最佳的性能,应该最先比较最有可能不一致的域,或者是开销最低的域。

5.当编写完equals方法的时候,应该问自己是否满足对称性传递性一致性。如果否定就必须要更改equals方法代码。最后编写equals方法总要覆盖hashcode()方法,不要试着让equals方法过于智能。不要将equals声明中的Object对象替换为其他的类型。

2.改写equals时总是要改写hashcode

      一个常见的错误就是,改写equals的时候要改写hashcode,不改写的话会违反java.lang.Object通用约定,从而导致该类无法与所有基于散列值的集合类结合在一起正常运作.

hashCode有三个规范,这三个规范和equals的规范差不多

第一:在一个对象的hsahCode没有被改变的时候,多次调用同一个对象,应该返回同一个整数,这个整数可以不一样,也就是这个程序程序执行的时候返回的整数可以不一致.

第二:如果两个对象根据equals(Object)方法是相等的,那么调用任意一个对象的hashCode也必须返回同一个整数.

第三:如果两个对象根据equals(object)方法是不相等的,那么调用任意一个对象的hashCode不要求返回的整数一定相同.

这里因为没有改写hashcode违反的关键约定是,相等的对象必须要有相等的散列码hashCode.

3.总是要改写toString

      改写toString并没有改写equals和hashCode那么重要,但是提供一个好的toString可以使一个类用起来更加愉快.建议所有的子类都覆盖这个方法.

   在Object提供的toString方法中打印一个PhoneNumber的实例的时候,输出的是com.wuyi.effective Java.PhoneNumber@9b448依次是类的名称,@号和无符号的十六进制表示法。查看源码:returngetClass().getName() + "@"+ Integer.toHex String(hashCode());建议所有的子类都覆盖这个方法。

4.谨慎地改写clone

      Object的clone是被保护的,如果不借助于映象机制,则不能仅仅因为一个对象实现了Cloneable,就可以调用clone,即使是映象调用也可能会失败,并不保证clone是一定可以调用成功的.

      clone一个对象首先是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部.

      这里有对clone的一些说明:https://blog.csdn.net/qq_36746994/article/details/82662957

5.考虑实现Comparable接口

        Comparable方法在Object中并没有被声明.相反它是java.lang.Comparable中唯一的方法.它与equals有相同的特征,一个类实现了Comparable接口就表明它的实例具有内在的排序关系,若一个数组中的对象实现了Comparable接口,则对这个数组进行排序非常简单.

      事实上,在java平台库中的所有值类都实现了Comparable接口,比如:String、Byte、Char、Date . Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,就是这些类是可以和自己比较的,具有非常明显的内在排序关系,至于具体和另一个实现了Comparable接口的类如何比较,则依赖compareTo方法的实现,compareTo方法也被称为自然比较方法。这个链接里面我觉得这个例子挺好,还比较了Comparable与Comparator的区别:https://www.cnblogs.com/szlbm/p/5504634.html

6.finalize

      Java中的finalize的调用具有不确定性.也就是在java中调用终结函数具有不确定性.并不能保证每次调用都会执行.

      finalize()是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。System.gc()与System.runFinalization()方法增加了finalize方法,但不可盲目依赖它们.finalize方法可能会带来性能问题。因为JVM通常在单独的低优先级线程中完成finalize的执行,也就是之前说到过的,执行finalize的线程优先级,比该应用程序的其他线程的优先级要低的多.不要依赖一个终结函数来更新关键性的永久状态,不能保证会被执行甚至会不会被执行都不知道.

(注:下面的这一块是我从网络上copy下来的,觉得写的很好,为了尊重原创哈...........)

finalize的执行过程(生命周期)

(1) 首先,大致描述一下finalize流程:当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。

(2) 具体的finalize流程:

对象可由两种状态,涉及到两类状态空间,一是终结状态空间 F = {unfinalized, finalizable, finalized};二是可达状态空间 R = {reachable, finalizer-reachable, unreachable}。各状态含义如下:

  • unfinalized: 新建对象会先进入此状态,GC并未准备执行其finalize方法,因为该对象是可达的
  • finalizable: 表示GC可对该对象执行finalize方法,GC已检测到该对象不可达。正如前面所述,GC通过F-Queue队列和一专用线程完成finalize的执行
  • finalized: 表示GC已经对该对象执行过finalize方法
  • reachable: 表示GC Roots引用可达
  • finalizer-reachable(f-reachable):表示不是reachable,但可通过某个finalizable对象可达
  • unreachable:对象不可通过上面两种途径可达

状态变迁图:

变迁说明:

  1. 新建对象首先处于[reachable, unfinalized]状态(A)
  2. 随着程序的运行,一些引用关系会消失,导致状态变迁,从reachable状态变迁到f-reachable(B, C, D)或unreachable(E, F)状态
  3. 若JVM检测到处于unfinalized状态的对象变成f-reachable或unreachable,JVM会将其标记为finalizable状态(G,H)。若对象原处于[unreachable, unfinalized]状态,则同时将其标记为f-reachable(H)。
  4. 在某个时刻,JVM取出某个finalizable对象,将其标记为finalized并在某个线程中执行其finalize方法。由于是在活动线程中引用了该对象,该对象将变迁到(reachable, finalized)状态(K或J)。该动作将影响某些其他对象从f-reachable状态重新回到reachable状态(L, M, N)
  5. 处于finalizable状态的对象不能同时是unreahable的,由第4点可知,将对象finalizable对象标记为finalized时会由某个线程执行该对象的finalize方法,致使其变成reachable。这也是图中只有八个状态点的原因
  6. 程序员手动调用finalize方法并不会影响到上述内部标记的变化,因此JVM只会至多调用finalize一次,即使该对象“复活”也是如此。程序员手动调用多少次不影响JVM的行为
  7. 若JVM检测到finalized状态的对象变成unreachable,回收其内存(I)
  8. 若对象并未覆盖finalize方法,JVM会进行优化,直接回收对象(O)
  9. 注:System.runFinalizersOnExit()等方法可以使对象即使处于reachable状态,JVM仍对其执行finalize方法
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值