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.
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.
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.
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 extendVector
. Similarly, a property list is not a hash table, soProperties
should not extendHashtable
. 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.
In computer science, a stack is a last in, first out (LIFO) abstract data type and data structure.
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.
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.
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.
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.