hashCode和equals

尽管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()方法的常规约定,该约定声明相等的对象必须具有相等的散列码。

hashCode()方法的常规约定如下:

1.在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这个同一对象调用多次,hashCode方法必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。

2.如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。反之,如果两个对象hashCode方法返回整数结果一样,则不代表两个对象相等,因为equals方法可以被重载。

3.如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。但,如果能让不同的对象产生不同的整数结果,则有可能提高散列表的性能。

2.基于Hash存储的数据结构使用hashCode()方法

基于散列的集合需要使用hashCode()方法返回的散列码存储和管理元素,如HashMap,HashSet等,在使用这些集合时,首先会根据元素对象的散列码确定其存储位置,然后再根据equals()方法结果判断元素对象是否已经存在。然后根据判断结果执行不同处理。如果对哈希码的获取没有相关保证,就可能会得不到预期的结果。

3.如何设计hashCode

依赖易变数据时要小心

设计hashCode()方法最重要的因素就是——无论何时,对同一个对象调用hashCode()都会生成相同的值

因此hashCode()方法依赖于对象中易变的数据,就要当心了。因为此数据发生变化时,hashCode()就会生成一个不同的散列码,相当于产生了一个不同的键。

不能依赖于具有唯一性的对象信息,尤其是使用this的值,这只会产生糟糕的hashCode()。

③要想使hashCode()实用,它必须速度快,并且有意义。也就是说它必须基于对象的内容生成散列码?散列码不必是独一无二的(应该更关注生成速度,而不是唯一性),但是通过hashCode()和equals(),必须能够完全确定对象的身份。

④好的hashCode()应该产生均匀的散列码。

具体案例请参考《Java编程思想》

 

总结:

1.Java中如何比较两个对象?==和equals的区别?

对于8种基本类型和枚举类型,直接使用 == 比较。对于引用类型,需要覆盖equals方法类实现比较逻辑。

Object类中的equals方法和==意义一样,都是比较内存地址。而通常重写的equals方法用来比较对象的内容。

2.hashCode()方法的作用?

hashCode()方法用于返回调用该方法的对象的散列码值。hashCode能唯一确定一个对象,不同的对象具有不同的hashCode。

3.为什么要重写equals()方法?

Object中的equals默认比较的是对象的内存地址,而通常我们要比较对象的内容是否相等,这样比比较内存地址是否相等更有意义,这就需要重写equals方法。

比如,JDK中一些常见的类都已经为我们重写了equals方法,所以我们可以直接使用equals进行内容比较。如果是我们自定义的类,则就需要我们自己去重写equals方法了。

4.为什么重写equals()方法时,要同时重写hashCode()方法?

一个类如果重写了equals方法,通常也必须重写hashCode()方法,目的是为了维护hashCode()方法的常规约定,该约定声明相等的对象必须具有相等的散列码。

重写hashCode()方法主要是因为像Map这类集合会使用hashCode来定位元素。

5.如何重写hashCode()方法?

6.一个对象的hashcode可以改变么?

应该说,当一个可变对象的内部状态改变时,其哈希码通常也会改变。但是,当该对象配合HashMap等使用时,我们必须确保该对象不会发生变化,以防其哈希代码发生改变。比如,使用HashMap时,我们经常将String型对象作为key,String就是不可变类型。

7.Java中两个对象的 hashCode相同,则 equals比较也一定为 true,对吗?

两个对象equals相等,则它们的hashcode必须相等,反之则不一定。
两个对象==相等,则其hashcode一定相等,反之不一定成立。

 

 

重写equal()时为什么也得重写hashCode()之深度解读equal方法与hashCode方法渊源

posted @ 2018-05-15 17:44  静水楼台/Java部落阁  阅读(273)  评论(0编辑  收藏  举报