== 和 equals() 的区别

基本数据类型只能使用 **== 不能使用equals,**用来比较值是否相同。引用数据类型时,==比较的是内从地址,如果该引用类型的类没有重写equals时候,比较的也是内存地址,只有当引用类型的类重写equals和hasecode方法之后,使用equals时会根据重写的equals进行比较,如String、Integer类的时候就是比较其中的值是否相同。

<font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">==</font>永远比较内存地址, <font style="color:rgb(15, 17, 21);background-color:rgb(235, 238, 242);">equals()</font>默认也如此,但可被重写为比较内容。

一、核心区别总览

特性 == 操作符 equals() 方法
本质 比较运算符(操作符) 对象方法(可被重写)
比较内容 内存地址(引用相等) 对象内容(逻辑相等,可自定义)
默认行为 总是比较内存地址 Object类默认比较内存地址(同==),但可被重写
适用类型 基本类型引用类型 只能用于引用类型
性能 极快(直接比较地址) 可能较慢(需要字段比较)
null安全 null == null 返回 true obj.equals(null) 应返回 false
对称性 永远对称(a == bb == a 取决于实现,应保证对称

二、详细对比分析

1. 基本类型(Primitive Types)的对比

java 复制代码
public class PrimitiveComparison {
    public static void main(String[] args) {
        int a = 100;
        int b = 100;
        int c = 200;
        
        // 对于基本类型,== 比较的是值
        System.out.println("=== 基本类型比较(只能用 ==) ===");
        System.out.println("a == b: " + (a == b));      // true(值相同)
        System.out.println("a == c: " + (a == c));      // false(值不同)
        
        // 基本类型没有equals()方法!
        // System.out.println(a.equals(b));  // 编译错误!
        
        // char类型
        char ch1 = 'A';
        char ch2 = 'A';
        char ch3 = 'B';
        System.out.println("ch1 == ch2: " + (ch1 == ch2));  // true
        System.out.println("ch1 == ch3: " + (ch1 == ch3));  // false
        
        // 浮点数比较(注意精度)
        double d1 = 0.1 + 0.2;
        double d2 = 0.3;
        System.out.println("\n浮点数精度问题:");
        System.out.println("0.1 + 0.2 == 0.3: " + (d1 == d2));  // false!
        System.out.println("实际值:");
        System.out.println("0.1 + 0.2 = " + d1);
        System.out.println("0.3 = " + d2);
        
        // 正确比较浮点数的方法
        double epsilon = 0.0000001;
        System.out.println("容差比较: " + (Math.abs(d1 - d2) < epsilon));  // true
    }
}

2. 引用类型(Reference Types)的对比

java 复制代码
public class ReferenceComparison {
    public static void main(String[] args) {
        System.out.println("=== 引用类型比较 ===");
        
        // 创建两个不同的对象,内容相同
        Object obj1 = new Object();
        Object obj2 = new Object();
        Object obj3 = obj1;  // obj3指向obj1的同一对象
        
        System.out.println("\n1. 使用 == 比较(内存地址):");
        System.out.println("obj1 == obj2: " + (obj1 == obj2));  // false(不同对象)
        System.out.println("obj1 == obj3: " + (obj1 == obj3));  // true(同一对象)
        
        System.out.println("\n2. 使用equals()比较(Object类默认实现):");
        System.out.println("obj1.equals(obj2): " + obj1.equals(obj2));  // false
        System.out.println("obj1.equals(obj3): " + obj1.equals(obj3));  // true
        
        // 结论:对于未重写equals()的类,== 和 equals() 效果相同!
    }
}

3. String类的特殊案例(最经典的例子)

java 复制代码
public class StringComparison {
    public static void main(String[] args) {
        System.out.println("=== String类比较(String重写了equals()) ===");
        
        // 情况1:字符串字面量(享元模式)
        String s1 = "hello";
        String s2 = "hello";
        
        System.out.println("\n1. 字符串字面量:");
        System.out.println("s1 == s2: " + (s1 == s2));          // true(字符串常量池)
        System.out.println("s1.equals(s2): " + s1.equals(s2));  // true
        
        // 情况2:new String()创建
        String s3 = new String("hello");
        String s4 = new String("hello");
        
        System.out.println("\n2. new String()创建:");
        System.out.println("s3 == s4: " + (s3 == s4));          // false(不同对象)
        System.out.println("s3.equals(s4): " + s3.equals(s4));  // true(内容相同)
        
        // 情况3:混合比较
        System.out.println("\n3. 字面量和new String()比较:");
        System.out.println("s1 == s3: " + (s1 == s3));          // false
        System.out.println("s1.equals(s3): " + s1.equals(s3));  // true
        
        // 情况4:intern()方法
        String s5 = s3.intern();
        System.out.println("\n4. 使用intern()方法:");
        System.out.println("s1 == s5: " + (s1 == s5));          // true(都指向常量池)
    }
}

4. 包装类(Wrapper Classes)的对比

java 复制代码
public class WrapperComparison {
    public static void main(String[] args) {
        System.out.println("=== 包装类比较(Integer缓存机制) ===");
        
        // 情况1:自动装箱(缓存范围内)
        Integer a = 127;
        Integer b = 127;
        
        System.out.println("\n1. 缓存范围内(-128~127):");
        System.out.println("a == b: " + (a == b));          // true(缓存同一对象)
        System.out.println("a.equals(b): " + a.equals(b));  // true
        
        // 情况2:自动装箱(缓存范围外)
        Integer c = 128;
        Integer d = 128;
        
        System.out.println("\n2. 缓存范围外(128陷阱):");
        System.out.println("c == d: " + (c == d));          // false(不同对象)
        System.out.println("c.equals(d): " + c.equals(d));  // true
        
        // 情况3:new创建(总是新对象)
        Integer e = new Integer(100);
        Integer f = new Integer(100);
        
        System.out.println("\n3. new Integer()创建:");
        System.out.println("e == f: " + (e == f));          // false(不同对象)
        System.out.println("e.equals(f): " + e.equals(f));  // true
        
        // 情况4:与基本类型比较(自动拆箱)
        int g = 100;
        Integer h = 100;
        
        System.out.println("\n4. 包装类与基本类型比较:");
        System.out.println("g == h: " + (g == h));          // true(自动拆箱)
        System.out.println("h.equals(g): " + h.equals(g));  // true(自动装箱比较)
        
        // 注意:基本类型不能调用equals()
        // System.out.println(g.equals(h));  // 编译错误!
    }
}

三、equals()方法的重写机制

1. 为什么需要重写equals()?

java 复制代码
class Person {
    String name;
    int age;
    
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 未重写equals(),继承Object的默认实现(比较地址)
}

public class WhyOverrideEquals {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 20);
        Person p2 = new Person("张三", 20);
        
        System.out.println("未重写equals()的问题:");
        System.out.println("p1 == p2: " + (p1 == p2));          // false(合理)
        System.out.println("p1.equals(p2): " + p1.equals(p2));  // false(不合理!)
        
        System.out.println("\n我们期望:");
        System.out.println("两个name和age相同的Person应该是相等的!");
    }
}

2. 正确重写equals()的方法

java 复制代码
import java.util.Objects;

class Student {
    private String studentId;
    private String name;
    private int score;
    
    public Student(String studentId, String name, int score) {
        this.studentId = studentId;
        this.name = name;
        this.score = score;
    }
    
    // 正确重写equals()的步骤:
    @Override
    public boolean equals(Object obj) {
        // 1. 检查是否是同一个对象
        if (this == obj) return true;
        
        // 2. 检查是否为null或类型不匹配
        if (obj == null || getClass() != obj.getClass()) return false;
        
        // 3. 类型转换
        Student other = (Student) obj;
        
        // 4. 逐个比较重要字段
        // 使用Objects.equals()安全处理null
        return Objects.equals(studentId, other.studentId) &&
               Objects.equals(name, other.name) &&
               score == other.score;
    }
    
    // 必须同时重写hashCode()!
    @Override
    public int hashCode() {
        return Objects.hash(studentId, name, score);
    }
}

public class CorrectEqualsExample {
    public static void main(String[] args) {
        Student s1 = new Student("1001", "李四", 90);
        Student s2 = new Student("1001", "李四", 90);
        Student s3 = new Student("1002", "王五", 85);
        
        System.out.println("重写equals()后:");
        System.out.println("s1.equals(s2): " + s1.equals(s2));  // true
        System.out.println("s1.equals(s3): " + s1.equals(s3));  // false
        
        // 验证hashCode一致性
        System.out.println("\nhashCode验证:");
        System.out.println("s1.hashCode() == s2.hashCode(): " + 
                          (s1.hashCode() == s2.hashCode()));  // true
        
        // 在集合中使用
        java.util.HashSet<Student> set = new java.util.HashSet<>();
        set.add(s1);
        set.add(s2);  // 不会被添加(equals()返回true)
        
        System.out.println("HashSet大小: " + set.size());  // 1
    }
}

四、equals()方法的契约(必须遵守!)

java 复制代码
/*
 * equals()方法必须满足的五大特性:
 * 
 * 1. 自反性 (Reflexive):   x.equals(x) 必须返回 true
 * 2. 对称性 (Symmetric):   如果 x.equals(y) 返回 true,则 y.equals(x) 也必须返回 true
 * 3. 传递性 (Transitive):  如果 x.equals(y) 返回 true,y.equals(z) 返回 true,则 x.equals(z) 也必须返回 true
 * 4. 一致性 (Consistent):  多次调用 equals(),只要对象没修改,结果应该一致
 * 5. 非空性 (Non-null):    x.equals(null) 必须返回 false
 */

public class EqualsContract {
    
    static class BadEquals {
        private String value;
        
        // 错误示例:违反对称性
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof String) {  // 错误!允许与String比较
                return value.equals(obj);
            }
            if (obj instanceof BadEquals) {
                return value.equals(((BadEquals) obj).value);
            }
            return false;
        }
    }
    
    public static void main(String[] args) {
        BadEquals bad = new BadEquals();
        bad.value = "test";
        String str = "test";
        
        System.out.println("违反对称性的例子:");
        System.out.println("bad.equals(str): " + bad.equals(str));    // true
        System.out.println("str.equals(bad): " + str.equals(bad));    // false!
        System.out.println("这违反了对称性!");
    }
}

五、实际开发中的最佳实践

1. 选择比较方式的决策树

plain 复制代码
要比较两个变量吗?
    ├── 是基本类型吗?
    │    ├── 是 → 使用 ==
    │    └── 否 → 是引用类型
    │
    └── 是引用类型吗?
         ├── 要比较对象引用吗?
         │    ├── 是 → 使用 ==
         │    └── 否 → 要比较对象内容
         │
         └── 要比较对象内容吗?
              ├── 是 → 使用 equals()
              └── 注意:确保类正确重写了equals()

2. 各种场景的推荐做法

java 复制代码
public class BestPractices {
    public static void main(String[] args) {
        System.out.println("=== 各种场景的最佳实践 ===");
        
        // 场景1:字符串比较
        String input = getUserInput();
        // ❌ 错误做法
        if (input == "admin") { ... }
        // ✅ 正确做法
        if ("admin".equals(input)) { ... }  // 还能避免NPE!
        
        // 场景2:枚举比较
        enum Status { ACTIVE, INACTIVE }
        Status status = getStatus();
        // ✅ 枚举可以用 ==(枚举值保证单例)
        if (status == Status.ACTIVE) { ... }
        // 也可以用 equals()
        if (Status.ACTIVE.equals(status)) { ... }
        
        // 场景3:BigDecimal比较(财务计算)
        java.math.BigDecimal price1 = new java.math.BigDecimal("10.00");
        java.math.BigDecimal price2 = new java.math.BigDecimal("10.0");
        // ❌ 错误做法
        System.out.println("price1 == price2: " + (price1 == price2));  // false
        // ✅ 正确做法
        System.out.println("price1.equals(price2): " + price1.equals(price2));  // false(精度不同)
        System.out.println("price1.compareTo(price2): " + price1.compareTo(price2));  // 0(值相同)
        
        // 场景4:集合中的对象比较
        java.util.List<String> list1 = java.util.Arrays.asList("A", "B");
        java.util.List<String> list2 = java.util.Arrays.asList("A", "B");
        System.out.println("\n集合比较:");
        System.out.println("list1 == list2: " + (list1 == list2));      // false
        System.out.println("list1.equals(list2): " + list1.equals(list2));  // true
        
        // 场景5:数组比较
        int[] arr1 = {1, 2, 3};
        int[] arr2 = {1, 2, 3};
        System.out.println("\n数组比较:");
        System.out.println("arr1 == arr2: " + (arr1 == arr2));      // false
        System.out.println("arr1.equals(arr2): " + arr1.equals(arr2));  // false!
        System.out.println("Arrays.equals(arr1, arr2): " + java.util.Arrays.equals(arr1, arr2));  // true
    }
    
    static String getUserInput() { return "admin"; }
    static Status getStatus() { return Status.ACTIVE; }
    enum Status { ACTIVE, INACTIVE }
}
java 复制代码
public class DefensiveProgramming {
    
    // 方法1:使用Objects.equals()处理null
    public static boolean safeCompare(Object a, Object b) {
        return java.util.Objects.equals(a, b);
    }
    
    // 方法2:常量在前避免NPE
    public static boolean checkPassword(String input) {
        // ✅ 好的做法:常量在前
        return "secret".equals(input);
        
        // ❌ 不好的做法:可能NPE
        // return input.equals("secret");
    }
    
    // 方法3:比较前检查类型
    public static boolean compareSafely(Object obj1, Object obj2) {
        if (obj1 == obj2) return true;               // 包括都为null的情况
        if (obj1 == null || obj2 == null) return false; // 一个为null
        return obj1.equals(obj2);
    }
    
    public static void main(String[] args) {
        String str1 = null;
        String str2 = "hello";
        
        System.out.println("安全比较示例:");
        System.out.println("safeCompare(null, null): " + safeCompare(null, null));      // true
        System.out.println("safeCompare(str1, str2): " + safeCompare(str1, str2));      // false
        System.out.println("safeCompare(\"hello\", \"hello\"): " + safeCompare("hello", "hello"));  // true
        
        System.out.println("\n避免NPE:");
        System.out.println("checkPassword(null): " + checkPassword(null));  // false(不会NPE)
    }
}

六、常见面试问题

Q1:==equals() 的根本区别是什么?

A== 是比较内存地址(物理相等),equals() 是比较对象内容(逻辑相等,可自定义)。

Q2:String的 ==equals() 有什么区别?

A :String的 == 比较对象引用,equals() 比较字符串内容。字符串字面量可能共享同一个对象。

Q3:为什么重写equals()必须同时重写hashCode()?

A:这是Java的契约。如果两个对象equals()返回true,它们的hashCode()必须相同,否则在使用HashMap、HashSet等集合时会出现问题。

Q4:Integer的 == 比较有什么特殊之处?

A :Integer有缓存机制(-128~127),这个范围内的值用 == 比较可能返回true(同一对象),范围外返回false。

Q5:如何正确比较浮点数?

A :不要用 == 直接比较浮点数(精度问题),应该使用容差比较或BigDecimal。


七、总结表格

比较维度 == equals()
类型 操作符 方法
基本类型 比较值 不可用
引用类型默认 比较内存地址 比较内存地址(同==)
引用类型重写后 仍比较内存地址 比较自定义内容
String 比较是否同一对象 比较字符串内容
包装类(缓存内) 可能true(同一对象) 比较值
包装类(缓存外) false(不同对象) 比较值
null处理 null == null为true obj.equals(null)应返回false
性能 O(1) 可能O(n)
集合中使用 不适合 必须正确实现
最佳实践 基本类型、枚举比较 引用类型内容比较

黄金法则:

  1. 基本类型 → 用 ==
  2. 引用类型比较引用 → 用 ==
  3. 引用类型比较内容 → 用 equals()(确保类正确重写)
  4. String比较 → 永远用 equals()(除非明确要比较引用)
  5. 包装类比较 → 永远用 equals()(避免缓存陷阱)
  6. 浮点数比较 → 不要用 <font style="color:#DF2A3F;">==</font>,用容差或BigDecimal
  7. 重写equals() → 必须同时重写hashCode()
相关推荐
好奇的菜鸟2 小时前
Windows 环境下使用 Docker 部署 Java 开发中间件完全指南
java·windows·docker
liwulin05062 小时前
【PYTHON】视频转图片
开发语言·python·音视频
Shirley~~2 小时前
PPTist 画布工具栏
开发语言·前端·javascript
koping_wu2 小时前
【leetcode】排序数组:快速排序、堆排序、归并排序
java·算法·leetcode
chen_2272 小时前
qt加ffmpeg制作简易录屏工具
开发语言·qt·ffmpeg
咘噜biu2 小时前
Java SpringBoot后端Filter包装请求(新增/覆盖请求头)
java·spring boot·filter·requestwrapper
历程里程碑2 小时前
LeetCode 283:原地移动零的优雅解法
java·c语言·开发语言·数据结构·c++·算法·leetcode
卜锦元2 小时前
Golang后端性能优化手册(第一章:数据库性能优化)
大数据·开发语言·数据库·人工智能·后端·性能优化·golang
程序猿零零漆2 小时前
Spring之旅 - 记录学习 Spring 框架的过程和经验(五)Spring的后处理器BeanFactoryPostProcessor
java·学习·spring