Java对象的相等判定问题与equals方法解析

1. Integer

首先我们来看以下这段代码的运行结果:

java 复制代码
package com.yyj;

public class Equivalence {
    public static void main(String[] args) {
        testIntEqual(127);
        testIntEqual(128);
        /*
         * [127] Auto: true true
         * [127] valueOf: true true
         * [127] Integer: false true
         * [127] Int: true
         * [128] Auto: false true
         * [128] valueOf: false true
         * [128] Integer: false true
         * [128] Int: true
         */
    }

    private static void testIntEqual(int value) {
        // 第一种定义方式,利用了自动装箱特性,推荐该写法
        Integer a1 = value;
        Integer b1 = value;
        System.out.printf("[%d] Auto: %b %b\n", value, a1 == b1, a1.equals(b1));

        // 第二种定义方式
        Integer a2 = Integer.valueOf(value);
        Integer b2 = Integer.valueOf(value);
        System.out.printf("[%d] valueOf: %b %b\n", value, a2 == b2, a2.equals(b2));

        // 第三种定义方式,Java9之后已弃用,效率远不如valueOf
        Integer a3 = new Integer(value);
        Integer b3 = new Integer(value);
        System.out.printf("[%d] Integer: %b %b\n", value, a3 == b3, a3.equals(b3));

        // 第四种定义方式,不使用包装类,无equals方法
        int a4 = value;
        int b4 = value;
        System.out.printf("[%d] Int: %b\n", value, a4 == b4);
    }
}

对于参数值127,== 操作符只在使用 new 创建对象的方式上判定两个数为 false,这是因为操作符 ==/!= 比较的是对象的引用,虽然参与比较的两个引用包含的内容相同,但他们指向了内存中的不同对象。注意这种定义方式在 Java9 之后已经被废弃,因为其效率远不如使用 valueOf 方法。

对于参数值128,可以发现所有创建方式创建的两个相同值的 Integer 对象在 == 操作符的判定下均为 false,这是因为出于效率的原因,Integer 会通过享元模式来缓存范围在-128~127内的对象,因此多次调用 Integer.valueOf(127) 生成的其实是同一个对象,而在此范围之外的值则不会这样,即每次调用 Integer.valueOf(128) 返回的都是不同的对象。

因此在使用 Integer 的时候如果需要比较值是否相等应该只是用 equals 方法。

2. Double

我们再来看下面这段代码:

java 复制代码
package com.yyj;

public class Equivalence {
    public static void main(String[] args) {
        testDoubleEqual(0, 0);
        testDoubleEqual(0, Double.MIN_VALUE);
        testDoubleEqual(Double.MAX_VALUE, Double.MAX_VALUE - Double.MIN_VALUE * 1000000);
        /*
         * [0.000000e+00 | 0.000000e+00] double: true
         * [0.000000e+00 | 0.000000e+00] Auto: false true
         * [0.000000e+00 | 0.000000e+00] valueOf: false true
         * [0.000000e+00 | 0.000000e+00] Double: false true
         * [0.000000e+00 | 4.900000e-324] double: false
         * [0.000000e+00 | 4.900000e-324] Auto: false false
         * [0.000000e+00 | 4.900000e-324] valueOf: false false
         * [0.000000e+00 | 4.900000e-324] Double: false false
         * [1.797693e+308 | 1.797693e+308] double: true
         * [1.797693e+308 | 1.797693e+308] Auto: false true
         * [1.797693e+308 | 1.797693e+308] valueOf: false true
         * [1.797693e+308 | 1.797693e+308] Double: false true
         */
    }

    private static void testDoubleEqual(double a, double b) {
        System.out.printf("[%e | %e] double: %b\n", a, b, a == b);

        Double a1 = a;
        Double b1 = b;
        System.out.printf("[%e | %e] Auto: %b %b\n", a1, b1, a1 == b1, a1.equals(b1));

        Double a2 = Double.valueOf(a);
        Double b2 = Double.valueOf(b);
        System.out.printf("[%e | %e] valueOf: %b %b\n", a2, b2, a2 == b2, a2.equals(b2));

        Double a3 = new Double(a);
        Double b3 = new Double(b);
        System.out.printf("[%e | %e] Double: %b %b\n", a3, b3, a3 == b3, a3.equals(b3));
    }
}

理论上浮点数的比较应该是很严格的,即两个数值之间即使有极小的不同也应该不相等。

例如0和 Double.MIN_VALUE 相比较并不相等,但是 Double.MAX_VALUE 减去一百万倍的 Double.MIN_VALUE 却仍等于 Double.MIN_VALUE,这是因为当一个非常大的数值减去一个相对较小的数值时,非常大的数值并不会发生显著变化,这叫做舍入误差,误差的产生原因是因为机器不能存储足够的信息来表示一个大数值的微小变化。

3. 自定义类

现在你是不是以为在比较对象内容是否相等的情况下都一律用 equals 函数即可?但是并没有这么简单。看下面这段代码:

java 复制代码
package com.yyj;

public class Equivalence {
    public static void main(String[] args) {
        testClassEqual();
    }

    private static void testClassEqual() {
        A a1 = new A(10);
        A a2 = new A(10);
        System.out.println(a1.equals(a2));  // false

        B b1 = new B(10);
        B b2 = new B(10);
        System.out.println(b1.equals(b2));  // true
    }
}

class A {
    int val;

    A(int val) {
        this.val = val;
    }
}

class B {
    int val;

    B(int val) {
        this.val = val;
    }

    public boolean equals(Object o) {
        B tempB = (B)o;  // 将Object对象转型为B
        return this.val == tempB.val;
    }
}

我们创建了两个类 A 的对象,且值相等,但是 equals 方法返回的结果为 false,这是因为 equals 方法的默认 行为是比较引用 ,如果想比较内容必须像类 B 那样重写 equals 方法。

因此实际上其实是大多数标准库会重写 equals 方法来比较对象的内容而不是他们的引用。

相关推荐
哎呦没3 分钟前
SpringBoot框架下的资产管理自动化
java·spring boot·后端
2401_857600956 分钟前
SpringBoot框架的企业资产管理自动化
spring boot·后端·自动化
m0_571957582 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
并不会2 小时前
常见 CSS 选择器用法
前端·css·学习·html·前端开发·css选择器
龙鸣丿2 小时前
Linux基础学习笔记
linux·笔记·学习
一点媛艺3 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风3 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生4 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功4 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2344 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端