Java hashCode()与equals()的关联

一、Hash表数据结构介绍

请参考 哈希表详解

二、equals的内部实现

equals()的定义位于Object.class中:

    public boolean equals(Object obj) {
        return (this == obj);
}

从这里可以看出,如果不重写的话,equals默认就是断定两个对象的内存地址是否相同。如果内存地址相同,必然是同一个对象;如果内存地址不相同,必然不是同一个对象。

三、hashCode()介绍

(一)hashCode()内部实现

hashCode()的定义位于Object.class中:

public native int hashCode();

根据这个方法的声明可知,该方法返回一个int类型的数值,并且是本地方法,因此在Object类中并没有给出具体的实现。


小贴士: 1 什么是native方法 简单地讲,一个Native Method就是一个java调用非java代码的接口。一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C或C++。 这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数。 2 为什么要用native方法 (1)与java环境外交互: 有时java应用需要与java外面的环境交互。这是本地方法存在的主要原因,你可以想想java需要与一些底层系统如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解java应用之外的繁琐的细节。 (2)与操作系统交互: JVM支持着java语言本身和运行时库,它是java程序赖以生存的平台,它由一个解释器(解释字节码)和一些连接到本地代码的库组成。然而不管怎 样,它毕竟不是一个完整的系统,它经常依赖于一些底层系统的支持。这些底层系统常常是强大的操作系统。通过使用本地方法,我们得以用java实现了jre的与底层系统的交互,甚至JVM的一些部分就是用C写的,还有,如果我们要使用一些java语言本身没有提供封装的操作系统的特性时,我们也需要使用本地方法。


(二)为何使用hashCode()?

对于包含容器类型的程序设计语言来说,基本上都会涉及到hashCode。在Java中也一样,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。 为什么这么说呢?考虑一种情况,当向基于散列的集合中插入对象时,如何判别在集合中是否已经存在该对象了?(注意:集合中不允许重复的元素存在)

  也许大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了,说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。下面这段代码是java.util.HashMap的中put方法的具体实现:

public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(hash, key, value, i);
        return null;
}

  put方法是用来向HashMap中添加新的元素,从put方法的具体实现可知,会先调用hashCode方法得到该元素的hashCode值,然后查看table中是否存在该hashCode值,如果存在则调用equals方法重新确定是否存在该元素,如果存在,则更新value值,否则将新的元素添加到HashMap中。从这里可以看出,hashCode方法的存在是为了减少equals方法的调用次数,从而提高程序效率。

  有些朋友误以为默认情况下,hashCode返回的就是对象的存储地址,事实上这种看法是不全面的,确实有些JVM在实现时是直接返回对象的存储地址,但是大多时候并不是这样,只能说可能存储地址有一定关联。

四、两者的关系

(一)如果equals方法得到的结果为true,则两个对象的hashcode是否相等?

例1:重写equals()但不重写hashCode() Student.java

public class Student {
    private int age;
    private String name;
    public Student() {
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) 
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}

Test.java

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
public class Test {
    public static void main(String[] args) {
        LinkedList<Student> list = new LinkedList<Student>();
        Set<Student> set = new HashSet<Student>();
        Student stu1  = new Student(3,"Li Si");
        Student stu2  = new Student(3,"Li Si");
        System.out.println("stu1 == stu2 : "+(stu1 == stu2));
        System.out.println("hashcode of stu1: " + stu1.hashCode());
        System.out.println("hashcode of stu2: " + stu2.hashCode());
        System.out.print("stu1.hashCode()==stu2.hashCode(): ");
        System.out.println(stu1.hashCode()==stu2.hashCode());
        System.out.println("stu1.equals(stu2) : "+stu1.equals(stu2));
        list.add(stu1);
        list.add(stu2);
        System.out.println("list size:"+ list.size());
        set.add(stu1);
        set.add(stu2);
        System.out.println("set size:"+ set.size());
    }
}

运行结果:

stu1 == stu2 : false
hashcode of stu1: 1291472364
hashcode of stu2: 1158801519
stu1.hashCode()==stu2.hashCode(): false
stu1.equals(stu2) : true
list size:2
set size:2

例2:重写equals()和hashCode() Student.java

public class Student {
    private int age;
    private String name;
    public Student() {
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override  
    public int hashCode() {  
        final int prime = 31;  
        int result = 1;  
        result = prime * result + age;  
        result = prime * result + ((name == null) ? 0 : name.hashCode());  
        return result;  
    } 
    @Override
    public boolean equals(Object obj) {
        if (this == obj) 
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}

Test.java的代码与例1中的代码一致,在此不再罗列。 运行结果:

stu1 == stu2 : false
hashcode of stu1: 73350135
hashcode of stu2: 73350135
stu1.hashCode()==stu2.hashCode(): true
stu1.equals(stu2) : true
list size:2
set size:1

例3:重写equals()和hashCode() Student.java

public class Student {
    private int age;
    private String name;
    private static int index = 5;
    public Student() {
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override  
    public int hashCode() {  
        final int prime = 31;  
        int result = 1;  
        result = prime * result + age + index;
        index++;
        result = prime * result + ((name == null) ? 0 : name.hashCode());  
        return result;  
    } 
    @Override
    public boolean equals(Object obj) {
        if (this == obj) 
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Student other = (Student) obj;
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}

Test.java的代码与例1中的代码一致,在此不再罗列。 运行结果:

stu1 == stu2 : false
hashcode of stu1: 73350290
hashcode of stu2: 73350321
stu1.hashCode()==stu2.hashCode(): false
stu1.equals(stu2) : true
list size:2
set size:2

结论:从上面三个例子可以看出,如果equals方法得到的结果为true,则两个对象的hashcode可能相等,也可能不相等。

(二)如果equals方法得到的结果为false,则两个对象的hashcode值是否不相等?

例4:不重写equals()和hashCode() Student.java

public class Student {
    private int age;
    private String name;
    public Student() {
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Test.java

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
public class Test {
    public static void main(String[] args) {
        LinkedList<Student> list = new LinkedList<Student>();
        Set<Student> set = new HashSet<Student>();
        Student stu1  = new Student(3,"Zhang San");
        Student stu2  = new Student(4,"Li Si");
        System.out.println("stu1 == stu2 : "+(stu1 == stu2));
        System.out.println("hashcode of stu1: " + stu1.hashCode());
        System.out.println("hashcode of stu2: " + stu2.hashCode());
        System.out.print("stu1.hashCode()==stu2.hashCode(): ");
        System.out.println(stu1.hashCode()==stu2.hashCode());
        System.out.println("stu1.equals(stu2) : "+stu1.equals(stu2));
        list.add(stu1);
        list.add(stu2);
        System.out.println("list size:"+ list.size());
        set.add(stu1);
        set.add(stu2);
        System.out.println("set size:"+ set.size());
    }
}

运行结果:

stu1 == stu2 : false
hashcode of stu1: 595755354
hashcode of stu2: 1291472364
stu1.hashCode()==stu2.hashCode(): false
stu1.equals(stu2) : false
list size:2
set size:2

例5:重写hashCode() Test.java

public class Student {
    private int age;
    private String name;
    public Student() {
    }
    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public String getName() {
        return name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public int hashCode() {
        return 1;
    }
}

Test.java与例4中的Test.java代码一致,在此不再罗列。 运行结果:

stu1 == stu2 : false
hashcode of stu1: 1
hashcode of stu2: 1
stu1.hashCode()==stu2.hashCode(): true
stu1.equals(stu2) : false
list size:2
set size:2

结论:由例4和例5可以看出,如果equals方法得到的结果为false,则两个对象的hashcode值可能相等,也可能不相等。

(三)如果两个对象的hashcode值相等,则equals方法得到的结果未是否为true?

结论:由例2和例5可以看出,如果两个对象的hashcode值相等,则equals方法得到的结果可能为true,也可能为false。

(四)如果两个对象的hashcode值不相等,则equals方法得到的结果是否为false?

结论:由例3和例4可以看出,如果两个对象的hashcode值不相等,则equals方法得到的结果可能为true,也可能为false。

原文发布于微信公众号 - 海天一树(gh_de7b45c40e8b)

原文发表时间:2018-03-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏木子昭的博客

Javascript实现完美继承实现javascript完美继承要考虑三个方面:

? 实现javascript完美继承要考虑三个方面: 第一步: 获取父构造函数体内的属性 解决方法: 通过 Father.call(this)实现(这里的t...

43460
来自专栏xingoo, 一个梦想做发明家的程序员

【设计模式】—— 解释器模式Interpret

  模式意图   自定义某种语言后,给定一种文法标准,定义解释器,进行解析。   做过搜索的朋友们可能更了解一些,平时我们搜索所需要的词库,通常就需要用这种方...

21860
来自专栏积累沉淀

Java HashCode详解

一、为什么要有Hash算法 Java中的集合有两类,一类是List,一类是Set。List内的元素是有序的,元素可以重复。Set元素无序,但元素不可重复。要想保...

23050
来自专栏python3

python3--object类,继承与派生,super方法,钻石继承问题

{'kind': '波斯猫', 'name': '小黑', 'sex': '公'}

37510
来自专栏PhpZendo

带你入门 JavaScript ES6 (四)

使用类声明是,需要先声明类,然后才能访问,否则抛出ReferenceError。这一点不同于函数声,函数声明会提升作用域,而无需事先声明

7610
来自专栏Python小屋

Python多态原理与示例演示

所谓多态(polymorphism),是指基类的同一个方法在不同派生类对象中具有不同的表现和行为。派生类继承了基类行为和属性之后,还会增加某些特定的行为和属性,...

35780
来自专栏女程序员的日常

Effective C++学习笔记之explicit

  explicit意思为“明确的”和“清楚的”,是C++的关键词,意在阻止隐式类型的转换;

10020
来自专栏Java技术栈

equals 和 hashCode 到底有什么联系?一文告诉你!

Java的基类Object提供了一些方法,其中equals()方法用于判断两个对象是否相等,hashCode()方法用于计算对象的哈希码。equals()和ha...

11830
来自专栏ImportSource

为什么实现了equals()就必须实现hashCode()?

我们先来看下面这个简单的例子,然后运行她: class Person{ private String name; private int age; ...

39940
来自专栏个人随笔

那些年~~~我们的C#笔试内测题目

《深入.NET平台和C#编程》内部测试题-笔试试卷 一 选择题 1) 以下关于序列化和反序列化的描述错误的是( C)。 a) 序列化是将对象的状态存储到特定存储...

406110

扫码关注云+社区

领取腾讯云代金券