hashCode()和equals()以及容器对象的修改一致性

前面对数组和容器类(集合类)已经做了整理和小结。本篇把相关的几个问题单独整理出来做一个小结,主要是对容器对象的修改一致性、并发初步考虑和经典的hashCode()以及equals()方法问题的讨论。

先看两段简单的示例代码。

捕获遍历删除

上面这段代码的问题你看出来了么?会抛出异常的。如果没看出来,看下下面修改过的吧,这个是不会抛出异常的。

捕获遍历删除1

再看一个迭代器的版本。

捕获遍历删除2

类似,也会出异常,更常见的写法是

for(Integer tmp : list)

其实,移除操作应该用itr.remove()代替,原因是其保持了数据的一致性,下面详细说明。

这里要说下modCount属性和fail-fast行为。

为了发现上述提到的容器对象修改不一致的行为(这通常会出现在使用迭代器的时候同时对原对象做结构性修改的情况下),很多容器类都有modCount属性字段。如List类是在AbstractList中,HashMap和TreeMap类中也都有modCount属性,而Set是基于Map实现的。在这些容器初始化时,modCount为0,当发生结构性变化的时候,比如add、remove等,会对其进行自加操作(++)。对应的,在iterator对象中,一般都有一个expectedModCount属性,初始化为外部对象的modCount,在进行结构性变化操作之前,会对两者进行对比校验,出现不一致时直接抛出ConcurrentModificationException异常。这就是fail-fast行为,她“尽最大努力”发现修改不一致的情况,但不保证容器对象同步的可靠性。

此外,关于线程同步(synchronized)的问题,Collections类还提供了同步化容器的方法synchronizedCollection()和静态内部类,SynchronizedCollection。这个类是对传入参数Collection对象的包装,执行方法相当于对被包装对象方法执行加了synchronized关键字块,同步对象为被包装对象。Collections也提供了重载方法,可以加一个Object类mutex参数,声明synchronized块需要锁住的对象。

对容器对象同步操作,更多的需要考虑使用java.util.concurrent包中的并发容器类。

为了满足对容器对象的只读需求,Collections还提供了unmodifiableCollection()方法和静态内部类实现UnmodifiableCollection。和同步化容器类似,这个类也是对参数传入的Collection对象的一个不可变包装,返回的UnmodifiableCollection对象的add()、remove()、clear()以及迭代器的remove()等方法不调用被包装对象方法,相当于不可用,调用时抛出UnsupportedOperationException异常。其它方法调用被包装对象的同名方法。

 

再说说hashCode()和equals()方法。这两个方法在java.lang.Object()都有声明,其中hashCode是native实现,按注释说明,此hashCode能保证不同对象不同结果(因为实现和内部地址有关,但这并不是Java语言所要求的)。在Object中,equals()这个方法不是native的,而是Java实现,是对对象的引用进行比较(直接将this对象和传入参数obj进行==比较)。

有关hashCode() ,前面文章说HashMap和HashSet实现的时候已经介绍了很多,这里再说下其它细节。

  • java.util.AbstractSet、java.util.AbstractList、java.util.AbstractMap有自己的重写实现,都与放入其中的对象的hashCode()有关
  • HashMap和TreeMap以及各自的Entry的hashCode()实现都基本一样,Map本身继承AbstractMap的hashCode(),Entry的hashCode()是key与value的hashCode()取异或
  • 对于HashMap,每个Entry对象除了有hashCode()方法,还有一个hash属性值,两者意义不同。hash属性值是通过key的hashCode()做位移等计算得来的(版本不同,计算方法不完全相同),这个hash值和整个map的表长度共同决定这个entry所在的位置
  • 和equals()结合使用。这里提到这个方法是说一下equals()和hashCode()的关系,equals()为真的,hashCode()原则上应该相等,而hashCode()相等的,equals()可以不为真。
  • 重写equals()的一些需要遵从的原则。至少要保证自等性、对称性、传递性和一致性。自己equals()自己必然要为true,a和b等反过来也要可以,a和b相等b和c相等a也要和c相等,a和b的比较一次调用equals()是返回true,如果对象内容(和比较逻辑有关的)没有发生变化,重复多次也依然要返回true。根据hashCode()和equals的关系,hashCode()也要遵从类似原则。

本篇就写到这里,有一个有趣的小问题我纠结过一会儿,本文开头处用迭代器循环,用list.remove(tmp)判断条件是tmp.equals(0),参数如果为1就不会抛出异常,这是为什么呢?呵呵,大家感兴趣的可以搞清楚为什么。

此条目发表在 Java, 开发, 计算机技术 分类目录,贴了 , , , , 标签。将固定链接加入收藏夹。

hashCode()和equals()以及容器对象的修改一致性》有 5 条评论

  1. Honwhy 说:

    你的代码示例是截屏的啊

发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>