如何使接口实例方法只接受同一类的参数?

8

我希望使用这样的一个接口:

public interface ResultItem {
    public int getConfidence();
    public boolean equals(ResultItem item);
    public ResultItem cloneWithConfidence(int newConfidence);
}

我已经使用不同类型的对象来表示语音识别结果。

我的想法是,我希望只比较相同类型的结果。也就是说,如果我创建一个实现ResultItem接口的IntResult类,我希望方法签名变为:

public boolean equals(IntResult item);
public IntResult cloneWithConfidence(int newConfidence);

我觉得我的界面存在设计缺陷,因为现在我在cloneWithConfidence和其他返回ResultItem的方法的结果上使用了相当丑陋的转换。

有更好的方法吗?


如果您按照您发布的方式进行操作,会发生什么? - OscarRyz
@OscarRyz,我为我的虚伪的转换和实现类equals方法中重复的检查语句 if(this.getClass().isInstance(item)) return false; 而哭泣。 - Dunaril
你最好继续哭泣。你需要覆盖Object.equals(Object other)方法,以便equals可以用于集合等。忘记你的专门版本吧,它可能更好,但没有任何地方使用(除了你明确调用的地方)。通过编写专门版本,你有遗忘编写覆盖版本的风险。 - maaartinus
将对象与其他任意类型的对象进行比较并没有什么不妥之处。加菲猫应该不会在被问及是否等于奥迪狗时大惊小怪,无论他感到多么愤慨。他应该简单地回答不,他们不相等。如果加菲猫能够认出任何与他相等的东西,那么不能识别某些东西并不意味着他不知道自己是否相等。相反,不能识别某些东西本身就证明他与之不相等。 - supercat
3个回答

11

有一个经常出现的成语,如下所示:

public interface ResultItem<T extends ResultItem<T>> {
    public int getConfidence();
    public boolean equals(T item);
    public T cloneWithConfidence(int newConfidence);
}

public class IntResult implements ResultItem<IntResult> {
  //...
}

当我们执行以下代码时 ResultItem item = new IntResultItem(); IntResultItem iResultItem = item.cloneWithConfidence(100);,仍然无法获得语法糖增益(编译器错误)。 - ring bearer
1
@Dunaril 1. 关于警告,你必须适当地使用ResultItem<IntResult>或者ResultItem<?>。2. 第二个问题并不是特定于这个解决方案的:如果传入了一个期望List<Object>的参数List<Integer>,你将会看到相同的行为。 - NPE
@Dunaril - 如果您只想读取列表,请尝试类似 List<? extends ResultItem<?>> 的东西。 - jtahlborn
@jtahlborn 看起来很有前途,但我需要在其中一个方法中删除和添加元素到列表中。 - Dunaril
除非我误解了什么,否则所提出的递归定义习惯用法与更简单的形式相比并没有什么优势。请参见:https://dev59.com/4nbZa4cB1Zd3GeqPElZw - Marcus Junius Brutus
显示剩余4条评论

4

这并不是对你问题的回答,但我认为这是一个重要的说明:

如果你想让你的 equals 方法可以用于集合等对象中,你需要实现 public boolean equals(Object o),并且它应该适用于所有类型的对象比较(在大多数情况下返回 false)。你可能还有一个更窄的参数类型的方法,并在实现中进行委托,如下所示:

public class IntResult {
    public boolean equals(Object o) {
        return o instanceof IntResult &&
             this.equals((IntResult)o);
    }
    public boolean equals(IntResult that) {
        // TODO
    }

}

请确保您遵守等式合同中的所有条件,即对称性、自反性、传递性和具有兼容的hashCode实现。


我认为像这样的答案提供了额外的价值并且非常重要(+1) - Sean Patrick Floyd
在我看来,你的“不算是答案”的回复比真正的答案更有价值。然而,我强烈建议不要使用参数类型更窄的附加方法。 - maaartinus
@maaartinus:也许这个附加方法应该是私有的,而且命名不同于“equals”,比如equalsImpl等。如果你不必检查正确的类型,实现起来通常更容易。 - Paŭlo Ebermann

1

嗯,你可以让它通用:

public interface ResultItem<T extends ResultItem<T>> {
    public boolean equals(ResultItem<T> item);
}

然后你需要让 IntResult 实现 ResultItem<IntResult> 接口。

当然,这并不能阻止其他类的不当行为,例如,FloatResult 实现了 ResultItem<IntResult> 接口,但是当所有类都表现良好时,它可以使API的各个部分正常工作。


恕我直言,我不明白为什么我们需要这个复杂的表格。请参见:https://dev59.com/4nbZa4cB1Zd3GeqPElZw - Marcus Junius Brutus
@MarcusJuniusBrutus:你收到的回答基本上是“不可能的”。这个答案展示了可能的内容......虽然它并不完美,但它仍然允许更愉悦的API——看一下Enum的例子就知道了。 - Jon Skeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接