Java Type Check in equals(): instanceof vs getClass()



首先,我不是在问它们之间的区别。

我有一个想法,我希望有更多的大脑来验证,这似乎能够采取(大部分(使用这两种方法的优点。

简而言之,Java 中的equals()通常如下所示。

public boolean equals(Object other) {
    if (other == null) return false;
    if (this == other) return true;
    if (this and other is not of the same type) return false
    // then actual equals checking
}

问题在于">这个和其他不是同一类型">检查。

一类人更喜欢使用if (! (other instanceof MyClass)),其缺点是

  • 容易导致结果不对称。 如果 other 是 MyClass 的子类并且覆盖了equals(),则 a.equals(b)b.equals(a) 将不同
  • 为了避免这种情况,我们需要避免子类覆盖equals(),通过声明它 final。

另一类人更喜欢使用if (getClass() != other.getClass()),这有缺点

  • 违反利斯科夫替代原则。 MyClass的子类应该能够代替MyClass
  • 因此,如果aMyClassbMyClass的子类,则a.equals(b)为假,即使内部属性也是一样的。

我心中有一个想法,我希望人们验证。

这种方法应该

  1. 如果子类被覆盖equals(),则结果仍应是对称的
  2. 子类仍然可以用作其父类型(在equals()方面,当然equals不会被覆盖(

    public class Parent {
        @Override
        public boolean equals(Object other) {
            if (other == null) return false;
            if (this == other) return true;
            if (!(other instanceof Parent)) return false;
            try {
                if (other.getClass().getMethod("equals", Object.class).getDeclaringClass() != Parent.class) {
                    return other.equals(this);
                }
            } catch(Exception neverHappen){}
            // do actual checking
        }
    }
    

主要思想是,如果this遇到一个Parent对象,但该对象的equals()没有在Parent中声明(即该对象是一个子类,equals()被覆盖,我将equals调用委托给子类对象(或者可能只是返回 false(。

使用这种方法有什么缺点是我忽略的吗?(我想性能损失将是一个问题,但我相信getClass().method("equals").getDeclaringClass()的调用应该很便宜吗?

如注释中所述,如果子类确实super.equals(..),您将获得堆栈溢出。为了防止这种情况,你最终会重写每个孩子的整个父母equals(..),这甚至是最糟糕的设计。

不知何故,这取决于您首先如何/为什么实现继承。孩子应该与父母相提并论吗?比较有意义吗?(已编辑(

如果您覆盖等于,那么根据定义,您就不尊重 LSP。如果一个子项有更多的参数(这意味着它覆盖了 equals 方法(,那么它不应该等于它的父项,这就是为什么我们可以在父项中使用 getClass() != other.getClass() 进行比较。如果你想在子类中使用父类的equals()(这样你就不必重写所有内容(,你最终不会堆积溢出; equals()只是错误的,因为它们并不意味着平等。

如果一个孩子与它的父母相当怎么办?如果你尊重LSP,孩子不应该有与他的父母不同的equals()(即:平等不应该被覆盖(,我猜。所以非对称情况不应该存在。

如果一个孩子与其父母相当,但有更多的参数?现在这是您的设计,不尊重 LSP,因此由您来查看它在您的上下文中确实具有什么意义并采取相应的行动。


编辑:@Adrian是的,"对称有意义吗"措辞很差,我应该说"比较有意义吗?

例如,如果您将两个子类与 getClass() 进行比较,并且它们也使用 super with getClass()它将返回 true(测试将是 redondant,因为 this.getClass()other.getClass() 在子类和父类中将始终具有相同的值(。但正如我所说,如果你比较一个孩子和一个父母,那将是错误的(我认为如果他们有不同的参数是正常的(。

为什么只在等于 的实例上使用 final?你说了,因为可能存在不对称,而使用 getClass() 继承不可能不对称,所以在这种情况下让它最终确定是没有用的。

作为旁注,如果您使用 getClass() ,那么同一父级的多个子项将无法比较(始终返回 false (。如果你使用 instanceof ,他们可以,但如果他们这样做,任何孩子都不应该因为冒着破坏对称的风险而覆盖equals()。(我想你明白了,但我想知道什么时候选择insteanceof而不是getClass()如果它有问题(。

使用这种方法有什么缺点吗?

这行不通。要调用的方法在编译时确定。多态性仅适用于之前.之后的引用。

other.equals(this); // always calls equals(Object); as `other` is an Object.

other.getClass().getMethod("equals", /* method with no args */).getDeclaringClass();

我假设你想要other.getClass((.getMethod("equals",Parent.class(,但你必须抓住NoSuchMethodException

我相信 getClass((.method("equals"(.getDeclaringClass(( 的调用应该很便宜吗?

不在我的选择中。它比从磁盘读取某些内容更快,但您不希望经常这样做。

最后但并非最不重要的一点是,您应该确保 a.equals(b( ==> b.equals(a( 和如果 a.equals(b( 那么 a.hashCode(( == b.hashCode(( 很难做到的是ab是不同的类型。

最新更新