在不少场景下,须要断定两个对象是否 “相等”,例如:判断某个Collection 中是否包含特定元素。
==和equals()有和区别?如何为自定义 ADT正确实现equals()?html
==
和 equals()
。==
是引用等价性 ;而equals()
是对象等价性。
==
比较的是索引。更准确的说,它测试的是指向相等(referential equality)。若是两个索引指向同一块存储区域,那它们就是==的。对于咱们以前提到过的快照图来讲,==
就意味着它们的箭头指向同一个对象。equals()
操做比较的是对象的内容,换句话说,它测试的是对象值相等(object equality)。e在每个ADT中,quals操做必须合理定义。Java中的数据类型,可分为两类: java
严格来讲,咱们能够从三个角度定义相等:ide
以上两种角度/定义其实是同样的,经过等价关系咱们能够构建一个抽象函数(译者注:就是一个封闭的二元关系运算);而抽象函数也能推出一个等价关系。函数
equals()
应该比较抽象值是否相等。这和 equals()
比较行为相等性是同样的。hashCode()
应该将抽象值映射为整数。equals()
和 hashCode()
.equals()
应该比较索引,就像 ==
同样。一样的,这也是比较行为相等性。hashCode()
应该将索引映射为整数。equals()
和 hashCode()
覆盖,而是直接继承 Object
中的方法。Java没有为大多数聚合类遵照这一规定,这也许会致使上面看到的隐秘bug。hashCode
必须为两个被该equals
方法视为相等的对象产生相同的结果。首先来看Object中实现的缺省equals()
:性能
public class Object { ... public boolean equals(Object that) { return this == that; } }
在Object中实现的缺省equals()
是在判断引用等价性。这一般不是程序员所指望的,所以须要重写,下面是一个栗子:测试
public class Duration { ... // Problematic definition of equals() public boolean equals(Duration that) { return this.getLength() == that.getLength(); } }
尝试以下客户端代码,可获得this
Duration d1 = new Duration (1, 2); Duration d2 = new Duration (1, 2); Object o2 = d2; d1.equals(d2) → true d1.equals(o2) → false
基于以上结果进行如下解释:spa
d2
和o2
最终参照相同的对象在内存中,对他们来讲你仍然获得不一样的结果。 Duration
已经超载equals()
,由于方法签名与Object’s 不相同。咱们实际上有两种equals()
方法:隐式equals(Object)
继承Object
,和新的equals(Duration)
。Object
参考,那么d1.equals(o2)
咱们最终会调用equals(Object)
实现。Duration
参考,如在d1.equals(d2)
,咱们最终调用equals(Duration)
版本。o2
,d2
二者都会在运行时指向同一个对象!平等已经变得不一致。
咱们须要注释 @Override ,重写超类中的方法,所以,这里实施正确的 equals() 方法:
code
@Override public boolean equals (Object thatObject) { if (!(thatObject instanceof Duration)) return false; Duration thatDuration = (Duration) thatObject; return this.getLength() == thatDuration.getLength(); }
再次执行客户端代码,可获得:
Duration d1 = new Duration(1, 2); Duration d2 = new Duration(1, 2); Object o2 = d2; d1.equals(d2) → true d1.equals(o2) → true
回忆以前咱们对于相等的定义,即它们不能被使用者观察出来不一样。而对于可变对象来讲,它们多了一种新的可能:经过在观察前调用改造者,咱们能够改变其内部的状态,从而观察出不一样的结果。
List
对象包含相同的序列元素,那么equals()
操做就会返回真。在有些时候,观察等价性可能致使bug,甚至可能破坏RI。
假设咱们作了一个List
,而后把它放到Set
:
List<String> list = new ArrayList<>(); list.add("a"); Set<List<String>> set = new HashSet<List<String>>(); set.add(list);
咱们能够检查该集合是否包含咱们放入其中的列表,而且它会:
set.contains(list) → true
可是若是咱们修改这个存入的列表:
list.add("goodbye");
它彷佛就不在集合中了!
set.contains(list) → false!
事实上,更糟糕的是:当咱们(用迭代器)循环遍历这个集合时,咱们依然会发现集合存在,可是contains()
仍是说它不存在!
for (List<String> l : set) { set.contains(l) → false! }
若是一个集合的迭代器和contains()
都互相冲突的时候,显然这个集合已经被破坏了。
发生了什么?咱们知道 List<String>
是一个可变对象,而在Java对可变对象的实现中,改造操做一般都会影响 equals()
和 hashCode()
的结果。因此列表第一次放入 HashSet
的时候,它是存储在这时 hashCode()
对应的索引位置。可是后来列表发生了改变,计算 hashCode()
会获得不同的结果,可是 HashSet
对此并不知道,因此咱们调用contains
时候就会找不到列表。
当 equals()
和 hashCode()
被改动影响的时候,咱们就破坏了哈希表利用对象做为键的不变量。
下面是 java.util.Set
规格说明中的一段话:
注意:当可变对象做为集合的元素时要特别当心。若是对象内容改变后会影响相等比较并且对象是集合的元素,那么集合的行为是不肯定的。
咱们应该从这个例子中吸收教训,对可变类型,实现行为等价性便可,也就是说,只有指 向一样内存空间的objects,才是相等的。因此对可变类型来讲,无需重写这两个函数,直接继承 Object对象的两个方法便可。 若是必定要判断两个可变对象看起来是否一致,最好定义一个新的方法。