== 和 equals() 到底谁管谁?你真的分得清吗?

举报
喵手 发表于 2025/12/08 20:21:53 2025/12/08
【摘要】 开篇语哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,...

开篇语

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛

  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

一、前言

说实话,只要是写 Java 的,几乎都经历过这样的灵魂拷问:“为啥我明明两个对象内容一样,== 比较就是 false?”、“equals() 要不要重写?”、“重写了 equals()hashCode() 要不要一起上?”

如果这些问题你心里都“差不多懂,但又说不太清”,那我们就聊一次透一点的。今天就不讲那些死板定义,我们用内存、对象、集合、代码例子一步一步把 ==equals() 讲清楚,再带你把 equals 重写规范打包带走,写代码心里有谱,不再靠猜。😎

二、为什么会同时存在 ==equals()

先丢一个直观的结论:

  • ==:比较的是“是不是同一个东西”

    • 基本类型:比较数值本身
    • 引用类型:比较是不是同一个对象(同一块内存地址)
  • equals():比较的是“看上去是不是一样”

    • 默认实现:和 == 一样
    • 但很多类(比如 String、包装类、集合)重写了 equals(),用来比较“内容逻辑相等”

你可以把它俩想成:

  • ==身份证号一样吗?(是人类编号)
  • equals()长得是不是一样?信息是不是一样?

三、== 到底干了啥?

1. 基本类型:值比较

int a = 10;
int b = 10;
System.out.println(a == b); // true

很直白,int 是基本类型,== 比的是数值本身,10 和 10 肯定相等。

2. 引用类型:内存地址比较

String s1 = new String("hello");
String s2 = new String("hello");

System.out.println(s1 == s2);      // false
System.out.println(s1.equals(s2)); // true

解释一下:

  • new String("hello") 是两块独立的对象内存
  • s1 == s2 比较的是“是不是同一个对象”,显然不是
  • equals()String 重写成“按内容比较”,所以 s1.equals(s2)true

所以结论:引用类型用 ==,你问的是“是不是同一个对象”;用 equals(),你问的是“内容是不是一样”。

四、equals() 的真面目:默认实现 VS 重写实现

1. Object 中的默认实现

java.lang.Object 里,equals() 默认就是:

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

也就是说,如果某个类没有重写 equals(),那你调用的其实和 == 一模一样,都是比地址。

2. 常见类是怎么重写 equals() 的?

比如:

  • String:按字符串内容比较
  • 包装类(IntegerLong 等):按数值比较
  • 集合类(ArrayListHashSet):按元素内容比较

来看一个典型差异示例 👇

String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");

System.out.println(s1 == s2);      // true(字符串常量池复用)
System.out.println(s1 == s3);      // false(new 出新对象)
System.out.println(s1.equals(s3)); // true(按内容比)

坑点: 有些人看到 s1 == s2true,就误以为以后比字符串都可以用 ==,那是因为 字符串常量池 在偷偷帮你省事,一旦涉及到 new 或从外部读取,就很容易翻车。


五、一个经常坑人的例子:Integer 缓存

Integer a = 100;
Integer b = 100;
Integer c = 200;
Integer d = 200;

System.out.println(a == b);      // true(在 -128 ~ 127 缓存范围内)
System.out.println(c == d);      // false(超出缓存范围)
System.out.println(c.equals(d)); // true(值相等)
  • Integer 对 -128 ~ 127 的值做了缓存,小数值可能“看起来像是同一个对象”
  • 超出范围就重新 new,因此 c == dfalse

所以:

想比数值,用 equals();想比是不是一个对象,用 ==
== 去比包装类,很容易写出“时好时坏”的诡异代码。


六、实战:哪些地方必须用 equals(),不能偷懒?

1. 比较字符串内容

String input = getUserInput(); // 来自用户输入 / 网络 / 文件...

if ("yes".equals(input)) {
    System.out.println("用户选择了 YES");
}

这里有两个关键点:

  1. equals() 比内容
  2. "yes".equals(input) 避免 inputnull 时抛 NullPointerException

对比一下这段“炸弹版”代码:

if (input.equals("yes")) { // input 为 null 时直接 NPE
    ...
}

2. 比较业务对象是否“相同”

比如你有一个 User 类,只要 id 一样,就认为是同一个用户

那就该重写 equals(),而不是用 ==


七、重头戏:equals() 的重写规范到底有哪些?

说白了,重写 equals() 不是“随便写写”的工具方法,而是一个有严格契约的东西。

1. equals() 必须遵守的 5 大原则(官方契约)

  1. 自反性(reflexive)

    • 对任何非 nullxx.equals(x) 必须是 true
  2. 对称性(symmetric)

    • 对任何 xy,如果 x.equals(y)true,那 y.equals(x) 也必须是 true
  3. 传递性(transitive)

    • 如果 x.equals(y)truey.equals(z)true,那么 x.equals(z) 也必须是 true
  4. 一致性(consistent)

    • 多次比较结果应保持一致,只要参与比较的字段没变,x.equals(y) 就不能时 true 时 false
  5. null 的约定

    • 对任何非 nullxx.equals(null) 必须是 false

2. 千万别忘:重写 equals() 必须同时重写 hashCode()

规则:只要你重写了 equals(),就必须重写 hashCode()

原因很简单:

  • HashMapHashSetHashTable 等基于哈希的集合会同时用到 hashCode()equals()

  • 逻辑上相等的两个对象(equals == true),如果 hashCode() 不一样,就会导致:

    • 同样元素加入 HashSet 却出现多个重复
    • HashMap 找不到刚刚 put 进去的 key

简单粗暴一句:

equals 决定“内容是否相等”,hashCode 决定“放在哪个桶里”。内容相等但桶不一样,集合就晕了。


八、实战:一个规范版的 equals() + hashCode()

来写一个经典的 Person 类:按 id + name 判断是否相等。

import java.util.Objects;

public class Person {
    private Long id;
    private String name;
    private int age;

    public Person(Long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        // 1. 引用相等,直接 true
        if (this == o) return true;
        // 2. null 或类型不同,直接 false
        if (o == null || getClass() != o.getClass()) return false;

        // 3. 强转类型
        Person person = (Person) o;

        // 4. 按关键字段比较
        return age == person.age &&
                Objects.equals(id, person.id) &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        // 5. 使用 Objects.hash 保证与 equals 字段一致
        return Objects.hash(id, name, age);
    }
}

这里有几个关键点:

  1. if (this == o) return true;

    • 优化性能,也符合自反性
  2. getClass() != o.getClass() vs instanceof

    • getClass()严格要求类型完全一致
    • instanceof:允许子类被认为“相等”,但设计不好容易破坏对称性、传递性
    • 一般业务实体类推荐用 getClass()
  3. 同一批字段 来实现 equals()hashCode()


九、一个错误示例:看着没事,其实很危险

看这段代码,你能找出它的问题吗?

public class User {
    private Long id;
    private String name;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User user = (User) o;
        return Objects.equals(id, user.id);
    }

    // 注意:这里没有重写 hashCode()
}

表面上:

  • equals() 的逻辑是:只要 id 相同,认为是同一个用户

但是:

  • 没有重写 hashCode()
  • 导致两个 id 相同的 Userequals() 返回 true,但 hashCode() 却不同(继承自 Object,等于内存地址)

HashSet 中就会翻车:

User u1 = new User(1L, "Tom");
User u2 = new User(1L, "Jerry");

Set<User> set = new HashSet<>();
set.add(u1);
set.add(u2);

System.out.println(set.size()); // 你以为是 1,结果是 2

这就是典型的:

equals() 说你俩一样,hashCode() 说你俩不是一路人,集合直接被玩晕。


十、数组、浮点数、集合里的 equals 怎么玩?

1. 数组不能直接用 equals() 比较内容

int[] a = {1, 2, 3};
int[] b = {1, 2, 3};

System.out.println(a.equals(b));         // false,引用比较
System.out.println(Arrays.equals(a, b)); // true,内容比较

如果你在 equals() 里有数组字段,一定要记得用:

Arrays.equals(array1, array2);

2. 浮点数比较:尽量用 Double.compare / 容差比较

double x = 0.1 + 0.2;
double y = 0.3;

System.out.println(x == y);                 // 可能为 false
System.out.println(Double.compare(x, y));   // 0 表示相等

equals() 里可以这样写:

Double.compare(this.price, other.price) == 0;

或者用误差容忍:

Math.abs(x - y) < 1e-6;

十一、实用小技巧:写 equals() 时的一套“模板动作”

以后你只要记住这套“模板流程”,基本不会翻车:

  1. 写上 @Override,不让自己拼错方法签名
  2. 第一行写:if (this == o) return true;
  3. 第二步:if (o == null || getClass() != o.getClass()) return false;
  4. 强转:MyClass other = (MyClass) o;
  5. Objects.equals() 按字段一个个比
  6. 同时重写 hashCode(),字段保持一一对应

真的懒得写的话,用 IDE(IntelliJ IDEA / Eclipse)自动生成 equals/hashCode,保证:

  • 字段勾选对了,逻辑就八九不离十

十二、最后帮你快速总结一遍(可以拿小本记了 📝)

1. 什么时候用 ==

  • 比基本类型的值
  • 判断两个引用是否指向同一个对象
  • 判断 obj == null

2. 什么时候必须用 equals()

  • 比较字符串内容
  • 比较包装类型内容(IntegerLong 等)
  • 比较自定义业务对象是否“意义上相同”
  • 在集合(ListSetMap key)里判断元素是否存在

3. 重写 equals() 要记住:

  • 遵守 5 大契约:自反、对称、传递、一致、对 null 为 false
  • 一定要同时重写 hashCode()
  • equals 和 hashCode 基于同一批字段
  • 注意数组用 Arrays.equals,浮点数用 Double.compare

4. 一句“顺口溜”帮你记:

“引用同不同时问 ==,内容像不像交给 equals()
同改 equals() 不改 hashCode(),集合分分钟整死你。”

… …

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

… …

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。


版权声明:本文由作者原创,转载请注明出处,谢谢支持!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。