Java 当只有某些实现提供正确的行为时,是否对接口进行编码

Java 当只有某些实现提供正确的行为时,是否对接口进行编码,java,Java,因此,我知道在OO代码中,对接口进行编码(使用接口作为变量的声明类型而不是具体类型)是一种很好的做法,原因有很多。例如,在Java集合中经常可以看到这种情况。那么,当只有某个接口的特定实现提供了正确的行为时,在程序中引用该接口仍然是一件好事吗 例如,我有一个Java程序。在那个程序中,我有多组对象。我选择使用集合,因为我不想要重复的元素。但是,我需要列表的排序属性(即维护插入顺序)。因此,我使用LinkedHashSet作为具体的集合类型。这些集合用于计算一个点积,该点积涉及集合中包含的对象的基

因此,我知道在OO代码中,对接口进行编码(使用接口作为变量的声明类型而不是具体类型)是一种很好的做法,原因有很多。例如,在Java集合中经常可以看到这种情况。那么,当只有某个接口的特定实现提供了正确的行为时,在程序中引用该接口仍然是一件好事吗

例如,我有一个Java程序。在那个程序中,我有多组对象。我选择使用集合,因为我不想要重复的元素。但是,我需要列表的排序属性(即维护插入顺序)。因此,我使用LinkedHashSet作为具体的集合类型。这些集合用于计算一个点积,该点积涉及集合中包含的对象的基本字段,例如in(简化一点):

双点(LinkedHashSet集,双[]数组){
双和=0.0;
int i=0;
用于(E元素:集合){
sum+=(element.getValue()*数组[i]);
}
回报金额;
}

此方法的结果取决于集合的迭代顺序,因此某些集合实现(主要是HashSet)将给出错误/意外的结果。目前,我在整个程序中使用LinkedHashSet作为声明的类型,而不是Set,以确保正确的行为。然而,这在风格上感觉很糟糕。在这里应该做什么?在这种情况下可以使用混凝土类型吗?或者我应该使用Set作为类型,但在文档中说明哪些实现将/不会产生正确的行为?我寻找的是一般性的输入,而不是上述场景的任何特定内容。特别是,这应该适用于使用LinkedHashSet或TreeSet的排序属性的任何场景。如何防止使用非预期的实现?您是在代码中强制它(通过删除接口),还是在文档中指定它?或者其他方法?

您可以为任何一种方法提供参数。只要您和维护此代码的其他人知道
Set
的特定实现可能会破坏应用程序或库的其余部分,那么对接口进行编码就可以了。但是,如果不是这样,那么您应该使用特定的实现

对界面进行编码的目的是为您提供灵活性,不会破坏您的应用程序。以JDBC为例。如果你使用了错误的驱动程序,它将破坏你的程序,就像你在这里描述的那样。但是,如果说Oracle决定在JDBC驱动程序中加入一些行为,这些行为微妙地破坏了编写到JDBC规范中的代码,而不是特定的Oracle驱动程序代码,那么您就必须做出选择


没有简单明了的“这永远是正确的”回答。

非常好的问题。一个解决方案是:制作另一个界面!假设一个扩展SortedMap但有一个getInsertionOrderIterator()方法或一个扩展Map的接口&有getOrderIterator()和getInsertionOrderIterator()方法


您可以编写一个包含LinkedHashMap和TreeMap作为后端数据结构的快速类。

确实,您应该为接口编写代码,但前提是它们做出的保证符合您的需要。在您的情况下,如果您只使用Set,那么您会说:我不想要重复的,但我不关心顺序。您也可以使用列表,意思是:我关心插入顺序,但不关心重复项。甚至还有一个SortedSet,但它没有您想要的顺序。因此,在您的情况下,如果不违反Liskov替换原则,就不能用其接口之一替换LinkedHashSet


因此,我认为在您的情况下,您应该坚持使用实现,直到您真正需要切换到另一个实现为止。对于现代IDE,重构不再那么难了,所以我不会做任何过早的优化——YAGNI和KISS。

我有点不同意。他要求的是接口总合同未提供的东西(订购)。如果他的代码根据实现而中断,他不应该使用通用接口。
double dot(LinkedHashSet<E> set, double[] array) {
    double sum = 0.0;
    int i = 0;
    for(E element : set) {
        sum += (element.getValue()*array[i]);
    }
    return sum;
}