equals方法深入解析

简介: equals方法深入解析

写在前面

最初接触java的时候,都会有涉及equals和==的区别,最经典的案例就是用String类型的数据作类比。最常见的说法就是:equals比较的是值,==比较的是引用地址。

首先这种说法是错误的,也有人认为这种说法是不完全正确的(至少对于String这个类来说这种说法是没问题的)。

之所以说这种说法是错误的,是因为本人真的觉得这个总结实在是误人子弟。

1、equals和==

1.1、==

首先我们要先清楚java中有哪些数据类型,及他们的存储位置。


java中的数据类型:

(1)基本数据类型:整数类型(byte,short,int,long)、浮点类型(float,double)、字符型(char)、布尔型(Boolean)

(2)引用数据类型:类(class)、接口(interface)、数组

存储位置:

基础数据类型:存储在常量池中(JDK8之后去除了元方法区,改为存在堆内存中的元空间)

引用数据类型:存储于堆中。

在java中,==比较的内容也跟数据所处的位置相关:


(1)基础数据类型:比较的是值。

(2)引用数据类型:比较的是是对象的引用地址。

为什么会这样分?我们就需要来看一个例子。


示例代码:

    public class StringTest { 
    String str1 = "字符串"; 
    String str2 = "字符串";  
    }


      public class StringTest1 {  
      String str3 = new String("字符串");  
      String str4 = new String("字符串");
      }


      首先来看一上述两段代码有什么不同?一个是直接赋值,一个是给对象赋值。我们通过javac把上述两段代码编译为.class文件,再用javap方法来反编译看一下:


      StringTest对应的反编译文件

      public StringTest();
          descriptor: ()V
          flags: ACC_PUBLIC
          Code:
            stack=2, locals=1, args_size=1
               0: aload_0
               1: invokespecial #1                  // Method java/lang/Object."<init>":()V
               4: aload_0
               5: ldc           #2                  // String 字符串
               7: putfield      #3                  // Field str1:Ljava/lang/String;
              10: aload_0
              11: ldc           #2                  // String 字符串
              13: putfield      #4                  // Field str2:Ljava/lang/String;
              16: return
          LineNumberTable:
              line 1: 0
              line 3: 4
              line 4: 10




      StringTest1 对应的反编译文件

        public StringTest1();
            descriptor: ()V
            flags: ACC_PUBLIC
            Code:
              stack=4, locals=1, args_size=1
                 0: aload_0
                 1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                 4: aload_0
                 5: new           #2                  // class java/lang/String
                 8: dup
                 9: ldc           #3                  // String 字符串
                11: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
                14: putfield      #5                  // Field str3:Ljava/lang/String;
                17: aload_0
                18: new           #2                  // class java/lang/String
                21: dup
                22: ldc           #3                  // String 字符串
                24: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
                27: putfield      #6                  // Field str4:Ljava/lang/String;
                30: return
              LineNumberTable:
                line 1: 0
                line 3: 4
                line 4: 17
        }


      分析结果:

      从反编译文件可以看出,StringTest并没有新建对象,仅做了赋值。StringTest1这段代码中new了两个String对象。也就是说:

      StringTest中的str1 和str2直接指向常量池中的"字符串"这个值;

      StringTest1中的str3 和str4分别指向堆中的两个String对象,String对象再指向常量池中的值。如下图:

      结论:

      基础数据类型是加载完成后,直接赋值,指向常量池中的数据。所以==方法比较的是常量池中的值;

      引用数据加载完成之后,会在堆中创建一个对象,其对象本身指向常量池中的数据,对象不一样,结果也就不一样。所以说==比较的是引用地址。

      1.2 equals方法

      java中,equals方法存在的作用是允许程序员自己根据需要定义比较方法。也就是说由程序员决定什么情况两个对象是相等的。为什么String中的equals()方法比较的是值,这种说法是正确的呢?

      首先我们来了解一下java中所有类的祖先:Object的equals方法


      1.2.1 Object的equals方法

      Object是Java中最原始的类,在Object中,有默认的hashCode和equals方法实现,二者是相互关联的。JDK中Object.java定义了这两个方法:


      public class Object {
        ......
          public native int hashCode();
          public boolean equals(Object obj) {
              return (this == obj);
          }
          ......
      }
      

      其中hashCode方法是native方法,具体是实现是返回一个对象的内存地址作为其hashcode。equals方法则是简单的直接比较两个对象的地址。


      1.2.2 String的equals方法

      String类继承自Object类,并覆写了其equals方法,仅对值做了循环匹配,所以才会有String的equals方法是比较值这一说法。String类的源码如下:

      public boolean equals(Object anObject) {
          if (this == anObject) {
              return true;
          }
          if (anObject instanceof String) {
              String anotherString = (String)anObject;
              int n = count;
              if (n == anotherString.count) {
                  char v1[] = value;
                  char v2[] = anotherString.value;
                  int i = offset;
                  int j = anotherString.offset;
                  while (n-- != 0) {
                      if (v1[i++] != v2[j++])
                          return false;
                  }
                  return true;
              }
          }
          return false;
      }
      public int hashCode() {
              int h = hash;
              if (h == 0 && value.length > 0) {
                  char val[] = value;
                  for (int i = 0; i < value.length; i++) {
                      h = 31 * h + val[i];
                  }
                  hash = h;
              }
              return h;
          }


      看过String源码就会发现,String类不仅覆写了equals代码,还覆写了hashCode的值。这是因为java常规协定关于equals方法的协定,规定了覆写equals方法的同时,必须覆写hashCode。协定内容想看第2章总结。


      1.2.3 示例代码

      代码中Student2 这个类覆写了equals方法,但是没有覆写hashCode。

      public class EqualsTest {
        public static void main(String[] args) {
          //String类
          System.out.println("常量直接赋值=====");
          String str1 = new String("字符串");
          String str2 = new String("字符串");
            System.out.println(str2.hashCode());
            System.out.println(str1.hashCode());
            System.out.println(str1.equals(str2));
          //未重写equals方法  
          System.out.println("未重写equals方法=====");
          Student1 student1 = new Student1("name");
          Student1 student2 = new Student1("name");
            System.out.println(student2.hashCode());
            System.out.println(student1.hashCode());
            System.out.println(student1.equals(student2));
            //重写equals方法  
            System.out.println("重写equals方法=====");
          Student2 student3 = new Student2("name");
          Student2 student4 = new Student2("name");
            System.out.println(student3.hashCode());
            System.out.println(student4.hashCode());
            System.out.println(student3.equals(student4));
        }
      }
      class Student1 {
          private String name;
          public Student1(String name) {
              this.name = name;
          }
        public String getName() {
          return name;
        }
        public void setName(String name) {
          this.name = name;
        }
      }
      class Student2 {
        private String name;
          public Student2(String name) {
              this.name = name;
          }
        public String getName() {
          return name;
        }
        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;
              Student2 other = (Student2) obj;
              if (name != other.name)
                  return false;
              return true;
          }
      }
      



      执行结果

      String类=====
      23468387
      23468387
      true
      未重写equals方法=====
      366712642
      1829164700
      false
      重写equals方法=====
      2018699554
      1311053135
      true


      分析:从结果可以看到Student2 未覆写hashCode所以两个对象的hash值不一样,但因为重写了equals方法所以equals比较结果为true.


      2、总结

      (1)==针对基础数据类型比较的是值,针对引用数据类型比较的是引用地址;

      (2)equals()方法由开发人员自己定义对象的相等方式,重写equals方法需要遵循协定:

      ①两个对象相等,hashcode一定相等

      ②两个对象不等,hashcode不一定不等

      ③hashcode相等,两个对象不一定相等

      ④hashcode不等,两个对象一定不等

      目录
      相关文章
      |
      8月前
      |
      监控 安全 网络安全
      深入解析PDCERF:网络安全应急响应的六阶段方法
      PDCERF是网络安全应急响应的六阶段方法,涵盖准备、检测、抑制、根除、恢复和跟进。本文详细解析各阶段目标与操作步骤,并附图例,助读者理解与应用,提升组织应对安全事件的能力。
      1049 89
      |
      11月前
      |
      人工智能
      歌词结构的巧妙安排:写歌词的方法与技巧解析,妙笔生词AI智能写歌词软件
      歌词创作是一门艺术,关键在于巧妙的结构安排。开头需迅速吸引听众,主体部分要坚实且富有逻辑,结尾则应留下深刻印象。《妙笔生词智能写歌词软件》提供多种 AI 功能,帮助创作者找到灵感,优化歌词结构,写出打动人心的作品。
      |
      11月前
      |
      存储 算法 Java
      解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用
      在Java中,Set接口以其独特的“无重复”特性脱颖而出。本文通过解析HashSet的工作原理,揭示Set如何利用哈希算法和equals()方法确保元素唯一性,并通过示例代码展示了其“无重复”特性的具体应用。
      179 3
      |
      9月前
      |
      存储 Java 开发者
      浅析JVM方法解析、创建和链接
      上一篇文章《你知道Java类是如何被加载的吗?》分析了HotSpot是如何加载Java类的,本文再来分析下Hotspot又是如何解析、创建和链接类方法的。
      471 132
      |
      7月前
      |
      编解码 缓存 Prometheus
      「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
      本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
      427 16
      「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
      |
      9月前
      |
      安全 Ubuntu Shell
      深入解析 vsftpd 2.3.4 的笑脸漏洞及其检测方法
      本文详细解析了 vsftpd 2.3.4 版本中的“笑脸漏洞”,该漏洞允许攻击者通过特定用户名和密码触发后门,获取远程代码执行权限。文章提供了漏洞概述、影响范围及一个 Python 脚本,用于检测目标服务器是否受此漏洞影响。通过连接至目标服务器并尝试登录特定用户名,脚本能够判断服务器是否存在该漏洞,并给出相应的警告信息。
      507 84
      |
      11月前
      |
      人工智能
      写歌词的技巧和方法全解析:开启你的音乐创作之旅,妙笔生词智能写歌词软件
      怀揣音乐梦想,渴望用歌词抒发情感?掌握关键技巧,你也能踏上创作之旅。灵感来自生活点滴,主题明确,语言简洁,韵律和谐。借助“妙笔生词智能写歌词软件”,AI辅助创作,轻松写出动人歌词,实现音乐梦想。
      |
      6月前
      |
      JSON 监控 网络协议
      Bilibili直播信息流:连接方法与数据解析
      本文详细介绍了自行实现B站直播WebSocket连接的完整流程。解析了基于WebSocket的应用层协议结构,涵盖认证包构建、心跳机制维护及数据包解析步骤,为开发者定制直播数据监控提供了完整技术方案。
      |
      6月前
      |
      安全 IDE Java
      重学Java基础篇—Java Object类常用方法深度解析
      Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
      170 1
      |
      6月前
      |
      传感器 监控 Java
      Java代码结构解析:类、方法、主函数(1分钟解剖室)
      ### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
      227 5

      热门文章

      最新文章

      推荐镜像

      更多
    • DNS