繁体   English   中英

接口中的 toString()、equals() 和 hashCode()

[英]toString(), equals(), and hashCode() in an interface

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

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

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

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

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

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 节)。”

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

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

您可能能够完成所需的一种方法是在接口中提供不同的方法,例如toPrettyString()或类似的方法。 然后您可以调用该方法而不是默认的toString()方法。

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

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

你想要达到的目标是好的,但不切实际。

这些方法的实现一直来自Object

如果要强制覆盖 equals() 和 hashCode(),请从抽象超类扩展,该超类将这些方法定义为抽象方法。

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

Java 只关心方法是否在某处定义。 接口不会强制您在第一次从接口继承的新类中重新定义方法(如果它们已经定义)。 因为java.lang.Object已经实现了这些方法,所以你的新对象即使不重写这三个方法也符合接口。

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

不过,这可能会破坏使用接口的初衷。 这就是一些人建议从一开始就不应将 equals、toString 和 hashCode 包含在 Object 类中的原因之一。

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() {
        ...
    }
}

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM