在java中有三大主要的容器类:list,set和map。他们都是interface,在这三大容器类下面分别有AbstractList,AbstractSet和AbstractMap这三个抽象类去实现他们,他们几乎实现了对应接口中的所有方法(有少数方法没有实现,仍然是abstract的)。其中他们实现的主要的方法就是hashCode和equals方法(toString方法不一定在这些类中实现的,因为他们有的不是直接实现对应接口的,而是通过继承AbstractCollection这个抽象类来实现接口的,toString方法有的是在抽象类中实现的,比如AbstractList就是继承了AbstractCollection的抽象类)。下面我们就来分别的讲解一下他们内部是如何实现hashCode和equals方法的:
首先我们需要说明的是他们的实现都是有效的,即他们的实现都能够保证下面的推导成立: a.equals(b) =》 a.hashCode() = b.hashCode()
上面这个是一个充分非必要条件(AbstractSet对于equals和hashCode的实现就是一个例子),即a.hashcode() = b.hashcode() =》 a.equals(b)是不对的。
所以有了上面的保证,我们以后就可以放心大胆的使用容器类(凡是继承了AbstractList,AbstractSet或者AbstractMap的容器类),而不用担心他们的equals和hashcode重新的问题了,而且java的设计者也是不希望我们去重新他们辛辛苦苦设计好的equals和hashcode方法的,我们自己贸贸然的重新可能会导致不必要的错误,所谓费力不讨好。比如最常用的就是将一个Set(比如HashSet)作为元素放在另一个Set(比如HashSet)中去,我们就可以直接使用,而不用担心因为hashcode和equals重写的问题造成的逻辑错误。
下面我们就来看看java的设计者是如何重新这些方法的:
| equals() (若a.equals(b)为true) | hashCode() (hashCode()的算法) |
AbstractList | 1.a和b都是List的子类 2.a.size()和b.size()相等 3. a中和b中元素必须对应相等,即a中和b中下标相等的元素必须相等(即调用元素本身的equals方法返回true) | 将list中每个元素的hashCode利用特殊的hash算法加起来,详细代码可以参考java源码。 |
AbstractSet | 1. a和b都是Set的子类 2. a.size()和b.size()相等 3. a中包含b中所有元素,由于Set是不考虑位置顺序的,所以我们不要求位置一样。由于有了第二条的限制,所以这条就保证了a和b一定含有相同的元素,当然比较a和b中的元素是否相等我们调用的也是元素本身的equals方法,返回true就表示两个元素相等。 | 将所有非null元素的hash code加起来,当然元素的hash code是通过调用元素的hashCode方法获得的。 |
AbstractMap | 1. a和b都是Map的子类 2. a.size()和b.size()相等 3. a中包含的mapping和b中包含的mapping是相同的,即 a.entrySet().equals(b.entrySet()),具体说来就是a中的key set和b中的key set是相等的,并且相同key对应的值也是相等的。值的相等都是通过equals方法判断的。 | Map中所有Entry对象的hash code的和(Entry对象的hash code通过调用他们自己的hashCode方法获得)。而Entry类的定义是Entry<K,V>在Map中起到存放键值对的作用,Entry<K, V>的hash code的算法是: return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); |
结语:愉快的享受java工程师给我们带来的便利吧。