As we know, null is not allowed in Hashtable.
But when I checked the source code of Hashtable (jdk 1.8).
I only saw the check of value and couldn't find the key check.
Here is the source code below of the put method:
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
#SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
The key check is here:
int hash = key.hashCode();
This will throw a NullPointerException if the key is null.
Related
When reading the Java Hashtable source code I noticed that the count field of Hashtable is not initialized when declare I see that in the readObject method there is this code:
count = 0;
When is the count field initialized?
readObject also calls reconstitutionPut(table, key, value) for each key-value pair, and that method increments count.
Here's the relevant code with the relevant lines marked:
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
...
count = 0;
// Read the number of elements and then all the key/value objects
for (; elements > 0; elements--) {
#SuppressWarnings("unchecked")
K key = (K)s.readObject();
#SuppressWarnings("unchecked")
V value = (V)s.readObject();
// synch could be eliminated for performance
reconstitutionPut(table, key, value); // <---------------
}
}
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<?,?> 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.
#SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++; // <---------------
}
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
I am trying to understand the HashMap implemintation. I understood everything except this line - Object k;
Please, explain how this Object k appears??
In that implementation of a HashMap, the data structure was backed by an array of linked lists of entries. These entries have a key and a value.
That variable k is used to store the key for each entry while iterating over the linked list bucket. If it's found to be equal (reference and then value equality) to the key with which you're trying to insert a value, then that value replaces the old.
I understand that HashMap doesn't allow insertion of duplicate values and it replaces the last duplicate value with the latest entry.
Is there a way to print the duplicates which were found during the put method?
I have the following code snippet:
for( int i = 0; i <= elements.length - 1; i++) {
nodeDBList = (NodeList) xPath.compile(elements[i]).evaluate(dbDocument, XPathConstants.NODESET);
for (int j = 0; j < nodeDBList.getLength(); j++) {
if(nodeDBList.item(j).getFirstChild() != null)
dbList.put(nodeDBList.item(j).getFirstChild().getNodeValue().toLowerCase().trim(),
nodeDBList.item(j).getNodeName().toLowerCase().trim());
}
}
Wrong. HashMap does not support duplicate keys, which are hashed.
Duplicate values are totally acceptable for different keys.
You can search for existing values by iterating them through the values() method and using the equals method.
Edit
There seems to be a confusion between keys and values here.
According to the HashMap implementation of Map's public V put(K key, V value);, the method put will return the original value for a given key if any, or null.
Quote from the API
#return the previous value associated with key, or null if there was
no mapping for key. (A null return can also indicate that the map
previously associated null with key.)
Well, the answer can be found in the API description of HashMap: The put method returns the value that was previously associated with the key.
Returns:
the previous value associated with key, or null if there was no mapping for key. (A null return can also indicate that the map
previously associated null with key.)
The old value of the key is returned by the put method, so you can output it.
Assuming the value of your HashMap is of type String :
for( int i = 0; i <= elements.length - 1; i++)
{
nodeDBList = (NodeList) xPath.compile(elements[i]).evaluate(dbDocument, XPathConstants.NODESET);
for (int j = 0; j < nodeDBList.getLength(); j++) {
if(nodeDBList.item(j).getFirstChild() != null) {
String oldVal = dbList.put(nodeDBList.item(j).getFirstChild().getNodeValue().toLowerCase().trim(), nodeDBList.item(j).getNodeName().toLowerCase().trim());
if (oldVal != null) {
System.out.println(oldVal);
}
}
}
}
Override the HashMap
this is an example
public class MyMap<K, V> extends HashMap<K,V> {
private static final long serialVersionUID = -1006394139781809796L;
#SuppressWarnings({ "unchecked" })
#Override
public V put(K key, V value) {
if (value == null) {
return super.put(key, value);
}
if (value.getClass() == Timestamp.class) {
DateFormat dateTimeFormatter;
dateTimeFormatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, getLocale());
super.put((K) (key + "_f"), (V) dateTimeFormatter.format(new Date(((Timestamp) value).getTime())));
DateFormat dateFormatter;
dateFormatter = DateFormat.getDateInstance(DateFormat.SHORT, getLocale());
super.put((K) (key + "_f_date"), (V) dateFormatter.format(new Date(((Timestamp) value).getTime())));
}
if (value.getClass() == java.sql.Date.class) {
DateFormat dateFormatter;
dateFormatter = DateFormat.getDateInstance(DateFormat.SHORT, getLocale());
super.put((K) (key + "_f"), (V) dateFormatter.format(new Date(((java.sql.Date) value).getTime())));
}
return super.put(key, value);
}
}
I am trying to put a map into a properties using putAll() and get a NullPointerException even when my map is not null
Map<String,Object> map = item.getProperties();
Properties props = new Properties();
if(map!=null) {
props.putAll(map); //NPE here
}
The item.getProperties() returns Map<String,Object> and I want to store those properties into a properties file.
I also tried to instantiate the map first
Map<String,Object> map = new HashMap<String, Object>()
map = item.getProperties();
Properties props = new Properties();
if(map!=null) {
props.putAll(map); //NPE here
}
I know that the map is not null, since I can see the map values in the log.
The Properties class extends Hashtable which does not accept null values for its entries.
Any non-null object can be used as a key or as a value.
If you try to put a null value, the Hashtable#put(Object, Object) method throws a NullPointerException. It's possible your
map = item.getProperties();
contains null values.
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
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;
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<K,V>(hash, key, value, e);
count++;
return null;
}
Maybe your map has null key or value.
I found that some code is changed for null keys in class HashMap in JDK 1.6 or above version compared to the previous JDK version, like 1.5.
In JDK1.5, a static final Object named NULL_KEY is defined: static final Object NULL_KEY = new Object();
Methods, including maskNull, unmaskNull, get and put etc, will use this object.
See
static final Object NULL_KEY = new Object();
static <T> T maskNull(T key) {
return key == null ? (T)NULL_KEY : key;
}
static <T> T unmaskNull(T key) {
return (key == NULL_KEY ? null : key);
}
public V get(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry<K,V> e = table[i];
while (true) {
if (e == null)
return null;
if (e.hash == hash && eq(k, e.key))
return e.value;
e = e.next;
}
}
public V put(K key, V value) {
K k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
if (e.hash == hash && eq(k, e.key)) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, k, value, i);
return null;
}
However, such Object (NULL_KEY) is not used in JDK 1.6 or above version.
Instead, two new methods named getForNullKey() and putForNullKey(value) is added, which are applied in get and put method as well.
See the source code as follows:
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
private V getForNullKey() {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
/**
* Offloaded version of put for null keys
*/
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
Change always has its reason for changing, such as improving the performance etc. Please help me out with the following 2 question
Q#1 ==> Why this change is made, is there some scenario that the null keys of HashMap implemented in JDK 1.5 encouneters issue ?
Q#2 ==> What is the advantage of null keys mechanism change of HashMap in JDK 1.6 or above version?
Documentation for private V getForNullKey() says
Offloaded version of get() to look up null keys. Null keys map to
index 0. This null case is split out into separate methods for the
sake of performance in the two most commonly used operations (get and
put), but incorporated with conditionals in others.