尽管Object类是一个具体的类,但是设计它主要是为了扩展。它所有的非final方法,如equals、hashCode、toString、clone和finalize都有明确的通用约定,因为它们被设计为要覆盖(override)的。任何一个类,在覆盖这些方法时,都必须遵守各自的约定,否则,其它依赖于这些约定的类(例如HashMap等)就无法结合该类一起正常运作。
一、equals()方法
1.Object类中的equals()方法
这是Object类中的equals()方法的声明
public boolean equals(Object obj) { return (this == obj); }
可以看到,Object中的equals方法是直接比较对象的地址。由于Object类是所有类的基类,我们在创建一个新类时,如果调用其equals方法,则默认比较的也是对象的地址。
我们知道,Java有8种基本类型:数值型(byte、short、int、long、float、double)、字符型(char)、布尔型(boolean)。
对于这几种基本类型,变量存储的就是值,比较变量也就是直接比较它们的值,因此可以直接使用 == 来进行比较。
另外还有一个枚举类型(enum),也可以直接用 == 来比较。查看源码就知道了,虽然它重写了equals方法,但和Object中的equals方法实现是一样的。
public final boolean equals(Object other) { return this==other; }
所以,对于Java中的8种基本类型和枚举类型,都可以使用 == 来比较。
2.为什么要重写equals()方法
而对于非基本类型,也就是引用类型,如类、接口、数组,由于变量中存储的是内存中的地址,并不是'值'本身,我们通常不需要比较对象的内存地址,而是要比较两个对象的字面值是否相等,因为这样更有意义。因此我们要覆盖默认的equals()方法。例如,String类就重写了equals方法和hashCode方法。
3.什么时候要重写equals方法
如果类具有“逻辑相等”(不同于对象相等)的概念,而其超类没有覆盖equals以实现期望的行为,这是就要覆盖equals方法。
这通常属于值类的情形,值类仅仅是一个表示值的类,例如Interger或者date。通常我们在利用equals方法来比较值对象的引用时,实际上是希望比较它们逻辑上是否相等,而非比较它们是否指向同一个对象。这时就必须覆盖equals方法,同时这样做也使得该类的实例可以作为映射表(map)的键(key)或者集合(set)的元素。
有一种值类不需要覆盖equals方法,
4.重写equals方法要遵循的约定
覆盖equals方法时,必须遵守它的通用约定。下面是约定的内容,来自Object的规范:
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)应该返回同样的结果; 非空性:对于任意非空引用x,x.equals(null)应该返回false;
二、hashCode()方法
1.hashcode()方法概述
hashCode()方法用于返回调用该方法的对象的散列码值。hashCode能唯一确定一个对象,不同的对象具有不同的hashCode
一个类如果重写了equals方法,通常也必须重写hashCode()方法,目的是为了维护hashCode()方法的常规约定,该约定声明相等的对象必须具有相等的散列码。
2.hashCode()方法的约定
hashCode()方法的常规约定如下:
1.在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这个同一对象调用多次,hashCode方法必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。 2.如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。反之,如果两个对象hashCode方法返回整数结果一样,则不代表两个对象相等,因为equals方法可以被重载。 3.如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。但,如果能让不同的对象产生不同的整数结果,则有可能提高散列表的性能。
3.基于Hash存储的数据结构使用hashCode()方法
基于散列的集合需要使用hashCode()方法返回的散列码存储和管理元素,如HashMap,HashSet等,在使用这些集合时,首先会根据元素对象的散列码确定其存储位置,然后再根据equals()方法结果判断元素对象是否已经存在。然后根据判断结果执行不同处理。如果对哈希码的获取没有相关保证,就可能会得不到预期的结果。
4.如何设计hashCode
①依赖易变数据时要小心
设计hashCode()方法最重要的因素就是:无论何时,对同一个对象调用hashCode()都会生成相同的值。
因此hashCode()方法依赖于对象中易变的数据,就要当心了。因为此数据发生变化时,hashCode()就会生成一个不同的散列码,相当于产生了一个不同的键。
②不能依赖于具有唯一性的对象信息
不能依赖于具有唯一性的对象信息,尤其是使用this的值,这只会产生糟糕的hashCode()。
③要想使hashCode()实用,它必须速度快,并且有意义。也就是说它必须基于对象的内容生成散列码?散列码不必是独一无二的(应该更关注生成速度,而不是唯一性),但是通过hashCode()和equals(),必须能够完全确定对象的身份。
④好的hashCode()应该产生均匀的散列码。
具体案例请参考《Java编程思想》
总结:
1.Java中如何比较两个对象?==和equals的区别?
对于8种基本类型和枚举类型,直接使用 == 比较。对于引用类型,需要覆盖equals方法类实现比较逻辑。
Object类中的equals方法和==意义一样,都是比较内存地址。而通常重写的equals方法用来比较对象的内容。
2.为什么要重写equals()方法?
Object中的equals比较的是对象的内存地址,而通常我们要比较对象的内容是否相等,这样比比较内存地址是否相等更有意义,这就需要重写equals方法。
3.hashCode()方法的作用?
hashCode()方法用于返回调用该方法的对象的散列码值。hashCode能唯一确定一个对象,不同的对象具有不同的hashCode。
4.为什么重写equals()方法的时候要重写hashCode()方法?
一个类如果重写了equals方法,通常也必须重写hashCode()方法,目的是为了维护hashCode()方法的常规约定,该约定声明相等的对象必须具有相等的散列码。
重写hashCode()方法主要是因为像Map这类集合会使用hashCode来定位元素。
5.如何重写hashCode()方法?
相关博客: