通过扩展类Vector,Java的设计者能够快速创建类Stack。是什么 这种继承使用的消极方面,特别是对于类Stack?
非常感谢。
答案 0 :(得分:25)
Effective Java 2nd Edition,Item 16:赞成组合而不是继承:
继承仅适用于子类实际上是超类的子类型的情况。换句话说,只有在两个类之间存在“is-a”关系时,类 B 才应该扩展类 A 。如果您想要一个类 B 扩展一个类 A ,请问自己这个问题:每个 B 是否真的是 A < / em>的?如果你不能如实回答这个问题,那么 B 不应该扩展 A 。如果答案是否定的,通常情况是 B 应包含 A 的私有实例,并公开更小更简单的API; A 不是 B 的重要组成部分,只是其实现的细节。
Java平台库中存在许多明显违反此原则的行为。例如,堆栈不是向量,因此
Stack
不应扩展Vector
。同样,属性列表不是哈希表,因此Properties
不应扩展Hashtable
。在这两种情况下,组合都是可取的。
本书更详细,并结合第17项:继承的设计和文档,或者禁止它,建议不要在设计中过度使用和滥用继承。
这是一个简单的示例,显示了Stack
允许un - Stack
行为的问题:
Stack<String> stack = new Stack<String>();
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)抽象数据类型和数据结构。
答案 1 :(得分:22)
一个问题是Stack是一个类,而不是一个接口。这与集合框架的设计不同,其中您的名词通常表示为接口(例如,List,Tree,Set等),并且存在特定实现(例如,ArrayList,LinkedList)。如果Java可以避免向后兼容,那么更合适的设计是使用Stack接口,然后将VectorStack作为实现。
第二个问题是Stack现在绑定到Vector,通常避免使用ArrayLists等。
第三个问题是您无法轻松提供自己的堆栈实现,并且堆栈支持非堆栈操作,例如从特定索引获取元素,包括索引异常的可能性。作为用户,您可能还必须知道堆栈的顶部是在索引0还是在索引n处。该接口还公开了容量等实现细节。
在原始Java类库中的所有决策中,我认为这是一个更奇特的决策。我怀疑聚合会比继承贵得多。
答案 2 :(得分:6)
让Stack
子类Vector
公开不适合堆栈的方法,因为堆栈不是向量(它违反了Liskov Substitution Principle)。
例如,堆栈是LIFO数据结构,但使用此实现,您可以调用elementAt
或get
方法来检索指定索引处的元素。或者您可以使用insertElementAt
来破坏堆栈合同。
我认为Joshua Bloch已经记录在案,说Stack
子类Vector
是一个错误,但不幸的是我找不到参考。
答案 3 :(得分:3)
好吧,Stack
应该是一个界面。
Stack
接口应该定义堆栈可以执行的操作。然后可能有Stack
的不同实现在不同的情况下表现不同。
但是,由于Stack
是一个具体的类,所以这不可能发生。我们仅限于堆栈的一个实现。
答案 4 :(得分:2)
除了上面提到的主要有效点之外,Stack继承自Vector的另一个大问题是Vector是完全同步的,所以无论你是否需要它都会得到这个开销(参见StringBuffer vs. StringBuilder)。就个人而言,每当我想要一个堆栈时,我倾向于使用ArrayDeque。
答案 5 :(得分:1)
它违反了我们所知道的关于继承的第一条规则:你能用一张笔直的面孔说出一个Stack IS-A Vector吗?显然不是。
另一个更合乎逻辑的操作是使用聚合,但IMO的最佳选择是使Stack成为可以通过任何适当的数据结构实现的接口,与C ++ STL类似(但不完全相同)确实