Effective Java 10.覆盖equals时请遵守通用约定

覆盖 equals 时请遵守通用约定

覆盖 equals 方法看似很简单,但是有很多覆盖方式会导致错误。equals也算是我们在开发中最常使用到的方法了。但是我们平常都很少会去重写equals方法。在这种情况下,类的每个实例都只与它本身相等。

什么时候覆盖 equals 方法?

如果类具有自己特有的 “逻辑相等”,并且超类还没有覆盖 equals,通常属于值类,这种做法在 String 类中最常见,因为

想要两个字符串对比,并不能单纯的比对两者的内存地址是否相同,而是要判断两个字符串中的内容是否完全一致。

为什么在 Java 中 String 类的 equals 方法要被重写?直接使用 Object 的 equals 方法不行吗?

答案是需要区分情况的。

1、字符串常量的情况:

String s1 = "abc";
String s2 = "abc";

对字符串常量来说,Java 会有字符串常量池,在 JVM 运行时分配的内存区域,确保该池中不会存在两个内存相同但内存地址不同的字符串对象。

对于上述这种情况,无论是否重写 equals 方法,使用 Object 的 equals 方法进行比较都不会出现问题。

2、使用 new 对象的方式来创建字符串

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

对于这种情况,使用 new 关键字创建的字符串对象,它们是会在堆中创建对象。这里只能做假设,或者自行修改 JDK 源码,去掉 String 的 equals 方法。想必使用 Object 的 equals 方法理论上是上述两个对象进行比对,返回的是 false。

这两种情况的结果就有了冲突,虽然大部分情况下,我们使用 String 都基本不会用 new 的方式去创建字符串对象。但为了普适性,JDK开发者还是做了考虑,直接重写 equals 方法,就没有那么多麻烦了。

但是,有一种值类不需要覆盖 equals 方法,书中所说的 “实例受控” 确保 “每个值至多只存在一个对象的类”,第一映像想到的就是单例模式,也包括书中所讲的枚举,对于这样的类来说,Object 的 equals 方法完全符合这种类的使用逻辑,所以不需要重写。

如何覆盖 equals 方法?

在覆盖 equals 方法时,也必须要遵守它的通用约定,下面是 Object 的规范

  • 自反性:对于任何非 null 的引用值 x,x.equals(x) 必须返回 true。讲人话就是同一个对象进行 equals 的比对,必须是一样的,返回 true
Object a = new Object();
boolean result = a.equals(a);
System.out.println(result);// result 必须是 true
  • 对称性:对于任何非 null 的引用值 x 和 y,当 y.equals(x) 返回 true,x.equals(y) 也必须返回 true,可以看下面案例
public class Person implements Cloneable {
    private Address address;


   // 省略构造函数、Getter&Setter方法
    @Override
    public Person clone() {
        try {
            Person person = (Person) super.clone();
            return person;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

public class Address implements Cloneable{
    private String name;

    // 省略构造函数、Getter&Setter方法
    @Override
    public Address clone() {
        try {
            return (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

public class Main {
    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
        Person person1 = new Person(new Address("测试"));
        // 克隆一个 person 对象
        Person person2 = person1.clone();
        Address address1 = person1.getAddress();
        Address address2 = person2.getAddress();
        System.out.println(address1.equals(address2)); // 输出 true
        System.out.println(address2.equals(address1)); // 输出 true
    }
}
  • 传递性:对于任何非 null 的引用值 x、y和z,简单说就是 x 、y 、z哪个对象对另一个对象进行 equals 方法,都必须返回 true
  • 一致性:对于任何非 null 的引用值 x 和 y,只要 equals 的比较操作在对象中所用的信息没有被修改,多次调用 x.equals(y) 就需要一致的返回 true 或者 false
  • 对于任何不是 null 的引用 x,x.equals(null) 必须返回false

最后的告诫

由于书中内容较长,只取了实用的部分。

不要将 equals 声明中的 Object 对象替换成其他的类型

public class Address implements Cloneable {
    private String name;


    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    public boolean equals(Address o) {
        return Objects.equals(this, o);
    }

    // 省略构造函数、Getter&Setter方法
    @Override
    public Address clone() {
        try {
            return (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

上面这种写法是可以编译成功并且启动也不会报错,两个 equals 方法。问题在于equals(Address o)并没有覆盖 Object 的方法,因为它的参数应该是 Object 类型,并且该方法也没有 @Override 注解,如果加上该注解方法会编译错误的。

以上是一种极端情况,在现在的开发中,已经不需要手动去编写 equals 方法。例如可以使用 Lombok 的 @EqualsAndHashCode注解,就会帮我们生成 equals方法,减少人工编写代码遇到的错误

总结

我们在开发中也基本上不会遇到需要覆盖 equals方法,除非迫不得已。在很多情况下,Object 的 equals 方法就已经可以覆盖到大部分的使用场景了。

参考文献

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYNwKrab' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片