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

时间:2016-09-29 09:16:50

标签: java equals

首先,我要求他们之间的区别。

我有一个想法,我想更多的大脑来验证,这似乎能够(大多数)采用这两种方法的优点。

简而言之,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(),声明最终

其他人喜欢使用if (getClass() != other.getClass()),但缺点是

  • 违反Liskov替代原则。 MyClass的子类应该可以代替MyClass使用。
  • 因此,如果a.equals(b)属于aMyClass属于b的子类,则MyClass将为false,即使内部属性只是相同。

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

这种方法应该

  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
        }
    }
    
  3. 主要的想法是,如果this遇到Parent个对象,但该对象的equals()未在Parent中声明(即该对象是子类,重写equals(),我将equals调用委托给子类对象(或者可能只返回false)。

    使用这种方法是否有任何我忽略的缺点? (我想性能损失将是一个问题,但我相信getClass().method("equals").getDeclaringClass()的呼叫应该相当便宜?)

2 个答案:

答案 0 :(得分:1)

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

不知何故,它取决于您首先实现继承的方式/原因。孩子应该与父母相提并论吗?比较法有意义吗? (已编辑)

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

如果孩子与其父母相当怎么办?如果您尊重LSP,那么孩子不应该有与父母不同的equals()(即:等于不应该被覆盖),我猜。所以不对称的情况不应该存在。

如果孩子与其父母相比,但有更多参数?现在这是您的设计,不尊重LSP,因此您可以自行查看它在您的上下文中的含义并采取相应的行动。

编辑:@Adrian yes," symmmetry是否有意义"措辞很差,我应该说"比较是否有意义?"。

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

为什么只在equals上使用final作为实例?你说的是,因为可能的不对称,而使用other.getClass()不可能对继承进行不对称,所以在这种情况下使它成为最终是没有用的。

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

答案 1 :(得分:0)

  

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

它不会起作用。调用方法在编译时确定。多态性仅适用于.之前的引用而不是之后的引用。

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()。方法("等于")。getDeclaringClass()应该相当便宜?

不在我的选择中。它比从磁盘上读取内容更快,但你不会经常这么做。

最后但并非最不重要的是,你应该确保a.equals(b)==> b.equals(a)和a.equals(b)然后a.hashCode()== b.hashCode()很难做到ab是不同的类型。