Java 中 == 与 equals() 详解

【Java】 ==equals()

文章目录

  • 【Java】 `==` 与 `equals()`
    • 为什么要关注 `==` 和 `equals()`?
    • `==`和`equals`的区别
      • `==`运算符
      • `equals()`方法
      • 关键区别
    • 字符串比较的特殊性
    • 自动装箱与缓存
    • 自定义类中如何正确重写 `equals()`
    • 数组比较是个坑!
    • 编程建议
      • 1. 基本类型 vs 对象类型
      • 2. 字符串比较
      • 3. 自定义类
      • 4. 数组比较
      • 5. 空指针防御
      • 6. 包装类比较
    • 总结

在 Java 编程中,“两个变量相等”这件事并不像表面看起来那么简单。 ==equals() 的行为常常令人迷惑,尤其是在处理字符串、集合、包装类以及自定义对象时。

本文将深入剖析两者的本质区别、使用场景和最佳实践,并带你避开那些常见的坑。


为什么要关注 ==equals()

看下面的例子:

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

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

两个内容一样的字符串,用 == 得到的是 false,但用 equals() 却是 true。为什么?

再比如:

Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true(意外?)

Integer x = 200;
Integer y = 200;
System.out.println(x == y); // false(更意外?)

这类问题在日常写代码中非常常见,理解清楚这两者的机制至关重要。

==equals的区别

==运算符

== 对于基本数据类型和引用类型的作用效果是不同的:

  • 基本数据类型int, char, boolean 等):比较它们的是否相等。
  • 引用类型(如数组、 String, 自定义类等):比较两个对象的内存地址(即是否为同一个对象)。

由于 Java 只有值传递,所以对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

equals()方法

equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。

Object类中的equals方法:

public boolean equals(Object obj) {
     return (this == obj);
}
  • 默认行为(未重写时):Object 类的 equals() 方法内部使用 ==,比较对象的内存地址。
  • 重写后:用于比较对象的内容是否逻辑相等。例如 StringInteger 等类已重写 equals() 方法。

关键区别

特性 == equals()
比较对象 基本类型值或对象的内存地址 对象的内容(需重写方法)
默认行为 比较地址 Object 类默认比较地址
可自定义 不可变 可重写以定义逻辑相等

字符串比较的特殊性

  • 直接赋值的字符串(如 String s = "abc")会进入字符串常量池,相同内容的字符串指向同一对象。
  • new String("abc") 会创建新对象,即使内容相同,== 也会返回 false

自动装箱与缓存

详见深入理解Java包装类:自动装箱拆箱与缓存池机制

  • IntegerLong 等包装类在 -128127 范围内会缓存对象,此时 == 可能返回 true

    Integer a = 127;
    Integer b = 127;
    System.out.println(a == b); // true(缓存范围内)
    
    Integer c = 128;
    Integer d = 128;
    System.out.println(c == d); // false(超出缓存范围)
    

自定义类中如何正确重写 equals()

  • 如果重写 equals(),必须同时重写 hashCode(),确保两个相等的对象返回相同的哈希码。
  • 否则在使用 HashMapHashSet 等哈希表时会出现逻辑错误。

关于hashCode()的详细内容可以查看:hashCode() 有什么用?

数组比较是个坑!

在 Java 中,数组是一种特殊的对象类型,它用于存储固定数量同类型数据。它继承自 Object 类,且没有重写equals(),因此:==比较两个数组的内存地址(是否为同一个对象),equals()使用的是Object中的默认实现,效果等同于==

内容正确比较方式

使用 Arrays.equals() 方法(一维数组)或 Arrays.deepEquals()(多维数组)

import java.util.Arrays;

int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(Arrays.equals(a, b)); // true(内容相同)

int[][] arr1 = {{1, 2}, {3, 4}};
int[][] arr2 = {{1, 2}, {3, 4}};
System.out.println(Arrays.deepEquals(arr1, arr2)); // true

编程建议

1. 基本类型 vs 对象类型

  • 基本类型:直接使用 == 比较值。

    int a = 10, b = 10;
    System.out.println(a == b); // true
    
  • 对象类型

    • 判断是否为同一对象:用 ==
    • 判断内容是否相等:用 equals()(需确保已正确重写)。

2. 字符串比较

  • 优先使用 equals():避免依赖字符串常量池。

    String s1 = new String("hello");
    String s2 = "hello";
    System.out.println(s1.equals(s2)); // true
    
  • 空安全比较:将常量放在 equals() 左侧。

    String str = null;
    System.out.println("hello".equals(str)); // false(不会抛异常)
    

3. 自定义类

  • 必须重写 equals()hashCode()

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MyClass obj = (MyClass) o;
        return Objects.equals(field1, obj.field1) && 
               Objects.equals(field2, obj.field2);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(field1, field2);
    }
    

4. 数组比较

  • 使用 Arrays.equals()Arrays.deepEquals()

    int[] arr1 = {1, 2, 3};
    int[] arr2 = {1, 2, 3};
    System.out.println(Arrays.equals(arr1, arr2)); // true
    

5. 空指针防御

  • 使用 Objects.equals()

    String s1 = null;
    String s2 = "abc";
    System.out.println(Objects.equals(s1, s2)); // false
    

6. 包装类比较

  • 直接使用 equals():避免依赖缓存机制。

    Integer a = 200, b = 200;
    System.out.println(a.equals(b)); // true
    

总结

  • ==:判断“物理相等”(基本类型值或对象地址)。
  • equals():判断“逻辑相等”(对象内容,需重写方法)。
  • 始终根据需求选择正确的比较方式,并在自定义类中正确实现 equals()hashCode()

你可能感兴趣的:(java,学习,笔记)