What are the negative aspects of Java class Stack

2019-01-11 03:54发布

问题:

By extending class Vector, Java’s designers were able to create class Stack quickly. What are the negative aspects of this use of inheritance, particularly for class Stack?

Thanks a lot.

回答1:

One problem is that Stack is a class, not an interface. This diverges from the design of the collection framework, where your noun is typically represented as an interface (e.g., List, Tree, Set, etc.), and there are specific implementations (e.g., ArrayList, LinkedList). If Java could avoid backward compatibility, then a more proper design would be to have a Stack interface, then VectorStack as an implementation.

A second problem is that Stack is now bound to Vector, which is generally avoided in favour of ArrayLists and the like.

A third problem is that you cannot easily provide your own stack implementation, and that stacks support very non-stack operations like getting an element from a specific index, including the potential for index exceptions. As a user, you may also have to know if the top of the stack is at index 0 or at index n. The interface also exposes implementation details such as capacity.

Of all the decisions in the original Java class library, I consider this one of the more peculiar ones. I doubt that Aggregation would have been much more expensive than inheritance.



回答2:

Effective Java 2nd Edition, Item 16: Favor composition over inheritance:

Inheritance is appropriate only in circumstances where the subclass really is a subtype of the superclass. In other words, a class B should only extend a class A only if an "is-a" relationship exists between the two classes. If you are tempted to have a class B extend a class A, ask yourself this question: Is every B really an A? If you cannot truthfully answer yes to this question, B should not extend A. If the answer is no, it is often the case that B should contain a private instance of A and expose a smaller and simpler API; A is not an essential part of B, merely a detail of its implementation.

There are a number of obvious violations of this principle in the Java platform libraries. For example, a stack is not a vector, so Stack should not extend Vector. Similarly, a property list is not a hash table, so Properties should not extend Hashtable. In both cases, composition would have been preferrable.

The book goes in greater detail, and combined with Item 17: Design and document for inheritance or else prohibit it, advises against overuse and abuse of inheritance in your design.

Here's a simple example that shows the problem of Stack allowing un-Stack-like behavior:

    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"

This is a gross violation of the stack abstract data type.

See also

  • Wikipedia/Stack (data structure)

    In computer science, a stack is a last in, first out (LIFO) abstract data type and data structure.



回答3:

Having Stack subclass Vector exposes methods that are not appropriate for a stack, because a stack is not a vector (it violates the Liskov Substitution Principle).

For example, a stack is a LIFO data structure yet using this implementation you can call the elementAt or get methods to retrieve an element at a specified index. Or you can use insertElementAt to subvert the stack contract.

I think Joshua Bloch has gone on record as saying that having Stack subclass Vector was a mistake, but unfortunately I can't find the reference.



回答4:

Well, Stack should be an interface.

The Stack interface should define the operations a stack can perform. Then there could be different implementations of Stack that perform differently in different situations.

But, since Stack is a concrete class, this cannot happen. We are limited to one implementation of a stack.



回答5:

In addition to the main valid points mentioned above, another big problem with Stack inheriting from Vector is Vector is completely synchronized, so you get that overhead whether you need it or not (see StringBuffer vs. StringBuilder). Personally I tend to use ArrayDeque whenever I want a stack.



回答6:

It violates the very first rule we all learned about inheritance: can you, with a straight face, say that a Stack IS-A Vector? Clearly not.

Another more logical operation would be to have used aggregation, but the best option IMO would be to have made Stack an interface which could be implemented by any appropriate data structure, similar (but not exactly the same) to what the C++ STL does.