1 equals规范
equals是Object类提供的方法之一,因为每一个Java类都继承自Object类,所以每一个对象都具有equals这个方法。因为Object 类中定义的equals(Object) 方法是直接使用“= =”运算符比较的两个对象,所以在没有覆盖equals(Object) 方法的情况下,equals(Object) 与“= =”运算符一样,比较的是引用。
通常你会希望放宽这个限制。一般来说如果两个对象有相同的类型和相同的字段,你会认为这两个对象相等,但也会有一些你不想加入 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。
- 对于任何非null的x,x.equals(null)应该返回false。
2 hashCode原理
hashCode()方法是从 Object 类中继承过来的,它也用来鉴定两个对象是否相等。Object类中的hashCode()方法返回对象在内存中地址转换成的一个int值,所以如果没有重写hashCode()方法,那么任何对象的hashCode()方法都是不相等的。一般在覆盖equals方法的同时也要覆盖hashCode()方法,否则,就会违反Object.hashCode的通用约定,从而导致该类无法与所有基于散列值(hash)的集合类(HashMap、HashSet 和Hashtable)结合在一起正常运行。
设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该生成同样的值。如果在将一个对象用put()添加进HashMap时产生一个hashCode()值,而用get()取出时却产生了另一个hashCode()值,那么就无法重新取得该对象了。所以,如果你的hashCode()方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()就会生成一个不同的散列码,相当于产生了一个不同的键。
此外,也不应该使hashCode()依赖于具有唯一性的对象信息,尤其是使用this值,这只能产生很糟糕的hashCode(),因为这样做无法生成一个新的键,使之与put()中原始的键值对中的键相同。
下面以String类为例。String有个特点:如果程序中有多个String对象,都包含相同的字符串序列,那么这些String对象都映射到同一块内存区域。所以new String(“hello”)生成的两个实例,虽然是相互独立的,但是对它们使用hashCode()应该生成同样的结果。通过下面的程序可以看到这种情况:
public class StringHashCodeCase {
public static void main(String[] args) {
String[] hellos = "Java,Java".split(",");
System.out.println(hellos[0].hashCode());
System.out.println(hellos[1].hashCode());
}
}
输出结果为:
2301506
2301506
对于String而言,hashCode() 明显是基于String的内容的。
因此,要想使hashCode() 实用,它必须速度快,并且必须有意义。也就是说,它必须基于对象的内容生成散列码。记得吗,散列码不必是独一无二的(应该更关注生成速度,而不是唯一性),但是通过 hashCode() 和 equals() ,必须能够完全确定对象的身份。
因为在生成桶的下标前,hashCode()还需要做进一步的处理,所以散列码的生成范围并不重要,只要是int即可。
还有另一个影响因素:好的hashCode() 应该产生分布均匀的散列码。如果散列码都集中在一块,那么HashMap或者HashSet在某些区域的负载会很重,这样就不如分布均匀的散列函数快。
3 总结
通常来说重写equals是因为在java的集合框架中,是通过equals来判断两个对象是否相等的,equals规范的默认规则不满足。因此我们重写符合我们实际要求的判断规则。而重写hashCode只是技术要求(为了提高效率)。