杰瑞科技汇

Java equals与==到底有啥区别?

  • :是一个运算符,用于比较两个变量引用的内存地址是否相同。
  • equals():是一个方法,用于比较两个对象内容(值)是否相同,但需要注意,它默认的行为也是比较内存地址,只有像 String, Integer 等包装类以及集合类重写了这个方法,才会比较内容。

运算符

是 Java 中的一个关系运算符,它的作用取决于它比较的两个变量的类型。

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

a) 比较基本数据类型 (Primitive Types)

当 用于比较两个基本数据类型(如 int, double, char 等)时,它会比较它们的是否相等。

示例:

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

对于基本数据类型, 比较的是“值是否相等”。

b) 比较引用数据类型 (Reference Types)

当 用于比较两个引用数据类型(如自定义的类、StringInteger 等)时,它会比较这两个引用是否指向同一个对象实例,也就是它们的内存地址是否相同。

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

示例:

class Person {
    String name;
    Person(String name) {
        this.name = name;
    }
}
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("张三");
        Person p2 = new Person("张三");
        Person p3 = p1;
        System.out.println(p1 == p2); // 输出 false
        System.out.println(p1 == p3); // 输出 true
    }
}

分析:

  • p1 == p2p1p2 是两个通过 new 关键字创建的新对象,它们在内存中占据不同的地址空间,p1 == p2 返回 false
  • p1 == p3p3 被赋值为 p1 的引用,这意味着 p3p1 现在指向了完全相同的内存地址,它们是同一个对象的两个“名字”。p1 == p3 返回 true

对于引用数据类型, 比较的是“地址是否相同”,即是否为同一个对象。


equals() 方法

equals() 是定义在 Object 类中的一个方法,这意味着所有的 Java 类(无论是 JDK 自带的还是我们自己创建的)都继承了这个方法。

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

a) Object 类中的 equals() 方法

让我们来看看 java.lang.Object 类中 equals() 方法的源码(简化版):

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

发现了吗? Object 类的 equals() 方法,其内部实现就是用 来比较的!如果你不重写 equals() 方法,那么对于任何对象,equals() 的效果和 是完全一样的,都是比较内存地址。

示例:

class Person {
    String name;
    Person(String name) {
        this.name = name;
    }
}
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("张三");
        Person p2 = new Person("张三");
        System.out.println(p1.equals(p2)); // 输出 false
    }
}

分析: 因为我们没有在 Person 类中重写 equals() 方法,p1.equals(p2) 调用的是 Object 类的 equals() 方法,它实际上执行的是 p1 == p2,结果是 false

b) 重写 equals() 方法

在实际开发中,我们更关心的是对象的内容(属性)是否相同,而不是它们是否是同一个内存实例,我们通常会重写 equals() 方法

重写 equals() 的约定(来自 Object 类的文档):

  1. 自反性x.equals(x) 必须返回 true
  2. 对称性x.equals(y) 返回 truey.equals(x) 也必须返回 true
  3. 传递性x.equals(y) 返回 truey.equals(z) 返回 truex.equals(z) 也必须返回 true
  4. 一致性:只要对象内容没有被修改,多次调用 x.equals(y) 应该一致地返回 truefalse
  5. 非空性x.equals(null) 必须返回 false

示例:重写 Person 类的 equals() 方法

class Person {
    String name;
    int age;
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 重写 equals 方法
    @Override
    public boolean equals(Object o) {
        // 1. 检查是否是同一个对象实例
        if (this == o) return true;
        // 2. 检查参数是否为 null 或是否属于不同的类
        if (o == null || getClass() != o.getClass()) return false;
        // 3. 类型转换,将 Object 转换为当前类
        Person person = (Person) o;
        // 4. 比较类的属性
        // 使用 Objects.equals 可以避免 NullPointerException
        return age == person.age && java.util.Objects.equals(name, person.name);
    }
}
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 25);
        Person p2 = new Person("张三", 25);
        Person p3 = new Person("李四", 25);
        System.out.println(p1.equals(p2)); // 输出 true,因为 name 和 age 都相同
        System.out.println(p1.equals(p3)); // 输出 false,因为 name 不同
    }
}

分析: p1.equals(p2) 会返回 true,因为我们重写了 equals() 方法,让它比较的是 nameage 这两个属性的内容,而不是内存地址。


String 类中的 equals() (一个重要的特例)

String 类是 Java 中最常用的类之一,它重写了 equals() 方法,专门用于比较字符串的内容是否相同。

示例:

String s1 = new String("hello");
String s2 = new String("hello");
String s3 = s1;
System.out.println(s1 == s2);      // 输出 false,因为 new 创建了两个不同的对象,地址不同
System.out.println(s1.equals(s2)); // 输出 true,因为 String 重写了 equals,比较的是内容 "hello"
System.out.println(s1 == s3);      // 输出 true,因为 s3 指向 s1 的地址
System.out.println(s1.equals(s3)); // 输出 true

注意: 还有一个 String 类的方法叫做 equalsIgnoreCase(),它在比较时忽略大小写。


equals()hashCode() 的关系

当你重写 equals() 方法时,必须同时重写 hashCode() 方法,这是一个非常重要的约定。

为什么? 因为很多 Java 集合类(特别是 HashMap, HashSet, Hashtable)都依赖于 hashCode() 来定位对象,这些集合的工作原理是:

  1. 计算对象的 hashCode(),确定它应该存储在哪个“桶”(bucket)里。
  2. 如果要查找一个对象,先计算它的 hashCode() 找到对应的桶。
  3. 然后在桶内部,使用 equals() 方法来精确比较,确认是否是我们要找的那个对象。

违反约定的后果: 如果你只重写了 equals() 而没有重写 hashCode(),那么两个内容相同的对象可能会被 HashMap 认为是不同的对象,导致它们被存储在不同的位置,从而破坏了集合的正确性。

最佳实践: 在重写 equals()hashCode() 时,通常使用相同的属性来计算,在上面的 Person 类中,我们可以用 nameage 来生成 hashCode

@Override
public int hashCode() {
    return Objects.hash(name, age);
}

总结表格

特性 运算符 equals() 方法
本质 运算符 方法
- 基本类型:比较
- 引用类型:比较内存地址
默认比较内存地址(继承自 Object),但可以被重写以比较
能否重写 不能 可以,且应该重写
String s1 == s2 比较地址
"a" == "a" (常量池) 可能比较地址
s1.equals(s2) 比较
与集合的关系 与集合类的内部逻辑无关 HashSet, HashMap 等依赖 equals() 来判断对象是否重复
最佳实践 - 重写 equals() 时,必须同时重写 hashCode()

希望这个详细的解释能帮助你彻底理解 和 equals() 的区别!

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