杰瑞科技汇

Java中equals和==到底有啥区别?

(H1):Java中equals和==的区别,一篇彻底搞懂!(附代码实例与避坑指南)

Meta描述: 深入剖析Java中equals方法和==运算符的本质区别,从内存地址到对象内容,通过详细代码示例和对比表格,让你彻底理解何时使用==,何时重写equals,并附上常见面试题与避坑指南,助你成为Java高手。

Java中equals和==到底有啥区别?-图1
(图片来源网络,侵删)

引言:你真的懂Java中的“等于”吗?

equals和到底有什么区别?”

这几乎是每一位Java初学者都会遇到,也是面试官百问不厌的经典问题,看似简单的一个问题,背后却隐藏着Java面向对象编程的核心思想:内存模型与对象比较的本质

很多开发者凭直觉回答:“比较的是地址,equals比较的是内容。” 这个答案对吗?它只说对了一半,而且在很多情况下,这个“一半”会引出更隐蔽的Bug。

我们就将拨开迷雾,从底层原理到实际应用,彻底搞懂equals和的区别,让你从此不再踩坑。

Java中equals和==到底有啥区别?-图2
(图片来源网络,侵删)

核心概念:== 运算符

在Java中,是一个关系运算符,它的作用是比较两个变量是否指向内存中的同一位置

比较基本数据类型

当用于比较基本数据类型(如 int, double, char, boolean 等)时,它比较的是这两个变量的值是否相等

int a = 10;
int b = 10;
int c = 20;
System.out.println(a == b); // 输出: true,因为a和b的值都是10
System.out.println(a == c); // 输出: false,因为a的值是10,c的值是20

小结: 对于基本数据类型,就是比较“值”是否相等。

比较引用数据类型

当用于比较引用数据类型(如所有自定义的类、StringInteger等)时,它比较的是这两个引用变量所指向的对象的内存地址是否相同,简单说,就是判断它们是不是同一个对象

Java中equals和==到底有啥区别?-图3
(图片来源网络,侵删)
// 创建两个独立的Person对象
Person p1 = new Person("张三");
Person p2 = new Person("张三");
Person p3 = p1;
System.out.println(p1 == p2); // 输出: false
// 解释:p1和p2是两个new出来的对象,它们在堆内存中分配了不同的地址空间。
System.out.println(p1 == p3); // 输出: true
// 解释:p3被赋值为p1,它们指向了同一个对象实例,内存地址相同。

小结: 对于引用数据类型,比较的是“地址”是否相等,即判断两个引用是否指向同一个对象实例。


深入解析:equals() 方法

equals()java.lang.Object类中的一个方法,这意味着Java中的所有类都继承了equals()方法

Object类中的equals()

让我们看看Object类中equals()方法的原始实现:

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

惊不惊喜,意不意外? Object类的equals()方法,其内部实现就是使用来比较的!对于任何你没有重写equals()方法的类,调用equals()和用进行比较,效果是完全一样的,都是比较对象的内存地址。

Person p1 = new Person("张三");
Person p2 = new Person("张三");
System.out.println(p1.equals(p2)); // 输出: false,因为Person类没有重写equals()

重写后的equals():约定与规范

既然Objectequals()不能满足我们“比较对象内容”的需求,Java开发者们可以在自己的类中重写(Override)equals()方法。

Java官方文档(Objectequals()方法的通用约定)为重写equals()方法定义了5个重要原则:

  1. 自反性:对于任何非null的引用值xx.equals(x)必须返回true
  2. 对称性:对于任何非null的引用值xy,如果x.equals(y)返回true,那么y.equals(x)也必须返回true
  3. 传递性:对于任何非null的引用值xyz,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)也必须返回true
  4. 一致性:对于任何非null的引用值xy,只要对象上equals比较中所用的信息没有被修改,多次调用x.equals(y) consistently返回truefalse
  5. 非空性:对于任何非null的引用值xx.equals(null)必须返回false

最佳实践:如何正确重写equals()?

一个健壮的equals()方法通常遵循以下模板(以IDE自动生成为例):

@Override
public boolean equals(Object o) {
    // 1. 检查是否是同一个对象(地址相同)
    if (this == o) return true;
    // 2. 检查参数是否为null,或者是否属于同一个类
    if (o == null || getClass() != o.getClass()) return false;
    // 3. 类型转换
    Person person = (Person) o;
    // 4. 比较类的“关键属性”
    // 使用Objects.equals可以优雅地处理null值
    return Objects.equals(name, person.name);
}

重写equals()的黄金法则: 如果你重写了equals()你必须同时重写hashCode()方法,否则,当你将对象作为键存入HashMapHashSet时,会得到意想不到的结果(后续文章会详解hashCode)。


经典案例:String 类中的 equals 和 ==

String类是Java中一个特殊的存在,它重写了equals()方法,这使得它成为理解两者区别的最佳案例。

String对象的创建方式

要理解String的行为,首先要明白它的两种创建方式:

  • 字面量赋值(使用常量池)String s1 = "hello"; JVM会先在字符串常量池中查找是否存在"hello",如果存在则直接引用,如果不存在则创建并放入池中。

  • new关键字创建(在堆上)String s2 = new String("hello"); 无论常量池中是否存在"hello",new关键字都会在堆内存中创建一个新的String对象。

代码演示

// 情况一:字面量赋值,指向常量池中的同一对象
String s1 = "hello";
String s2 = "hello";
System.out.println("s1 == s2: " + (s1 == s2));      // 输出: true (地址相同)
System.out.println("s1.equals(s2): " + s1.equals(s2)); // 输出: true (内容相同)
// 情况二:new关键字创建,堆上创建不同对象
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println("s3 == s4: " + (s3 == s4));      // 输出: false (地址不同)
System.out.println("s3.equals(s4): " + s3.equals(s4)); // 输出: true (内容相同,因为String重写了equals)
// 情况三:混合比较
System.out.println("s1 == s3: " + (s1 == s3));      // 输出: false (一个指向常量池,一个指向堆)
System.out.println("s1.equals(s3): " + s1.equals(s3)); // 输出: true (内容相同)
  • 比较String,比较的是它们是否指向同一个字符串字面量(地址)。
  • String重写的equals()方法,比较的是字符串的字符序列是否完全一样)。

一张图看懂:equals vs. == 对比表

特性 运算符 equals() 方法
本质 运算符 方法
基本类型:比较是否相等。
引用类型:比较内存地址是否相等。
默认情况下(Object类)比较内存地址
通常被重写后,用于比较对象或状态是否相等。
能否重写 不能 可以,且通常需要重写以满足业务逻辑。
使用场景 比较基本数据类型。
判断两个引用是否指向同一个对象实例。
比较两个对象在逻辑上是否“相等”。
String, Date, Integer等包装类都进行了重写。
最佳实践 重写equals()时,必须同时重写hashCode()

常见误区与避坑指南

误区1:所有类的equals()都比较内容

真相: 只有开发者主动重写了equals()方法的类,才会比较内容,对于自定义的POJO/DTO,如果你没有重写,它仍然使用Object的版本,比较的是地址。

误区2:equals() 一定比 == 慢

真相: 不一定,对于String等已经优过的类,equals()内部可能会先快速比较哈希码,如果哈希码不同则直接返回false,只有在哈希码相同时才会逐个字符比较,而是一个JVM级别的指令,速度极快,但在绝大多数业务场景下,性能差异可以忽略不计,正确性永远优先于性能

误区3:可以用 == 比较Integer的值

这是一个非常经典的坑!由于Java的自动装箱机制:

Integer a = 127;
Integer b = 127;
System.out.println(a == b); // 输出: true (在[-128, 127]缓存范围内)
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // 输出: false (超出了缓存范围,创建了新对象)

在这个场景下,比较的是Integer对象实例的地址,而不是其int值,要比较值,必须使用equals()intValue()

System.out.println(c.equals(d)); // 输出: true

总结与核心要点回顾

  1. 是运算符,equals()是方法。
  2. 基本数据类型:比较的是
  3. 引用数据类型:比较的是内存地址,即是否为同一个对象。
  4. Objectequals():内部使用,比较内存地址
  5. 重写equals():是为了实现基于/状态的逻辑比较,重写equals()时,必须遵守其5大约定,并且必须同时重写hashCode()
  6. String:是equals()重写的典范,其equals()方法比较的是字符串内容,而比较的是地址。
  7. 业务判断:当你想知道两个对象在业务逻辑上是否“等价”时(比如两个订单号相同的订单),请使用equals(),当你想知道两个变量是否指向内存中的同一个实体时,使用。

掌握equals和的区别,是迈向Java高级开发者的必经之路,希望这篇文章能帮你彻底扫清这个知识盲点。


#Java #Java基础 #equals #== #面向对象 #编程面试 #程序员 #编程教程

分享:
扫描分享到社交APP
上一篇
下一篇