In this chapter, we learn about Hashtable.
We first have a general understanding of Hashtable, and then learn its source, and finally through the example to learn to use Hashtable.
The 1th part Hashtable introduction
Hashtable Introduction
Like HashMap, Hashtable is also a hash table that stores the key-value pair (key-value) mappings.
Hashtable inherits from Dictionary, realizes map, cloneable, java.io.Serializable interface.
Hashtable functions are synchronized, which means that it is thread-safe. Its key, value can be null. In addition, the mappings in Hashtable are not ordered.
An instance of Hashtable has two parameters that affect its performance: initial capacity and load factor. The capacity is the number of buckets in the hash table, and the initial capacity is the size of the hashtable when it was created. Note that the status of the hash table is open: When a hash conflict occurs, a single bucket stores multiple entries that must be searched sequentially. The load factor is a measure of how full a hash table can be when its capacity is automatically increased. Both the initial capacity and the load factor are just hints for the implementation. Specific details about when and whether to call the rehash method depend on the implementation.
Typically, the default load factor is 0.75, which is to find a tradeoff between time and space costs. The load factor is too high although it reduces the space overhead, it also increases the time to find an entry (which is reflected in most Hashtable operations, including get and put operations).
The inheritance relationship of Hashtable
Java.lang.Object
Java.util.dictionary<k, v>
Java.util.hashtable<k, v>
public class Hashtable<k,v> extends dictionary<k,v>
Implements Map<k,v>, Cloneable, java.io.Serializable {}
Hashtable and map relate to the following diagram:
Constructor for Hashtable
The default constructor. Public
Hashtable ()
//Specify the constructor for capacity size public
Hashtable (int initialcapacity)
//Specify the constructor for capacity size and load factor Public
Hashtable (int initialcapacity, float loadfactor)
//constructor with "child Map" public
Hashtable (map<? Extends K,? Extends V> t)
API for Hashtable
synchronized void clear()
synchronized Object clone()
boolean contains(Object value)
synchronized boolean containsKey(Object key)
synchronized boolean containsValue(Object value)
synchronized Enumeration<V> elements()
synchronized Set<Entry<K, V>> entrySet()
synchronized boolean equals(Object object)
synchronized V get(Object key)
synchronized int hashCode()
synchronized boolean isEmpty()
synchronized Set<K> keySet()
synchronized Enumeration<K> keys()
synchronized V put(K key, V value)
synchronized void putAll(Map<? extends K, ? extends V> map)
synchronized V remove(Object key)
synchronized int size()
synchronized String toString()
synchronized Collection<V> values()
See more highlights of this column: http://www.bianceng.cnhttp://www.bianceng.cn/Programming/Java/
2nd part Hashtable Source analysis
To get a better idea of the Hashtable principle, the following is an analysis of the Hashtable source code.
In reading the source code, we recommend that the following instructions to establish a general understanding of the Hashtable, so it is easier to understand Hashtable.
package java.util;
import java.io. *;
public class Hashtable <K, V>
extends Dictionary <K, V>
implements Map <K, V>, Cloneable, java.io.Serializable {
// Hashtable holds an array of key-values.
// Hashtable is implemented using the zipper method, each Entry is essentially a one-way linked list
private transient Entry [] table;
// actual number of elements in Hashtable
private transient int count;
// Threshold, used to determine whether the capacity of Hashtable needs to be adjusted (threshold = capacity * load factor)
private int threshold;
// load factor
private float loadFactor;
// number of times the Hashtable was changed
private transient int modCount = 0;
// serial version number
private static final long serialVersionUID = 1421746759512286392L;
// Constructor specifying "capacity size" and "load factor"
public Hashtable (int initialCapacity, float loadFactor) {
if (initialCapacity <0)
throw new IllegalArgumentException ("Illegal Capacity:" +
initialCapacity);
if (loadFactor <= 0 || Float.isNaN (loadFactor))
throw new IllegalArgumentException ("Illegal Load:" + loadFactor);
if (initialCapacity == 0)
initialCapacity = 1;
this.loadFactor = loadFactor;
table = new Entry [initialCapacity];
threshold = (int) (initialCapacity * loadFactor);
}
// Constructor specifying "capacity size"
public Hashtable (int initialCapacity) {
this (initialCapacity, 0.75f);
}
// The default constructor.
public Hashtable () {
// The default constructor, the specified capacity is 11; the load factor is 0.75
this (11, 0.75f);
}
// Constructor with "Child Map"
public Hashtable (Map <? extends K,? extends V> t) {
this (Math.max (2 * t.size (), 11), 0.75f);
// Add all elements of "Child Map" to Hashtable
putAll (t);
}
public synchronized int size () {
return count;
}
public synchronized boolean isEmpty () {
return count == 0;
}
// return the enumeration object of "all keys"
public synchronized Enumeration <K> keys () {
return this. <K> getEnumeration (KEYS);
}
// return the enumeration object of "all values"
public synchronized Enumeration <V> elements () {
return this. <V> getEnumeration (VALUES);
}
// determine whether Hashtable contains "value"
public synchronized boolean contains (Object value) {
// The value of "key-value pair" in Hashtable cannot be null,
// If it is null, throw an exception!
if (value == null) {
throw new NullPointerException ();
}
// Iterate through the elements of the table array from back to front (Entry)
// For each Entry (one-way linked list), iterate one by one to determine whether the value of the node is equal to value
Entry tab [] = table;
for (int i = tab.length; i--> 0;) {
for (Entry <K, V> e = tab [i]; e! = null; e = e.next) {
if (e.value.equals (value)) {
return true;
}
}
}
return false;
}
public boolean containsValue (Object value) {
return contains (value);
}
// determine if the Hashtable contains a key
public synchronized boolean containsKey (Object key) {
Entry tab [] = table;
int hash = key.hashCode ();
// calculate the index value,
// The purpose of% tab.length is to prevent data from going out of bounds
int index = (hash & 0x7FFFFFFF)% tab.length;
// Find the "Entry corresponding to the key (linked list)", and then find the elements in the linked list whose "hash value" and "key value" are equal to key
for (Entry <K, V> e = tab [index]; e! = null; e = e.next) {
if ((e.hash == hash) && e.key.equals (key)) {
return true;
}
}
return false;
}
// return the value corresponding to the key, or null if there is no
public synchronized V get (Object key) {
Entry tab [] = table;
int hash = key.hashCode ();
// calculate the index value,
int index = (hash & 0x7FFFFFFF)% tab.length;
// Find the "Entry corresponding to the key (linked list)", and then find the "hash value" and "key value" and key elements that are equal
for (Entry <K, V> e = tab [index]; e! = null; e = e.next) {
if ((e.hash == hash) && e.key.equals (key)) {
return e.value;
}
}
return null;
}
// Adjust the length of the Hashtable to make it the original length (2 times +1)
// (01) Assign "old Entry array" to a temporary variable.
// (02) create a "new Entry array" and assign it to "old Entry array"
// (03) Add all elements in "Hashtable" to "New Entry Array"
protected void rehash () {
int oldCapacity = table.length;
Entry [] oldMap = table;
int newCapacity = oldCapacity * 2 + 1;
Entry [] newMap = new Entry [newCapacity];
modCount ++;
threshold = (int) (newCapacity * loadFactor);
table = newMap;
for (int i = oldCapacity; i--> 0;) {
for (Entry <K, V> old = oldMap [i]; old! = null;) {
Entry <K, V> e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF)% newCapacity;
e.next = newMap [index];
newMap [index] = e;
}
}
}
// add "key-value" to Hashtable
public synchronized V put (K key, V value) {
// Cannot insert elements with null value in Hashtable! !! !!
if (value == null) {
throw new NullPointerException ();
}
// If "a key-value pair with key already exists in Hashtable",
// replace "old value" with "new value"
Entry tab [] = table;
int hash =key.hashCode ();
int index = (hash & 0x7FFFFFFF)% tab.length;
for (Entry <K, V> e = tab [index]; e! = null; e = e.next) {
if ((e.hash == hash) && e.key.equals (key)) {
V old = e.value;
e.value = value;
return old;
}
}
// If "the key-value pair whose key is key does not exist in Hashtable",
// (01) +1 "Modify Statistics"
modCount ++;
// (02) if "Hashtable actual capacity"> "threshold" (threshold = total capacity * load factor)
// resize the Hashtable
if (count> = threshold) {
// Rehash the table if the threshold is exceeded
rehash ();
tab = table;
index = (hash & 0x7FFFFFFF)% tab.length;
}
// (03) save the Entry (linked list) at "index" in Hashtable to e
Entry <K, V> e = tab [index];
// (04) Create a "New Entry node", insert "New Entry" into "index position of Hashtable", and set e to the next element of "New Entry" (that is, "New Entry" is a linked list head).
tab [index] = new Entry <K, V> (hash, key, value, e);
// (05) +1 "actual capacity of Hashtable"
count ++;
return null;
}
// delete the element with key in Hashtable
public synchronized V remove (Object key) {
Entry tab [] = table;
int hash = key.hashCode ();
int index = (hash & 0x7FFFFFFF)% tab.length;
// Find "Entry corresponding to key (linked list)"
// Then find the node to delete in the linked list and delete the node.
for (Entry <K, V> e = tab [index], prev = null; e! = null; prev = e, e = e.next) {
if ((e.hash == hash) && e.key.equals (key)) {
modCount ++;
if (prev! = null) {
prev.next = e.next;
} else {
tab [index] = e.next;
}
count--;
V oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
// Add all elements of "Map (t)" to Hashtable one by one
public synchronized void putAll (Map <? extends K,? extends V> t) {
for (Map.Entry <? extends K,? extends V> e: t.entrySet ())
put (e.getKey (), e.getValue ());
}
// Clear Hashtable
// Set all values of Hashtable's table array to null
public synchronized void clear () {
Entry tab [] = table;
modCount ++;
for (int index = tab.length; --index> = 0;)
tab [index] = null;
count = 0;
}
// Clone a Hashtable and return it as an Object.
public synchronized Object clone () {
try {
Hashtable <K, V> t = (Hashtable <K, V>) super.clone ();
t.table = new Entry [table.length];
for (int i = table.length; i--> 0;) {
t.table [i] = (table [i]! = null)
? (Entry <K, V>) table [i] .clone (): null;
}
t.keySet = null;
t.entrySet = null;
t.values = null;
t.modCount = 0;
return t;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError ();
}
}
public synchronized String toString () {
int max = size ()-1;
if (max == -1)
return "{}";
StringBuilder sb = new StringBuilder ();
Iterator <Map.Entry <K, V >> it = entrySet (). Iterator ();
sb.append ('(');
for (int i = 0;; i ++) {
Map.Entry <K, V> e = it.next ();
K key = e.getKey ();
V value = e.getValue ();
sb.append (key == this? "(this Map)": key.toString ());
sb.append ('=');
sb.append (value == this? "(this Map)": value.toString ());
if (i == max)
return sb.append (')'). toString ();
sb.append (",");
}
}
// Get Hashtable's enumeration class object
// If the actual size of the Hashtable is 0, return an "empty enumeration class" object;
// Otherwise, return a normal Enumerator object. (Enumerator implements iterator and enumeration two interfaces)
private <T> Enumeration <T> getEnumeration (int type) {
if (count == 0) {
return (Enumeration <T>) emptyEnumerator;
} else {
return new Enumerator <T> (type, false);
}
}
// Get the iterator of Hashtable
// If the actual size of the Hashtable is 0, return an "empty iterator" object;
// Otherwise, return a normal Enumerator object. (Enumerator implements iterator and enumeration two interfaces)
private <T> Iterator <T> getIterator (int type) {
if (count == 0) {
return (Iterator <T>) emptyIterator;
} else {
return new Enumerator <T> (type, true);
}
}
// Hashtable's "key collection". It is a Set, meaning there are no duplicate elements
private transient volatile Set <K> keySet = null;
// Hashtable's "key-value collection". It is a Set, meaning there are no duplicate elements
private transient volatile Set <Map.Entry <K, V >> entrySet = null;
// Hashtable's "key-value collection". It's a Collection, meaning there can be duplicate elements
private transient volatile Collection <V> values = null;
// Returns a KeySet object encapsulated by synchronizedSet
// The purpose of the synchronizedSet encapsulation is to add synchronized to all methods of the KeySet to achieve multi-thread synchronization
public Set <K> keySet () {
if (keySet == null)
keySet = Collections.synchronizedSet (new KeySet (), this);
return keySet;
}
// Set of Hashtable's Key.
// KeySet inherits from AbstractSet, so there are no duplicate elements in KeySet.
private class KeySet extends AbstractSet <K> {
public Iterator <K> iterator () {
return getIterator (KEYS);
}
public int size () {
return count;
}
public boolean contains (Object o) {
return containsKey (o);
}
public boolean remove (Object o) {
return Hashtable.this.remove (o)! = null;
}
public void clear () {
Hashtable.this.clear ();
}
}
// Returns an EntrySet object encapsulated by a synchronizedSet
// The purpose of the synchronizedSet encapsulation is to add synchronized to all methods of EntrySet to achieve multi-thread synchronization
public Set <Map.Entry <K, V >> entrySet () {
if (entrySet == null)
entrySet = Collections.synchronizedSet (new EntrySet (), this);
return entrySet;
}
// Hashtable's Entry collection.
// EntrySet inherits from AbstractSet, so there are no duplicate elements in EntrySet.
private class EntrySet extends AbstractSet <Map.Entry <K, V >> {
public Iterator <Map.Entry <K, V >> iterator () {
return getIterator (ENTRIES);
}
public boolean add (Map.Entry <K, V> o) {
return super.add (o);
}
// Find if the EntrySet contains Object (0)
// First, find the Entry corresponding to o in the table (Entry is a one-way linked list)
// Then, find if an Object exists in the Entry list
public boolean contains (Object o) {
if (! (o instanceof Map.Entry))
return false;
Map.Entry entry = (Map.Entry) o;
Object key = entry.getKey ();
Entry [] tab = table;
int hash = key.hashCode ();
int index = (hash & 0x7FFFFFFF)% tab.length;
for (Entry e = tab [index]; e! = null; e = e.next)
if (e.hash == hash && e.equals (entry))
return true;
return false;
}
// delete element Object (0)
// First, find the Entry corresponding to o in the table (Entry is a one-way linked list)
// Then delete the element in the linked list Object
public boolean remove (Object o) {
if (! (o instanceof Map.Entry))
return false;
Map.Entry <K, V> entry = (Map.Entry <K, V>) o;
K key = entry.getKey ();
Entry [] tab = table;
int hash = key.hashCode ();
int index = (hash & 0x7FFFFFFF)% tab.length;
for (Entry <K, V> e = tab [index], prev = null; e! = null;
prev = e, e = e.next) {
if (e.hash == hash && e.equals (entry)) {
modCount ++;
if (prev! = null)
prev.next = e.next;
else
tab [index] = e.next;
count--;
e.value = null;
return true;
}
}
return false;
}
public int size () {
return count;
}
public void clear () {
Hashtable.this.clear ();
}
}
// return a ValueCollection object encapsulated by synchronizedCollection
// The purpose of the synchronizedCollection encapsulation is to add synchronized to all methods of ValueCollection to achieve multi-thread synchronization
public Collection <V> values () {
if (values == null)
values = Collections.synchronizedCollection (new ValueCollection (),
this);
return values;
}
// A collection of Hashtable values.
// ValueCollection inherits from AbstractCollection, so the elements in ValueCollection can be repeated.
private class ValueCollection extends AbstractCollection <V> {
public Iterator <V> iterator () {
return getIterator (VALUES);
}
public int size () {
return count;
}
public boolean contains (Object o) {
return containsValue (o);
}
public void clear () {
Hashtable.this.clear ();
}
}
// re-equals () function
// If all key-value pairs of two Hashtables are equal, judge them to be equal
public synchronized boolean equals (Object o) {
if (o == this)
return true;
if (! (o instanceof Map))
return false;
Map <K, V> t = (Map <K, V>) o;
if (t.size ()! = size ())
return false;
try {
// Take out the key-value key-value pairs of the current Hashtable through the iterator
// And judge that the key-value pair exists in Hashtable (o).
// If it does not exist, return false immediately; otherwise, iterate over the "current Hashtable" and return true.
Iterator <Map.Entry <K, V >> i = entrySet (). Iterator ();
while (i.hasNext ()) {
Map.Entry <K, V> e = i.next ();
K key = e.getKey ();
V value = e.getValue ();
if (value == null) {
if (! (t.get (key) == null && t.containsKey (key)))
return false;
} else {
if (! value.equals (t.get (key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
// Calculate the hash value of Hashtable
// If the actual size of the Hashtable is 0 or the load factor is <0, then 0 is returned.
// Otherwise, return "the sum of the XOR value of the key and value of each Entry in the Hashtable".
public synchronized int hashCode () {
int h = 0;
if (count == 0 || loadFactor <0)
return h; // Returns zero
loadFactor = -loadFactor; // Mark hashCode computation in progress
Entry [] tab = table;
for (int i = 0; i <tab.length; i ++)
for (Entry e = tab [i]; e! = null; e = e.next)
h + = e.key.hashCode () ^ e.value.hashCode ();
loadFactor = -loadFactor; // Mark hashCode computation complete
return h;
}
// Write function of java.io.Serializable
// write "total capacity, actual capacity, all Entry" of Hashtable to the output stream
private synchronized void writeObject (java.io.ObjectOutputStream s)
throws IOException
{
// Write out the length, threshold, loadfactor
s.defaultWriteObject ();
// Write out length, count of elements and then the key / value objects
s.writeInt (table.length);
s.writeInt (count);
for (int index = table.length-1; index> = 0; index--) {
Entry entry = table [index];
while (entry! = null) {
s.writeObject (entry.key);
s.writeObject (entry.value);
entry = entry.next;
}
}
}
// java.io.Serializable read function: read according to the write mode
// Read the "Total Capacity, Actual Capacity, All Entry" of Hashtable in turn
private void readObject (java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
// Read in the length, threshold, and loadfactor
s.defaultReadObject ();
// Read the original length of the array and number of elements
int origlength = s.readInt ();
int elements = s.readInt ();
// Compute new size with a bit of room 5% to grow but
// no larger than the original size. Make the length
// odd if it's large enough, this helps distribute the entries.
// Guard against the length ending up zero, that's not valid.
int length = (int) (elements * loadFactor) + (elements / 20) + 3;
if (length> elements && (length & 1) == 0)
length--;
if (origlength> 0 && length> origlength)
length = origlength;
Entry [] table = new Entry [length];
count = 0;
// Read the number of elements and then all the key / value objects
for (; elements> 0; elements--) {
K key = (K) s.readObject ();
V value = (V) s.readObject ();
// synch could be eliminated for performance
reconstitutionPut (table, key, value);
}
this.table = table;
}
private void reconstitutionPut (Entry [] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException ();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
int hash = key.hashCode ();
int index = (hash & 0x7FFFFFFF)% tab.length;
for (Entry <K, V> e = tab [index]; e! = null; e = e.next) {
if ((e.hash == hash) && e.key.equals (key)) {
throw new java.io.StreamCorruptedException ();
}
}
// Creates the new entry.
Entry <K, V> e = tab [index];
tab [index] = new Entry <K, V> (hash, key, value, e);
count ++;
}
// Hashtable's Entry node, which is essentially a one-way linked list.
// Therefore, we can infer that Hashtable is a hash table implemented by zipper
private static class Entry <K, V> implements Map.Entry <K, V> {
// hash value
int hash;
K key;
V value;
// The next Entry pointed to, that is, the next node of the linked list
Entry <K, V> next;
// Constructor
protected Entry (int hash, K key, V value, Entry <K, V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
protected Object clone () {
return new Entry <K, V> (hash, key, value,
(next == null? null: (Entry <K, V>) next.clone ()));
}
public K getKey () {
return key;
}
public V getValue () {
return value;
}
// Set value. If value is null, an exception is thrown.
public V setValue (V value) {
if (value == null)
throw new NullPointerException ();
V oldValue = this.value;
this.value = value;
return oldValue;
}
// Override the equals () method to determine if the two Entry are equal.
// If the keys and values of two Entry are equal, they are considered equal.
public boolean equals (Object o) {
if (! (o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry) o;
return (key == null? e.getKey () == null: key.equals (e.getKey ())) &&
(value == null? e.getValue () == null: value.equals (e.getValue ()));
}
public int hashCode () {
return hash ^ (value == null? 0: value.hashCode ());
}
public String toString () {
return key.toString () + "=" + value.toString ();
}
}
private static final int KEYS = 0;
private static final int VALUES = 1;
private static final int ENTRIES = 2;
// The function of Enumerator is to provide "the interface to traverse Hashtable through elements ()" and "the interface to traverse Hashtable through entrySet ()". Because it implements both the "Enumerator interface" and the "Iterator interface".
private class Enumerator <T> implements Enumeration <T>, Iterator <T> {
// table pointing to Hashtable
Entry [] table = Hashtable.this.table;
// total size of Hashtable
int index = table.length;
Entry <K, V> entry = null;
Entry <K, V> lastReturned = null;
int type;
// Flag of Enumerator as "Iterator" or "Enumeration"
// iterator is true, indicating that it is an iterator; otherwise, it is an enum class.
boolean iterator;
// It will be used when Enumerator is used as an iterator to implement fail-fast mechanism.
protected int expectedModCount = modCount;
Enumerator (int type, boolean iterator) {
this.type = type;
this.iterator = iterator;
}
// Look forward from the end of the array that traverses the table until an Entry that is not null is found.
public boolean hasMoreElements () {
Entry <K, V> e = entry;
int i = index;
Entry [] t = table;
/ * Use locals for faster loop iteration * /
while (e == null && i> 0) {
e = t [-i];
}
entry = e;
index = i;
return e! = null;
}
// get the next element
// Note: From hasMoreElements () and nextElement () you can see "Hashtable elements () traversal mode"
// First, traverse the table array from back to front. Each node of the table array is a one-way linked list (Entry).
// Then, iterate backward through the one-way linked list Entry.
public T nextElement () {
Entry <K, V> et = entry;
int i = index;
Entry [] t = table;
/ * Use locals for faster loop iteration * /
while (et == null && i> 0) {
et = t [-i];
}
entry = et;
index = i;
if (et! = null) {
Entry <K, V> e = lastReturned = entry;
entry = e.next;
return type == KEYS? (T) e.key: (type == VALUES? (T) e.value: (T) e);
}
throw new NoSuchElementException ("Hashtable Enumerator");
}
// Iterator determines whether the next element exists
// Actually, it is hasMoreElements () called
public boolean hasNext () {
return hasMoreElements ();
}
// the iterator gets the next element
// Actually, it is called nextElement ()
public T next () {
if (modCount! = expectedModCount)
throw new ConcurrentModificationException ();
return nextElement ();
}
// The iterator's remove () interface.
// First, it finds the Entry in the table array where the element is to be deleted,
// Then delete the elements in the one-way linked list Entry.
public void remove () {
if (! iterator)
throw new UnsupportedOperationException ();
if (lastReturned == null)
throw new IllegalStateException ("Hashtable Enumerator");
if (modCount! = expectedModCount)
throw new ConcurrentModificationException ();
synchronized (Hashtable.this) {
Entry [] tab = Hashtable.this.table;
int index = (lastReturned.hash & 0x7FFFFFFF)% tab.length;
for (Entry <K, V> e = tab [index], prev = null; e! = null;
prev = e, e = e.next) {
if (e == lastReturned) {
modCount ++;
expectedModCount ++;
if (prev == null)
tab [index] = e.next;
else
prev.next = e.next;
count--;
lastReturned = null;
return;
}
}
throw new ConcurrentModificationException ();
}
}
}
private static Enumeration emptyEnumerator = new EmptyEnumerator ();
private static Iterator emptyIterator = new EmptyIterator ();
// empty enum class
// When the actual size of the Hashtable is 0; at this time, when the Hashtable is traversed through Enumeration, the object of the "empty enumeration class" is returned.
private static class EmptyEnumerator implements Enumeration <Object> {
EmptyEnumerator () {
}
// hasMoreElements () for empty enums always returns false
public boolean hasMoreElements () {
return false;
}
// nextElement () of an empty enum class throws an exception
public Object nextElement () {
throw new NoSuchElementException ("Hashtable Enumerator");
}
}
// empty iterator
// When the actual size of the Hashtable is 0; at this time, when the Hashtable is traversed through the iterator, the object of "empty iterator" is returned.
private static class EmptyIterator implements Iterator <Object> {
EmptyIterator () {
}
public boolean hasNext () {
return false;
}
public Object next () {
throw new NoSuchElementException ("Hashtable Iterator");
}
public void remove () {
throw new IllegalStateException ("Hashtable Iterator");
}
}
}