详解hashCode()和equals()的本质区别和联系

在学习java,根据视频做实例的过程中,对equals和hashcode两个方法理解稍微深刻一点,主要是它们两个很容易混淆,容易出错,自己又通过网上的资料学习,和大家分享

equals()方法

equals是Object类提供的方法之一,众所周知,每一个java类都继承自Object类,所以说每一个对象都有equals这个方法。而我们在用这个方法时却一般都重写这个方法,why?

先看一个Object类中equals()方法的源代码:

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

从这个方法中可以看出,只有当一个实例等于它本身的时候,equals()才会返回true值。通俗地说,此时比较的是两个引用是否指向内存中的同一个对象,也可以称做是否实例相等。而我们在使用equals()来比较两个指向值对象的引用的时候,往往希望知道它们逻辑上是否相等,而不是它们是否指向同一个对象——这就是我们通常重写这个方法的原因。

Strings1 = new String(“kvill”),String s2 = new String(“kvill”);s1.equals(s2)为ture,

说明String类中已经重写了equals()方法,如果不重写equals()方法,那么s1.equals(s2)默认比较两个对象所指向的内存地址是否相同,返回值必然为false。

当然接下来我们要改写equals()方法,必须要遵守通用约定。来自java.lang.Object的规范,equals方法实现了等价关系,以下是要求遵循的5点:

1.自反性:对于任意的引用值x,x.equals(x)一定为true。

2.对称性:对于任意的引用值x 和 y,当x.equals(y)返回true时,y.equals(x)也一定返回true。

3.传递性:对于任意的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也一定返回true。

4. 一致性:对于任意的引用值x 和y,如果用于equals比较的对象信息没有被修改,多次调用x.equals(y)要么一致地返回true,要么一致地返回false。
5.非空性:对于任意的非空引用值x,x.equals(null)一定返回false。

hashCode()方法

hashcode()这个方法也是从object类中继承过来的,在object类中定义如下:

public native int hashCode(); 

hashCode()返回该对象的哈希码值,该值通常是一个由该对象的内部地址转换而来的整数,它的实现主要是为了提高哈希表(例如java.util.Hashtable提供的哈希表)的性能。

必须铭记:在每个重写了equals方法的类中,你必须也要重写hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而导致该类无法与所有基于散列值(hash)的集合类结合在一起正常运行。

     hashCode()的返回值和equals()的关系如下:

如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。

如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。

public class TestEquals {
  public static void main(String args[]) {
    Student s1 = new Student("张一", 6);
    Student s2 = new Student("张一", 6);
if (s1.equals(s2)) {
      System.out.println("相同 s1的代码:" + s1.hashCode() + " s2的代码:"
          + s2.hashCode());
    } else {
      System.out.println("不相同");
    }
  }
}
class Student {
  private int age;
  private String name; 

  public Student() {
  }
public Student(String name, int age) {
    this.age = age;
    this.name = name;
  }
public int getAge() {
    return age;
  }
public void setAge(int age) {
    this.age = age;
  }
public String getName() {
    return name;
  }
public void setName(String name) {
    this.name = name;
  }
public int hashCode() {
    return (this.name.hashCode() + this.age) * 31;
  }
public boolean equals(Object obj) {
    boolean result = false;
    if (obj == null) {
      result = false;
    }
    if (this == obj) {
      result = true;
    }
if (obj instanceof Student) {
      Student stu = (Student) obj;
      if (stu.getName().equals(this.name) && stu.getAge() == (this.age)) {
        result = true;
      }
} else {
      result = false;
    }
    return result;
  }
}

详细分析

equals()是判读两个Set是否相等[前提是equals()在类中被覆盖]。==决定引用值是否指向同一对象。

1、当向集合set中增加对象时,首先计算要增加对象的hashCode码,根据该值来得到一个位置来存放当前的对象,当在该位置没有一个对象存在的话,那么集合set认为该对象在集合中不存在,直接增加进去。如果在该位置有一个对象的话,接着将准备增加到集合中的对象与该位置上的对象进行equals方法比较,如果该equals方法返回false,那么集合认为集合中不存在该对象,再进行一次散列,将该对象放到散列后计算出的新地址里,如果equals方法返回true,那么集合认为集合中已经存在该对象了,不会再将该对象增加到集合中了。

2、当重写equals方法时,必须要重写hashCode方法。在java的集合中,判断两个对象是否相等的规则是:

1),判断两个对象的hashCode是否相等

如果不相等,认为两个对象也不相等,完毕 ; 如果相等,转入2

2),判断两个对象用equals运算是否相等

如果不相等,认为两个对象也不相等

如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键)

可见hashcode()相等时,equals()方法也可能不等。

public static void main(String args[]){
String s1=new String("zhaoxudong"); //此语句创建了两个对象,一个是字符串对象“zhaoxudong”(存放于栈中的字面量),另一个是new后在堆中产生的对象。详细见下面的四.4
String s2=new String("zhaoxudong");
//上述两条语句一共是产生了三个对象,因为栈中只有产生了一个对象。
System.out.println(s1==s2);//false
System.out.println(s1.equals(s2));//true
System.out.println(s1.hashCode());//s1.hashcode()等于s2.hashcode() ,指向同一内存的引用
System.out.println(s2.hashCode()); //equals和hashCode方法只用于两个对象的比较和容器中,与对象的创建没有关系
Set hashset=new HashSet();
hashset.add(s1);
hashset.add(s2); /*在添加s1,s2时, hashset认为s1和s2是相等的,所以让s2覆盖了s1;*/
Iterator it=hashset.iterator();
      while(it.hasNext()){
       System.out.println(it.next());
      } //最后在while循环的时候只打印出了一个”zhaoxudong”。

这是因为String类已经重写了equals()方法和hashcode()方法。

但是看下面的程序:

>public class HashSetTest {
  public static void main(String[] args)  {
         HashSet hs=new HashSet();
         hs.add(new Student(1,"zhangsan"));
         hs.add(new Student(2,"lisi"));
         hs.add(new Student(3,"wangwu"));
         hs.add(new Student(1,"zhangsan"));
         Iterator it=hs.iterator();
         while(it.hasNext()){
            System.out.println(it.next());
         } } }
class Student {
   int num;
   String name;
   Student(int num,String name) {
        this.num=num;
         this.name=name; }
   public String toString() { return num+":"+name; }
      } 

输出结果为:

          1:zhangsan
          1:zhangsan
          3:wangwu
          2:lisi

为什么hashset添加了相等的元素呢?

这是不是和hashset的原则违背了呢?回答:没有因为在根据hashcode()对两次建立newStudent(1,"zhangsan")对象进行比较时,生成的是不同的哈希码值,所以hashset把他当作不同的对象对待了,当然此时的equals()方法返回的值也不等。那么为什么会生成不同的哈希码值呢?原因就在于我们自己写的Student类并没有重新自己的hashcode()和equals()方法

所以在比较时,是继承的object类中的hashcode()方法,它是一个本地方法,比较的是对象的地址(引用地址),使用new方法创建对象,两次生成的当然是不同的对象了,造成的结果就是两个对象的hashcode()返回的值不一样。那么怎么解决这个问题呢?

原因是:在Student类中重新hashcode()和equals()方法。

class Student{ 

int num; 

String name; 

Student(int num,String name){ 

      this.num=num; 

      this.name=name; } 

public int hashCode(){ //重写hashCode的方法 

      return num*name.hashCode(); } 

public boolean equals(Object o) { 

      Student s=(Student)o; 

      return num==s.num && name.equals(s.name); //&&的优先级比==低,所以前面不必加括号 

} 

public String toString(){return num+":"+name; } 

}

根据重写的方法,即便两次调用了new Student(1,"zhangsan"),我们在获得对象的哈希码时,根据重写的方法hashcode(),获得的哈希码肯定是一样的。所以运行修改后的程序时,我们会看到重复元素的问题已经消除。

总结

这块知识比较容易出错,理解一定要深刻,多多的实践会对原理与定义理解的更加的深刻。有什么问题可以随时留言,小编会及时回复大家的。感谢大家对本站的支持。

(0)

相关推荐

  • java中hashCode方法与equals方法的用法总结

    首先,想要明白hashCode的作用,必须要先知道Java中的集合. 总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set. 前者集合内的元素是有序的,元素可以重复:后者元素无序,但元素不可重复. 那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢? 这就是Object.equals方法了.但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了. 也就是说,如果集合中现在已经有

  • java中重写equals和重写hashCode()

    java中重写equals和重写hashCode() 记得在刚上初一的时候,第一堂数学课学的是集合,那时候我知道了集合是不允许重复元素存在的. hashCode 方法用于散列集合的查找,equals 方法用于判断两个对象是否相等. 为什么重写了 equals 方法,还要重写 hashCode 方法? 因为如果只重写了 equals 方法,两个对象 equals 返回了true,但是如果没有重写 hashCode 方法,集合还是会插入元素.这样集合中就出现了重复元素了. 接下来详细分析,以 Has

  • java集合——Java中的equals和hashCode方法详解

    Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要重写这两个方法,今天就来介绍一些这两个方法的作用. equals()和hashCode()方法是用来在同一类中做比较用的,尤其是在容器里如set存放同一类对象时用来判断放入的对象是否重复. 这里我们首先要明白一个问题: equals()相等的两个对象,hashcode()一定相等,equals()不相等的两个对象,却并不能证明他们的hashcode()不相等.换

  • java中重写equals()方法的同时要重写hashcode()方法(详解)

    object对象中的 public boolean equals(Object obj),对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true: 注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码.如下: (1) 当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true (2) 当obj

  • Java equals 方法与hashcode 方法的深入解析

    PS:本文使用jdk1.7解析1.Object类 的equals 方法 复制代码 代码如下: /**     * Indicates whether some other object is "equal to" this one.     * <p>     * The {@code equals} method implements an equivalence relation     * on non-null object references:     * &l

  • 详解hashCode()和equals()的本质区别和联系

    在学习java,根据视频做实例的过程中,对equals和hashcode两个方法理解稍微深刻一点,主要是它们两个很容易混淆,容易出错,自己又通过网上的资料学习,和大家分享 equals()方法 equals是Object类提供的方法之一,众所周知,每一个java类都继承自Object类,所以说每一个对象都有equals这个方法.而我们在用这个方法时却一般都重写这个方法,why? 先看一个Object类中equals()方法的源代码: public boolean equals(Object ob

  • 详解 Java 中 equals 和 == 的区别

    详解 Java 中 equals 和 == 的区别 1 前言 在 Java 语言中,equals 和 == 都是用来检测两个字符串是否相等,返回值也都是布尔型(boolean),但是两者在内部比较的处理中却不尽相同,因此在需要检测两个字符串是否相等的时候,我们一定要特别的注意,选择适当的检测方式,防止造成不必要的 bug.从表面上来看,这种 bug 很像随机产生的间歇性错误. 2 区别 在需要检测两个字符串是否相等的时候,我们可以使用 equals 方法.对于表达式: s.equals(t) 如

  • Linux 下xargs命令详解及xargs与管道的区别

    为什么要用xargs,问题的来源 在工作中经常会接触到xargs命令,特别是在别人写的脚本里面也经常会遇到,但是却很容易与管道搞混淆,本篇会详细讲解到底什么是xargs命令,为什么要用xargs命令以及与管道的区别.为什么要用xargs呢,我们知道,linux命令可以从两个地方读取要处理的内容,一个是通过命令行参数,一个是标准输入.例如cat.grep就是这样的命令,举个例子: echo 'main' | cat test.cpp 这种情况下cat会输出test.cpp的内容,而不是'main'

  • java 线程详解及线程与进程的区别

    java  线程详解及线程与进程的区别 1.进程与线程 每个进程都独享一块内存空间,一个应用程序可以同时启动多个进程.比如IE浏览器,打开一个Ie浏览器就相当于启动了一个进程. 线程指进程中的一个执行流程,一个进程可以包含多个线程. 每个进程都需要操作系统为其分配独立的内存空间,而同一个进程中的多个线程共享这块空间,即共享内存等资源. 每次调用java.exe的时候,操作系统都会启动一个Java虚拟机进程,当启动Java虚拟机进程时候,Java虚拟机都会创建一个主线程,该线程会从程序入口main

  • 详解Python中@staticmethod和@classmethod区别及使用示例代码

    本文主要介绍Python中,class(类)的装饰器@staticmethod和@classmethod的使用示例代码和它们的区别. 1.@staticmethod和@classmethod区别 @staticmethod:静态方法 @classmethod:类方法 一般来说,要使用某个类的方法,需要先实例化一个对象再调用方法. 而使用@staticmethod或@classmethod,就可以不需要实例化,直接通过类名就可以实现调用 使用:直接类名.方法名()来调用.@staticmethod

  • java  线程详解及线程与进程的区别

    java  线程详解及线程与进程的区别 1.进程与线程 每个进程都独享一块内存空间,一个应用程序可以同时启动多个进程.比如IE浏览器,打开一个Ie浏览器就相当于启动了一个进程. 线程指进程中的一个执行流程,一个进程可以包含多个线程. 每个进程都需要操作系统为其分配独立的内存空间,而同一个进程中的多个线程共享这块空间,即共享内存等资源. 每次调用java.exe的时候,操作系统都会启动一个Java虚拟机进程,当启动Java虚拟机进程时候,Java虚拟机都会创建一个主线程,该线程会从程序入口main

  • 详解JS中? ?和?. 和||的区别

    目录 1.?? 与 || 的区别 2.?? 和 ?. 的区别 1.?? 与 || 的区别 1)相同点: ?? 和 || 的用法相同,都是前后是值,中间用符号连接,根据前面的值来判断最终是返回前面的值还是后面的值. One ?? Two One || Two 2)不同点: 判断的方法不同: 使用 ?? 时,只有One为 null 或者 undefined 时才会返回 two; 使用 || 时,One会先转化为布尔值判断,为true时返回One , false 返回Two  // ??   unde

  • 详解Java中Thread 和Runnable区别

    Thread 和Runnable 关系 Thread类是接口Runnable的一个实现类. public class Thread implements Runnable 源码分析 Thread Threa类运行的时候调用start()方法,源代码如下: 调用start()方法,实际运行的是start0方法,方法声明如下: private native void start0() native表明这个方法是个原生函数,即这个函数是用C/C++实现的,被编译成DLL,由Java调用. native

  • 详解Python中is和==的区别

    在Python中一切都是对象. Python中对象包含的三个基本要素,分别是: id(身份标识) type(数据类型) value(值) 对象之间比较是否相等可以用 == ,也可以用 is . is 和 == 都是对对象进行比较判断作用的,但对对象比较判断的内容并不相同.下面来看看具体区别在哪? is 比较的是两个对象的id值是否相等,也就是比较两个对象是否为同一个实例对象,是否指向同一个内存地址. == 比较的是两个对象的内容是否相等,默认会调用对象的 __eq__ 方法. == 是pytho

  • 图文分析详解计算机网络TCP与UDP两者区别及原理

    目录 1.概念 2.从是否面向连接来看 2.1.UDP 2.2.TCP 3.从连接对象个数来看 3.1.UDP 3.2.TCP 4.从对应用报文的处理来看 4.1.UDP 4.2.TCP 发送方: 接收方: 5.从向其上层提供传输服务来看 5.1.UDP 5.2.TCP 6.从首部格式来看 6.1.UDP 6.2.TCP 7.总结 1.概念 UDP和TCP 在使用TCP/IP 体系结构的网络通信中,这两个协议的使用频率仅次于网际层的IP协议.TCP/IP 体系结构应用层中的某些协议需要使用运输层

随机推荐