【问题标题】:Implementation of equals(): compare against implemented interface or implementing class?equals() 的实现:与实现的接口或实现类比较?
【发布时间】:2011-08-12 16:17:30
【问题描述】:

我一直想知道如何最好地为实现相同接口的一系列类实现 equals()(并且客户端应该只使用所述接口并且永远不知道实现类)。

我还没有编写自己的具体示例,但是 JDK 中有两个示例 - java.lang.Number 和 java.lang.CharSequence 说明了这个决定:

boolean b1 = new Byte(0).equals( new Integer(0) ) );

或使用 CharSequence

boolean b2 = "".equals(new StringBuilder());

理想情况下,您是否希望这些评估结果为真?两种类型都实现了相同的数据类型接口,作为使用 Numbers(分别是 CharSequences)实例的客户端,如果 equals 比较接口类型而不是实现类型,我会更轻松。

现在这不是一个理想的例子,因为 JDK 将实现类型公开给公众,但假设我们不必维护与现有内容的兼容性 - 从设计人员的角度来看:应该 em> 等于检查接口还是更好地检查实现?


注意:我知道在实践中真正正确实现对接口的相等性检查可能非常困难,而且它甚至更加棘手,因为相等的接口也需要返回相同的 hashCode()。 但这些只是实现中的障碍,例如 CharSequence,虽然接口非常小,但相等检查所需的一切都存在,而没有揭示实现的内部结构(因此原则上可以正确实现,即使 没有 提前了解未来的实现)。 但我对设计方面更感兴趣,而不是如何实际实现它。我不会仅仅根据实现的难易程度来决定。

【问题讨论】:

    标签: java interface equals


    【解决方案1】:

    定义一个抽象类,implements 您的接口并定义最终的 equals()/hashCode() 方法并让您的客户扩展它:

    public interface Somethingable {
        public void something();
    }
    
    public abstract class AbstractSomethingable implements Somethingable {
        public final boolean equals(Object obj) {
            // your consistent implementation
        }
    
        public final int hashCode() {
            // your consistent implementation
        }
    }
    

    请注意,通过使您的类abstract,您可以implements 接口而不定义接口的方法。

    您的客户仍然必须实现 something() 方法,但他们的所有实例都将使用 your 代码用于 equals()/hashCode()(因为您已经使用了这些方法 final) .

    对您的客户的区别是:

    • 使用extends 关键字代替implements 关键字(次要)
    • 无法扩展他们选择的其他类以使用您的 API(可能是次要的,也可能是主要的 - 如果可以接受,那就去吧)

    【讨论】:

      【解决方案2】:

      我通常会假设“相似”对象不相等 - 例如,我不希望 Integer(1) 会通过 equals(Long(1)) 。我可以想象这样的情况是合理的,但是由于 jdk 需要成为一个通用 API,您将无法做出这样的假设总是是正确的做法。

      如果你有某种合理的自定义对象,我认为如果你实现 equals 的扩展定义是非常好的

      • 确保您没有某些边缘情况确实需要更具体的相等性(即需要相同的类)
      • 记录它非常清楚
      • 确保哈希码的行为与新的 equals 一致。

      【讨论】:

      • 另外,确保 equals 是对称的:如果 a.equals(b) 则 b.equals(a),反之亦然。
      • @JB Nizet:有时有用的一种模式是有两个 equals 方法;如果第一种方法既不能确定相等也不能确定不相等,则应调用另一个对象的第二种方法;如果无法确定相等,则第二种方法应报告不相等。在这种情况下,如果“int”不知道如何将自己与“long”进行比较,但“long”知道如何将自己与“int”进行比较,则主等号运算符的行为将是对称的。
      【解决方案3】:

      对于它的价值,我可能会做一个特定于实现的 equals 实现(旁注 - 不要忘记实现 hashCode...)。接口级别的 equals() 给接口的实现者带来了相当沉重的负担——他们可能知道也可能不知道特殊要求。

      通常,实现级别可以正常工作,因为您的客户端只处理一个实现(即 MyNumberProcessor 可以适用于任何数字,但实际上它的一个实例只需要处理 Long 并且可能只需要另一个双倍的)。泛型是确保发生这种情况的好方法。

      在它确实重要的极少数情况下,我可能会设计客户端以允许注入 Comparator 或 - 当不可用时 - 将我的 Numbers 封装到 VarTypeNumber 中。

      【讨论】:

        【解决方案4】:

        我会尝试在我的界面中添加另一个 equals 方法。怎么样:

        assertFalse(new Integer(0).equals(new Byte(0))); // pass
        assertTrue(new Integer(0).valueEquals(new Byte(0))); // hypothetical pass
        

        这不会产生意外行为(不同类型相等),但可以检查相等值。

        在 Effective java 中有一个相关的话题,其中讨论了与 instanceof 和 getClass 的相等。不过不记得货号了。

        【讨论】:

        • 实现你自己的 equals 的问题是你不能使用像 myCollection.contains(myObject) 这样的东西,如果你希望扩展接口的不同类的实例被认为是相等的(假设你保持equals/hashcode 合约)
        • 好点,完全正确。我更关心的是可读性和简洁性。总的来说,我认为不同的班级不应该是平等的。它们可能是子类或实现相同的接口。
        • 如果你说“不同的类不应该相等”,那么子类呢? JDK 中有许多不同子类型确实相等的示例(最坏的示例可能是 java.util.Date 及其在 java.sql 中的子类)。
        • 是的,在某些情况下子类是相等的,但我认为这并不总是一个好的决定。最后,当您向子类添加其他字段并覆盖等于时,universum 可能会崩溃。非对称等于不好! :)
        【解决方案5】:

        我认为任何对不具有相同具体类型的对象返回 true 的 equals 实现都是非常“令人惊讶”的行为。如果您在编译时知道接口的每个可能实现者的盒子内操作,您可以制造仅对接口方法有意义的等式,但这对于 API/框架代码来说不是现实。

        你甚至不能确定没有人会编写一个接口的实现,当你调用你用来实现 equals 的方法时,它会改变它的内部状态!说说令人困惑的,一个返回true并在过程中使自己无效的equals检查?

        --

        就“检查接口是否相等”而言,这就是我所理解的问题:

        public interface Car {
        
          int speedKMH();
          String directionCardinal();
        
        }
        
        public class BoringCorrolla implements Car {
        
          private int speed;
          private String directionCardinal;
        
          public int speedKMH() { return speed; }
          public String directionCardinal() { return directionCardinal; }
        
          @Override
          public boolean equals(Object obj) {
            if (obj isntanceof Car) {
              Car other = (Car) obj;
              return (other.speedKMH() == speedKMH() && other.directionCardinal().equals(directionCardinal());
            }
          }
        }
        
        public class CRAZYTAXI implements Car, RandomCar {
        
              public int speedKMH() { return randomSpeed(); }
              public String directionCardinal() { return randomDirection();}
            }
        

        【讨论】:

        • 不确定您所说的“当您调用用于实现 equals 的方法时会改变其内部状态的接口的实现”是什么意思 - 似乎您通常会通过以下方式检查 equals检查字段值,因此要执行此操作,您必须编写一个更改字段值的 equals 实现。我想不出一个例子,这并不意味着你从事错误的职业。你能详细说明一下吗?
        • 如果要检查接口是否相等,如何比较字段值?是的,大多数例子看起来很奇怪,而且很难证明是正确的。当你发布一个 API 时,你是否认为它只会被从不编写奇怪的意外代码的高技能程序员使用?
        • 很明显,与接口进行比较需要接口以规范形式为内部状态公开某种形式的 getter。我当然不会指望任何人,甚至是一个糟糕的程序员都不会在一个明显打算用作 getter 的方法中实现状态变异的副作用。我明白你的意思,但我个人对这类问题的决心是:你会犯(这种)错误,但你也会从中吸取教训。
        • 在某些情况下,不同具体类的对象应该比较相同。例如,考虑一个应该用于不可变 64x64 模式位图的接口,相等被定义为在所有 4,096 像素中具有相同的颜色。存储此类对象的一种方法是为每个像素存储 2 或 4 个字节,以便每个位图占用 8K 或 16K。另一方面,拥有可以以其他形式存储一些位图的类可能会有所帮助(例如,SolidColorPatternBitmap 类存储一种颜色并表示具有该颜色的 4096 像素的位图)。
        • 找到比较两个位图的最佳方法将是一个双分派问题,但对于每个位图 Fred 使用策略 (1) 看看 Fred 是否对自己有专门的方法可能会有所帮助另一个位图; (2) 询问其他位图是否有专门的手段与 Fred 进行比较; (3) 向另一个位图询问其规范形式的哈希,并将其与 Fred 自己缓存的哈希进行比较(如果它们不同,则位图不匹配); (4) 向其他位图询问其规范形式,将 Fred 临时转换为其规范形式,并进行比较。
        【解决方案6】:

        可以定义不同类之间的相等性。

        在您的情况下,接口必须指定精确的相等算法,因此任何实现接口的类都必须遵守它。更好的是,由于该算法仅依赖于接口公开的信息,因此只需实现它,子类就可以简单地借用它。

        interface Foo
        
        class Util 
        
            static int hashCode(Foo foo){ ... }
        
            static boolean equal(Foo a, Foo b){ ... }
        
            static boolean equal(Foo a, Object b)
                return (b instanceof Foo) && equal(a, (Foo)b);
        
        class FooX implements Foo
        
            int hashCode()
                return Util.hashCode(this); 
        
            boolean equals(Object that)
                return Util.equal(this, that);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-01-15
          • 2014-12-01
          • 2015-01-27
          • 1970-01-01
          • 2017-12-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多