从Vectorinheritance的Java类Stack的负面影响是什么?

通过扩展类Vector,Java的设计者能够快速创建类Stack。 这种inheritance使用的负面影响是什么,特别是对于类Stack?

非常感谢。

一个问题是Stack是一个类,而不是一个接口。 这与集合框架的设计不同,其中您的名词通常表示为接口(例如,List,Tree,Set等),并且存在特定实现(例如,ArrayList,LinkedList)。 如果Java可以避免向后兼容,那么更合适的设计将是具有Stack接口,然后将VectorStack作为实现。

第二个问题是Stack现在绑定到Vector,通常避免使用ArrayLists等。

第三个问题是您无法轻松提供自己的堆栈实现,并且堆栈支持非堆栈操作,例如从特定索引获取元素,包括索引exception的可能性。 作为用户,您可能还必须知道堆栈的顶部是在索引0还是在索引n处。 该接口还公开了容量等实现细节。

在原始Java类库中的所有决策中,我认为这是一个更奇特的决策。 我怀疑聚合会比inheritance贵得多。

Effective Java 2nd Edition,Item 16:赞成组合而不是inheritance

只有在子类确实是超类的子类型的情况下,inheritance才是合适的。 换句话说,只有当两个类之间存在“is-a”关系时, B类才应该扩展A类。 如果你想让B级扩展A级,那么问问自己这个问题:每个B真的是A吗? 如果你不能如实回答这个问题, B不应该延伸A. 如果答案是否定的,那么B通常应该包含A的私有实例并暴露更小更简单的API; A不是B的重要部分,仅仅是其实现的细节。

Java平台库中存在许多明显违反此原则的行为。 例如,堆栈不是向量,因此Stack不应该扩展Vector 。 同样,属性列表不是哈希表,因此Properties不应扩展Hashtable 。 在这两种情况下,组合都是可取的。

本书更详细,并结合第17项:设计和文档inheritance或禁止它 ,建议不要在设计中过度使用和滥用inheritance。

这是一个简单的例子,它显示了Stack允许类似Stack的行为的问题:

  Stack stack = new Stack(); stack.push("1"); stack.push("2"); stack.push("3"); stack.insertElementAt("squeeze me in!", 1); while (!stack.isEmpty()) { System.out.println(stack.pop()); } // prints "3", "2", "squeeze me in!", "1" 

这严重违反了堆栈抽象数据类型。

也可以看看

  • 维基百科/堆栈(数据结构)

    在计算机科学中,堆栈是后进先出(LIFO)抽象数据类型和数据结构。

拥有Stack子类Vector暴露不适合堆栈的方法,因为堆栈不是向量(它违反了Liskov替换原则 )。

例如,堆栈是LIFO数据结构,但使用此实现,您可以调用elementAtget方法来检索指定索引处的元素。 或者您可以使用insertElementAt来破坏堆栈契约。

我认为Joshua Bloch已经记录在案,说Stack子类Vector是一个错误,但不幸的是我找不到参考。

好吧, Stack应该是一个接口。

Stack接口应该定义堆栈可以执行的操作。 然后可能有不同的Stack实现在不同的情况下执行不同。

但是,由于Stack是一个具体的类,这不可能发生。 我们仅限于堆栈的一个实现。

除了上面提到的主要有效点之外,Stackinheritance自Vector的另一个大问题是Vector是完全同步的,所以无论你是否需要它都会得到这个开销(参见StringBuffer vs. StringBuilder)。 就个人而言,每当我想要一个堆栈时,我倾向于使用ArrayDeque。

它违反了我们所知道的关于inheritance的第一条规则:你能用一张笔直的面孔说出一个Stack IS-A Vector吗? 显然不是。

另一个更合乎逻辑的操作是使用聚合,但IMO的最佳选择是使Stack成为可以通过任何适当的数据结构实现的接口,类似于(但不完全相同)C ++ STL。