【问题标题】:toString(), equals(), and hashCode() in an interface接口中的 toString()、equals() 和 hashCode()
【发布时间】:2009-11-11 21:13:58
【问题描述】:

所以,我有一个接口,里面有一堆需要实现的方法,方法名是无关紧要的。

实现这个接口的对象经常被放入集合中,并且还有我希望它们使用的特殊 toString() 格式。

所以,我认为将 hashCode()、equals() 和 toString() 放入接口会很方便,以确保我记得覆盖这些的默认方法。但是当我将这些方法添加到接口时,如果我没有实现这三个方法,IDE/编译器不会抱怨,即使我明确地将它们放在接口中。

为什么不对我强制执行?如果我不实现任何其他方法,它会抱怨,但它不会强制执行这三种方法。是什么赋予了?有什么线索吗?

【问题讨论】:

  • 您可以将它们添加到接口中,以在 javadoc 中定义实现应该如何特殊(在 Object 中的合同之上),但没有静态方法让编译器强制执行它。
  • 是的,这也是至少将其正式记录在案的好方法。我想远离继承解决方案;这些方法首先位于接口中的原因是,我可以针对它而不是实现进行编码。

标签: java interface equals hashcode tostring


【解决方案1】:

听起来你想强制你的类覆盖这些方法的默认实现。如果是这样,这样做的方法是声明一个抽象超类,该超类具有声明为抽象的方法。例如:

public abstract class MyBaseClass implements ... /* existing interface(s) */ {

    public abstract boolean equals(Object other);

    public abstract int hashCode();

    public abstract String toString();
}

然后将您当前的课程更改为extend这个课程。

这种方法可行,但不是理想的解决方案。

  • 对于现有的类层次结构可能会有问题。

  • 强制实现现有接口的类来扩展特定抽象类是一个坏主意。例如,您可以更改方法签名中的参数以使用抽象类而不是现有接口。但最终结果是不太灵活的代码。 (无论如何,人们都可以找到颠覆这种做法的方法;例如,通过添加他们自己的抽象子类,通过super.<method>(...) 调用“实现”这些方法!)

  • 强加特定的类层次结构/实现模式是短视的。您无法预测未来的某些需求变化是否意味着您的限制会导致困难。 (这就是为什么人们建议针对接口而不是特定类进行编程的原因。)


回到您的实际问题,即为什么您的界面不强制类重新声明这些方法:

为什么不对我强制执行?如果我不实现任何其他方法,它会抱怨,但它不会强制执行这三个方法。是什么赋予了?有什么线索吗?

接口施加的约束是实现它的具体类对每个方法都有一个实现。但是,它并不要求类本身 提供这些方法。方法实现可以从超类继承。在这种情况下,这就是正在发生的事情。从java.lang.Object 继承的方法满足约束。

JLS 8.1.5 声明如下:

“除非被声明的类是抽象的,否则每个直接超接口的所有抽象成员方法都必须通过该类中的声明或通过继承的现有方法声明来实现(第 8.4.8.1 节)来自直接超类或直接超接口,因为非抽象类不允许有抽象方法(第 8.1.1.1 节)。”

【讨论】:

  • 我不知道可以强制实施这些。感谢您提供超出问题范围的答案。
【解决方案2】:

Java 中的所有对象都继承自 java.lang.ObjectObject 提供这些方法的默认实现。

如果您的接口包含其他方法,如果您没有通过提供这些方法的实现来完全实现该接口,Java 会抱怨。但是对于equals()hashCode()toString()(以及您未提及的其他一些),实现已经存在。

您可能能够完成您想要的事情的一种方法是在界面中提供不同的方法,例如toPrettyString() 或类似的东西。然后您可以调用该方法而不是默认的toString() 方法。

【讨论】:

  • 关于不同方法名称的要点。我只是希望有一种方法,除了继承之外,我可以指定一个类来覆盖默认实现。
  • 这不就是继承的目的吗?为什么需要不同的机制?
  • 如果你可以强制执行,那么该覆盖可能只是调用 super.hashCode()。
  • 针对matt b,定义自己的toString版本是很常见的,这样当你打印对象时,你会得到一个连贯的答案,而不是类名的hash等。至于hashcode 和 equals,每当您在集合中使用对象时,hashcode 和 equals 的默认实现并不总是正确地代表您的应用程序的“有意义的相等”。
  • 但是当涉及到在本机集合框架中专门使用的 equals() 和 hashCode() 时,我不能这样做。而且我不能改变框架!
【解决方案3】:

所有这 3 个方法都由 java.lang.Object 定义,它(隐式)由所有其他类扩展;因此这些方法的默认实现是存在的,编译器没有什么可抱怨的。

【讨论】:

    【解决方案4】:

    任何实现你的接口的类也扩展了 Object。 Object 定义了 hashCode、equals 和 toString,并且默认实现了这三个。

    你想要达到的效果很好,但不切实际。

    【讨论】:

      【解决方案5】:

      如果你想强制重写 equals() 和 hashCode(),从一个抽象超类扩展,它将这些方法定义为抽象。

      【讨论】:

      • 这是实现此目的的正确方法。当您有一组要共享行为的子类(或者在这种情况下,共享覆盖行为)时,请使用继承。
      • 它是“正确的”,但也不是没有问题——看我的回答。
      【解决方案6】:

      Object 一路来的这些方法都有一个实现。

      【讨论】:

        【解决方案7】:

        您的对象已经包含这三个方法的实现,因为每个对象都从 Object 继承这些方法,除非它们被覆盖。

        【讨论】:

          【解决方案8】:

          其他人已经充分回答了您的实际问题。至于您的特定问题的解决方案,您可以考虑创建自己的方法(可能是 getStringRepresentation、getCustomHashcode 和 equalsObject),并让您的对象扩展一个基类,其 equals、toString 和 hashCode 方法调用这些方法。

          不过,这可能会破坏首先使用接口的目的。这也是一些人认为 equals、toString 和 hashCode 一开始就不应该包含在 Object 类中的原因之一。

          【讨论】:

            【解决方案9】:

            Java 只关心方法是否在某处定义。接口不会强制您重新定义第一次从接口继承的新类中的方法(如果它们已经定义)。由于java.lang.Object 已经实现了这些方法,因此您的新对象符合接口,即使它们自己没有覆盖这三个方法。

            【讨论】:

              【解决方案10】:

              Adam 告诉你为什么你无法摆脱尝试强制 equals、hashCode 和 toString 的原因。我会选择以下涉及 Adam 和 Stephan 提供的解决方案的实现:

              public interface Distinct {
                  boolean checkEquals(Object other);
                  int hash();
              }
              
              public interface Stringable {
                  String asString();
              }
              
              public abstract class DistinctStringableObject {
              
                  @Override
                  public final boolean equals(Object other) {
                      return checkEquals();
                  }
              
                  @Override
                  public final int hashCode() {
                      return hash();
                  }
              
                  @Override
                  public final String toString() {
                      return asString();
                  }
              }
              

              现在,任何需要自己明确区分并表示为字符串的类都可以扩展 DistinctStringableObject,这将强制实现 checkEquals、hashCode 和 toString。

              示例具体类:

              public abstract class MyDistinctStringableObject extends DistinctStringableObject {
              
                  @Override
                  public final boolean checkEquals(Object other) {
                      ...
                  }
              
                  @Override
                  public final int hash() {
                      ...
                  }
              
                  @Override
                  public final String asString() {
                      ...
                  }
              }
              

              【讨论】:

                【解决方案11】:

                如果你有一个孙子,抽象类将不起作用,因为它的父亲已经覆盖了 equals 和 hashCode 方法,然后你又遇到了问题。

                尝试使用注释和 APT (http://docs.oracle.com/javase/1.5.0/docs/guide/apt/GettingStarted.html) 来完成它。

                【讨论】:

                  【解决方案12】:

                  如果你声明了一个接口,默认情况下所有方法都是抽象的,你确实需要提供功能,但是当你在子类中实现它时,你提供实现权。 如您所见,每个类都是一个超类的子类(简单地说 Object 是所有类的超类) 所以如果你有一个实现接口的类,你需要提供方法的实现。但是这里需要记住一件事。

                  不管怎样,如果你没有在接口中声明这些方法,你仍然对最初实现接口的子类有这种行为。

                  所以如果不声明,它仍然存在,另外,由于这些方法和Object类的其他方法对所有类的对象都存在,所以不需要实现。

                  【讨论】:

                    猜你喜欢
                    • 2021-10-31
                    • 1970-01-01
                    • 2017-02-05
                    • 2013-09-25
                    • 2019-06-24
                    • 1970-01-01
                    • 1970-01-01
                    • 2021-11-27
                    • 2011-02-26
                    相关资源
                    最近更新 更多