覆盖Object.equals VS重载它 [英] Overriding Object.equals VS Overloading it

查看:102
本文介绍了覆盖Object.equals VS重载它的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

阅读:有效的Java - 第二版由Joshua Bloch提供

Reading: Effective Java - Second Edition by Joshua Bloch

第8项 - 在覆盖等于状态时遵守一般合同:


程序员写一个等于
的方法看起来像这样,然后花几个小时迷惑为什么它没有这种情况并不少见t
正常工作:

It is not uncommon for a programmer to write an equals method that looks like this, and then spend hours puzzling over why it doesn't work properly:

[此处代码示例]

问题是此方法可以不重写Object.equals,其
参数的类型为Object,但重载它。

The problem is that this method does not override Object.equals, whose argument is of type Object, but overloads it instead.

代码示例:

public boolean equals(MyClass o) {
    //...
}

我的问题:

为什么强类型的equals方法像这个代码示例中的那样重载不够?该书指出,重载而不是覆盖是不好的,但它没有说明为什么会出现这种情况或者什么情况会使这种等于方法失败。

Why is a strongly typed equals method that overloads like the one in this code sample not sufficient? The book states that overloading rather than overriding is bad, but it doesn't state why this is the case or what scenarios would make this equals method fail.

推荐答案

这是因为重载方法不会改变集合或其他地方的行为,而是明确使用 equals(Object)方法。例如,请使用以下代码:

This is because overloading the method won't change the behavior in places like collections or other places that the equals(Object) method is explicitly used. For example, take the following code:

public class MyClass {

    public boolean equals(MyClass m) {
        return true;
    }
}

如果你把它放在像<$ c这样的东西$ c> HashSet :

public static void main(String[] args) {
    Set<MyClass> myClasses = new HashSet<>();
    myClasses.add(new MyClass());
    myClasses.add(new MyClass());
    System.out.println(myClasses.size());
}

这将打印 2 ,而不是 1 ,即使你希望所有 MyClass 实例与你的重载相等,并且set wouldn不要添加第二个实例。

This will print 2, not 1, even though you'd expect all MyClass instances to be equal from your overload and the set wouldn't add the second instance.

所以基本上,即使这是 true

So basically, even though this is true:

MyClass myClass = new MyClass();
new MyClass().equals(myClass);

这是 false

Object o = new MyClass();
new MyClass().equals(o);

后者是集合和其他类用于确定相等性的版本。事实上,这个将返回 true 是参数显式为 MyClass 或其中一个子类型。

And the latter is the version that collections and other classes use to determine equality. In fact, the only place this will return true is where the parameter is explicitly an instance of MyClass or one of its subtypes.

编辑:根据您的问题:

覆盖与重载

让我们从覆盖和重载之间的区别开始。通过覆盖,您实际上重新定义方法。您删除其原始实现,实际上用您自己的实现替换它。所以当你这样做时:

Let's start with the difference between overriding and overloading. With overriding, you actually redefine the method. You remove its original implementation and actually replace it with your own. So when you do:

@Override
public boolean equals(Object o) { ... }

您实际上正在重新链接新的等于实施替换 Object 中的那个(或者最后定义它的任何超类)。

You're actually re-linking your new equals implementation to replace the one from Object (or whatever superclass that last defined it).

另一方面,当你这样做:

On the other hand, when you do:

public boolean equals(MyClass m) { ... }

您正在定义一个全新的方法,因为您定义的方法具有相同的名称,但参数不同。当 HashSet 调用等于时,它会在类型为 Object

You're defining an entirely new method because you're defining a method with the same name, but different parameters. When HashSet calls equals, it calls it on a variable of the type Object:

Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

(该代码来自 HashMap.put 的源代码,用作底层实现对于 HashSet.add 。)

(That code is from the source code of HashMap.put, which is used as the underlying implementation for HashSet.add.)

要清楚,唯一一次它将使用不同的等于等于方法被重写时,不会超载。如果您尝试将 @Override 添加到重载的等于方法,它将因编译器错误而失败,抱怨它不会覆盖方法。我甚至可以在同一个类中声明等于方法,因为它正在重载:

To be clear, the only time it will use a different equals is when an equals method is overridden, not overloaded. If you try to add @Override to your overloaded equals method, it will fail with a compiler error, complaining that it doesn't override a method. I can even declare both equals methods in the same class, because it's overloading:

public class MyClass {

    @Override
    public boolean equals(Object o) {
        return false;
    }

    public boolean equals(MyClass m) {
        return true;
    }
}

泛型

对于泛型,等于 不是泛型。它明确地将 Object 作为其类型,因此这一点没有实际意义。现在,假设你试图这样做:

As for generics, equals is not generic. It explicitly takes Object as its type, so that point is moot. Now, let's say you tried to do this:

public class MyGenericClass<T> {

    public boolean equals(T t) {
        return false;
    }
}

这将无法使用以下消息编译:

This won't compile with the message:


名称冲突:MyGenericClass类型的方法equals(T)与Object类型的equals(Object)具有相同的擦除但不覆盖它

Name clash: The method equals(T) of type MyGenericClass has the same erasure as equals(Object) of type Object but does not override it

如果您尝试 @Override 它:

public class MyGenericClass<T> {

    @Override
    public boolean equals(T t) {
        return false;
    }
}

你会得到这个:


MyGenericClass类型的等于(T)的方法必须覆盖或实现超类型方法

The method equals(T) of type MyGenericClass must override or implement a supertype method

所以你不能赢。这里发生的是Java使用擦除来实现泛型。当Java在编译时完成所有泛型类型的检查时,实际的运行时对象全部被 Object 替换。你看到的任何地方 T ,实际的字节码包含 Object 。这就是为什么反射不适用于泛型类以及为什么你不能做像 list instanceof List< String> 这样的事情。

So you can't win. What's happening here is that Java implements generics using erasure. When Java finishes checking all the generic types on compile time, the actual runtime objects all get replaced with Object. Everywhere you see T, the actual bytecode contains Object instead. This is why reflection doesn't work well with generic classes and why you can't do things like list instanceof List<String>.

这也使得你不能用泛型类型重载。如果你有这个类:

This also makes it so that you can't overload with generic types. If you have this class:

public class Example<T> {
    public void add(Object o) { ... }
    public void add(T t) { ... }
}

你会从 add(T)方法中得到编译器错误,因为当这些类实际完成时编译时,这些方法都有相同的签名, public void add(Object)

You'll get compiler errors from the add(T) method because when the classes are actually done compiling, the methods would both have the same signature, public void add(Object).

这篇关于覆盖Object.equals VS重载它的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆