|
Description
|
The implementation of Hashtable.put() makes creating classes that
extend Hashtable and reimplement put and get very dangerous, in a
subtle and undocumented way.
Consider a class, say OrderedHashtable, that extends Hashtable
and implements its own get and put methods. OrderedHashtable
wants to maintain the insertion order of entries in the table by
keeping a separate linked list of OrderedHashtableEntry objects,
each of which wraps an Object that was passed into its put()
method. The Objects stored in the underlying Hashtable are
instances of OrderedHashtableEntry, not the origial objects that
were passed to OrderedHashtable.put
(One can argue about the merits of this implementation and
suggest that the right relationship is OrderedHashtable HAS-A
Hashtable, rather than IS-A Hashtable, but that's beside the
point for now.)
Given the class described above, you get the following scenario
when the base Hashtable runs out of space:
(1) Client calls OrderedHashtable.put on some customer (O) of
type T, with some key (K) of type String.
(2) OrderedHashtable constructs a wrapper (W) of type
OrderedHashtableEntry around O and calls super.put(K, W)
to put it into the base hashtable. This is usually okay
because OrderedHashtable.get will unwrap the value
for you and return O.
(3) If the hashtable is nearly full, Hashtable.put extends
the underlying data structure AND THEN RECURSIVELY CALLS
put() TO PUT THE OBJECT INTO THE NEW TABLE.
(4) The call to put resolves to OrderedHashtable.put instead
of to the normal Hashtable.put (because all non-final
methods in Java are virtual), which means
OrderedHashtable.put is now asked to use key K to store
customer W instead of the original customer O.
(5) OrderedHashtable dutifully complies with this request,
wrapping the value in yet another instance of
OrderedHashtableEntry (W2) and storing that in
the table under key K.
When you go to retrieve the value for K, OrderedHashtable
retrieves W2 and then unwraps it, handing you back W, which
is what it got from the recursive call to put that was made
by the Hashtable internals in step (3), but which is NOT the
customer you originally asked it to store.
Hashtable should not be calling put() recurisively after
extending the table -- it should use a private method to actually
store data in the table so that subclasses can't get into this
broken state, or else it should extend the table and then store
the value inline in Hashtable.put
======================================================================
|
|
Work Around
|
A hastable subclass like OrderedHashtable can check in its own
put() method to see whether the value being stored is of type
OrderedHashtableEntry, and if so assume a recursive call from
the superclass put method, but this is a truly ugly hack.
======================================================================
|
|
Comments
|
Submitted On 03-FEB-1999
Stendal
The internal use of public non-final methods can be really confusing sometimes.
In general, developer should know nothing about implementation of a class, but
it may be useful to make some note indicating that this method is used
internally and provide a list of methods which use it. Then, if a developer has
a problem with subclass (as I do), this note can give him a hint to inspect
implementation of a superclass. Such notes can be implemented with a help of
javadoc.
PLEASE NOTE: JDK6 is formerly known as Project Mustang
|