Java中的比较: == 和 equals

深入解析Java中的==和equals方法:如何正确比较对象 本篇博客深入探讨了Java中的"=="和"equals"方法,介绍了它们的概念、原理、代码示例和区别,并给出了适用场景和如何覆盖equals方法的建议。可以帮助读者更好地应用Java中的==和equals方法。 阅读详情

两种比较方法

  • ==

    • 基本类型

      对于基本类型,== 的功能是比较值。

    • Object

      比较对象在内存中的地址。

  • equals

    基本类型无equals方法。Object对象默认equals的实现如下:

    /**
     * ...
     * @param   obj   the reference object with which to compare.
     * @return  {@code true} if this object is the same as the obj
     *          argument; {@code false} otherwise.
     * @see     #hashCode()
     * @see     java.util.HashMap
     */
    public boolean equals(Object obj) {
        return (this == obj);
    }

    有很长一段注释,最终的实现我们可以看到还是用的 == 来比较两个对象在内存中的地址。

equals() & hashCode()

对于equals的默认实现——比较对象在内存中的地址——有时候可能并不是我们期望的,比如有一个Student类:

class Student{
   private int id;
   private String name;

   public Student(int id){
       this.id = id;
   }

   // ... getter and setter
}

当 Student.id 相同时,我们更愿意认为这是同一个学生,而下面这个测试是无法通过的:

@Test
public void equalsStudent() throws Exception {
   Student st1 = new Student(2333);
   Student st2 = new Student(2333);
   assertEquals(st1, st2);
   // assertNotEquals(st1, st2);
}

为了达到我们的目的——相同的学号就认为是同一个人——我们可以重写Student类的 equals 方法:

class Student {
   private int id;
   private String name;

   public Student(int id) {
       this.id = id;
   }

   @Override
   public boolean equals(Object o) {
       if (null == o) {
           return false;
       }
       Student std = (Student) o;
       if (this.id == std.id) {
           return true;
       }
       return super.equals(o);
   }

   // ... getter and setter
}

修改之后上面的测试就可以通过了。Demo毕竟是简单的,当我们在实际的使用中需要重写equals方法时还是需要遵守它的生成规则,这里贴出来供参考:

public boolean equals(Object obj)

    指示其他某个对象是否与此对象“相等”。

    equals 方法在非空对象引用上实现相等关系:

        自反性:对于任何非空引用值 x,x.equals(x) 都应返回 true。
        对称性:对于任何非空引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true。
        传递性:对于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true。
        一致性:对于任何非空引用值 x 和 y,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
        对于任何非空引用值 x,x.equals(null) 都应返回 false。 

    Object 类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true(x == y 具有值 true)。

    注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

    参数:
        obj - 要与之比较的引用对象。 
    返回:
        如果此对象与 obj 参数相同,则返回 true;否则返回 false。
    另请参见:
        hashCode(), Hashtable

事情还没有结束。看下面代码:

@Test
public void studentSet() throws Exception {
   Student st1 = new Student(2333);
   Student st2 = new Student(2333);

   Set<Student> stds = new HashSet<>();
   stds.add(st1);
   stds.add(st2);
   assertEquals(1, stds.size());
}

你会发现这段代码测试不通过。set不是重复对象只会保留一份吗,为什么不是 1 呢?

这里就要介绍 hashCode() 方法:

public int hashCode()

    返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。

    hashCode 的常规协定是:

        在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
        如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
        如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。 

    实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)

    返回:
        此对象的一个哈希码值。
    另请参见:
        equals(java.lang.Object), Hashtable

当我们使用用哈希表实现的工具类时,这个方法的价值就体现了。由于之前我们没有重写这个方法,把Student对象存入HashSet时还是按照之前系统默认的方式计算他们的hash值,导致Set中存在两个Student对象。

下面做一个简单修改:

class Student {
   private int id;
   private String name;

   public Student(int id) {
       this.id = id;
   }

   @Override
   public boolean equals(Object o) {
       if (null == o) {
           return false;
       }

       // 有时候instanceof不合适的时候可以考虑用getClass()方法
       if (getClass() != o.getClass()) {
           return false;
       }

       if (o instanceof Student) {
           Student std = (Student) o;
           if (this.id == std.id) {
               return true;
           }
       }

       return super.equals(o);
   }

   @Override
   public int hashCode() {
       return this.id; 
       // return super.hashCode();
   }

   // ... getter and setter
}

再运行下面的测试,你会发现和我们期待的一致了:

@Test
public void studentSet() throws Exception {
   Student st1 = new Student(2333);
   Student st2 = new Student(2333);

   Set<Student> stds = new HashSet<>();
   stds.add(st1);
   stds.add(st2);
   assertEquals(1, stds.size());
}

Demo中直接将id作为hashcode返回不是一种好的生成方式,具体的生成规则请参考上面的注释。

Demo

再来看一个demo:

@Test
public void stringTest() throws Exception {
   String str1 = "abc";
   String str2 = "abc";
   String str3 = new String("abc");

   System.out.println(str1 == str2);
   System.out.println(str1 == str3);

   assertEquals(str1, str2);
   assertEquals(str1, str3);
}

结果会怎样?没错,测试通过,输出:true,false。

首先我们来看下String类重写的equals()和hashCOde():

@Override 
public boolean equals(Object other) {
   if (other == this) {
     return true;
   }
   if (other instanceof String) {
       String s = (String)other;
       int count = this.count;
       if (s.count != count) {
           return false;
       }
       if (hashCode() != s.hashCode()) {
           return false;
       }
       for (int i = 0; i < count; ++i) {
           if (charAt(i) != s.charAt(i)) {
               return false;
           }
       }
       return true;
   } else {
       return false;
   }
}

@Override 
public int hashCode() {
   int hash = hashCode;
   if (hash == 0) {
       if (count == 0) {
           return 0;
       }
       for (int i = 0; i < count; ++i) {
           hash = 31 * hash + charAt(i);
       }
       hashCode = hash;
   }
   return hash;
}

从上面可以看出,String类重写了这两个方法,equals()中的逻辑是比较字符串中每个字符是否相同。因此 str1, str2, str3相同就可以理解了。
对于 str1 == str2 和 str1 != str3 这涉及到不同字符串创建方法。

在开始这个例子之前,同学们需要知道JVM处理String的一些特性。Java的虚拟机在内存中开辟出一块单独的区域,用来存储字符串对象,这块内存区域被称为字符串缓冲池。当使用 String a = “abc”这样的语句进行定义一个引用的时候,首先会在字符串缓冲池中查找是否已经相同的对象,如果存在,那么就直接将这个对象的引用返回给a,如果不存在,则需要新建一个值为”abc”的对象,再将新的引用返回a。String a = new String(“abc”);这样的语句明确告诉JVM想要产生一个新的String对象,并且值为”abc”,于是就在堆内存中的某一个小角落开辟了一个新的String对象。

Java中的equals和==:比较与区别 equals方法是定义在Object类中的,默认情况下,它比较的是对象的引用。然而,对于大多数情况,我们更关心的是判断对象的内容是否相等,而非它们的引用是否相同。总结一下,equals方法是用于比较对象内容的,而==操作符是用于比较对象引用的。然而,对于基本数据类型,==可以直接比较它们的值。在编写自己的类时,我们应该重写equals方法来定义对象的相等性比较规则,以满足我们的需求。对于基本数据类型,==比较它们的值是否相等。在上面的示例中,虽然s1和s3的内容相同,但它们引用的是不同的内存。 阅读详情

相关推荐

告别密码本!用Windows NPS给华为/华三交换机做Radius认证,SSH登录更安全

本文详细介绍了如何利用Windows NPS实现华为/华三交换机的Radius认证,提升SSH登录安全性。通过集中化管理、精细化控制和完整审计追踪,企业可告别传统密码本,大幅提升网络设备管理效率。文章包含NPS服务部署指南、交换机配置详解及调试技巧,助力企业构建安全高效的统一认证体系。

weixin_42549723的博客 421

java:String使用equals和==比较的区别

"=="操作符的作用 1、用于基本数据类型的比较 2、判断引用是否指向堆内存的同一块地址。 equals所在位置: 在Object类当中,而Object是所有类的父类,包含在jdk里面,但并不适合绝大多数场景,通常需要重写 public boolean equals(Object obj) { return (this == obj); } equals的作...

越来越好ing的博客 6万+

UE5深度图与Mask图渲染实战:从基础到高级应用

本文详细解析了在UE5中渲染深度图与Mask图的完整实战流程。从启用电影渲染队列、配置自定义深度模板通道等前期准备,到构建即用型深度图材质网络与精准物体Mask生成,并深入探讨了二者结合在NPR风格化、程序化雾气及AI工作流(如ComfyUI)中的高级应用,为影视、游戏后期与AI创作提供了高效的解决方案。

weixin_29001419的博客 350

Java:String类型比较,equals和==比较的区别

String使用"=="操作符的作用 用于基本数据类型的比较 判断引用是否指向堆内存的同一块地址。 String的equals方法 equals方法在Object类当中,而Object是所有类的父类 public boolean equals(Object obj) { return (this == obj); } 而String对equals方法进行了重写。 pub...

qq_37476266的博客 9969

Robot Arm 机械臂源码解析

由于我们用的是步进电机,运动的时候是一步一步的,并不是平滑的,这里假设在一个平面上面,一个x步进电机,一个y步进电机。那么放大它们所走的轨迹就会是阶梯状的。而它们也只能沿着网格运动。相当于有了一个最小的分辨率,这个分辨率就是一步。如下图所示,如果x,y电机同时走就能走出45度的斜线。在三维空间里面运动也是如此,你应该可以想象的出来;首先,是updateActualPosition,用来时时更新此时的差补位置,得到的是一个路径与时间t的函数(其中路径的范围为0,1,时间范围为0,dist/v)...

爱吃炸鸡的小猪's blog 1万+

LABVIEW学习之MODBUS-TCP通讯

LABVIEW学习之MODBUS-TCP通讯 1,MODBUS-TCP通讯介绍: MODBUS通讯是工业上面经常用到的一种通讯协议。该协议的原理及介绍不在此累述,下面直接介绍LABVIEW对此协议的应用。 2,MODBUS通讯所需的函数: 安装了DSC模块之后可以在数据通信内找到相关函数 3 ,主VI 主要思路就是使用状态机来进行连接 读取 写入 断开。使用枚举类型来选择状态: 1,连接 2,写入多个M继电器: 3,写入单个M继电器 4,写入双字节D...

风动9527的博客 2万+

Java ==和equals的区别:是否比较地址值

首先我们要比较的是引用类型。 ==比较的是引用类型对象在内存中存放的地址。准确的说是堆内存中的地址。 而equals是基类Object自带的方法,我们写的每个类都会有这个默认的方法,你可以重写它也可以不重写。代码是: 也就是说当我们自己写的类,我们没有重写equals方法时,equals等同于==。 我们来看下一个小demo: class A{ int a=0; publ...

weixin_42036942的博客 1815

特征模理论指导滤波天线设计的优势和方法

本文探讨了特征模理论在滤波天线设计中的应用优势和方法。特征模理论通过分析天线结构的本征模特性,突破了传统经验设计的局限,实现了辐射与滤波功能的一体化设计。文章重点阐述了如何利用模式显著性(MS)和模式加权系数(MWC)来指导辐射零点的产生,揭示了辐射零点形成的物理机制。通过特征模分析,设计师可以明确调控天线结构和馈电系统,优化滤波性能。这种方法为现代多频/宽带滤波天线设计提供了理论依据和实用工具,显著提升了设计效率和性能指标。

qq_64207806的博客 957

JAVA:String使用equals和==比较的区别

&amp;quot;==&amp;quot;操作符的作用 1、用于基本数据类型的比较 2、判断引用是否指向堆内存的同一块地址。 equals所在位置: 在Object类当中,而Object是所有类的父类,包含在jdk里面,但并不适合绝大多数场景,通常需要重写 public boolean equals(Object obj) { return (this == obj); } equ...

Schon_zh的博客 134

VSCode配置C/C++开发环境:从零安装MinGW-w64并运行第一个C程序

在Windows系统上搭建C语言开发环境时,很多初学者混淆编辑器、编译器与调试器的分工,导致配置过程屡屡受挫。理解VSCode作为轻量级编辑器的定位,以及MinGW-w64提供的gcc编译器在源码编译中的核心作用,是顺利完成环境配置的关键。环境变量的正确设置直接影响gcc命令能否全局调用,而C/C++插件则赋予编辑器语法高亮与智能提示能力。通过安装VSCode、配置MinGW-w64、安装插件、汉化界面并运行一个最简单的hello.c程序,就能系统掌握这套开源工具链的完整工作流。无论是用于课程学习、算法练习

weixin_42531923的博客 535

百万 Token 压力实测:1M Context 的真实能力边界在哪里?

本文揭示了长上下文模型的性能衰减规律:1M上下文存在"容量曲线"与"质量曲线"的分歧,512K是可靠拐点。通过MRCR测试发现,不同任务类型衰减差异显著——检索任务在512K后性能下降30.6%,而生成任务仅下降5%。研究提出三阶段补偿策略:三明治注入(首尾指令强化)、语义面包屑(位置标记)、引用检查(强制溯源),可将512K-1M区间的可用性提升20%。工程实践表明,中段区域(30-70%位置)是注意力薄弱区,需要特殊处理。最终建议:关键检索任务控制在512K内,关联推理任务可扩展至

liuzhupeng的专栏 342

使用Websocket实现消息推送(上)

WebSocket一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范,WebSocketAPI被W3C定为标准。

ttdevs 3万+

详细分析 Bladex中的swagger-resources资源未授权访问的解决方法

有如下方式,网关过滤、去除配置以及修改代码还有隐藏彩蛋(附Demo),推荐阅读

码农研究僧的博客 1728
上一篇: Android闹钟遇到的那些坑
下一篇: 使用Websocket实现消息推送(心跳)
ttdevs
博客等级 码龄15年 388粉丝 91原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值