I am having a problem with JAVA map. I enter an object as a key in the map. Then I modify the key and the map does not consider the object as a key of the map any more. Even though the key inside the object has been modified accordingly.
I am working with the object CoreLabel from StanfordNLP but it applies to a general case I guess.
Map <CoreLabel, String> myMap = new HashMap...
CoreLabel key = someCreatedCoreLabel
myMap.put(key, someString)
myMap.get(key) != null ----> TRUE
key.setValue("someValue");
myMap.get(key) != null ----> FALSE
I hope I was clear enough. The question is why is the last statement false? I am not a very experienced programmer but I would expect it to be true. Maybe has something to do with the CoreLabel object?
I check if .equals() still holds, and it actually does
for(CoreLabel token: myMap.keySet()) {
if(key.equals(token))
System.out.println("OK");
}
This is explicitly documented in the Map Javadoc as dangerous and unlikely to work:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map. A special case of this prohibition is that it is not permissible for a map to contain itself as a key. While it is permissible for a map to contain itself as a value, extreme caution is advised: the equals and hashCode methods are no longer well defined on such a map.
The problem is that in modifying the value of the key, now the hash code of the key has changed as well. A HashMap will first use the hash code of the key to determine if it exists. The modified hash code didn't exist in the map, so it didn't even get to try the step of using the equals method. That's why it's a bad idea to change your key objects while they're in a HashMap.
Related
I was fetching key from a constant map earlier using HashMap.
On passing a NULL key at containsKey(), I used to get FALSE.
To make the code look fancy, I tried java-8 over it. So, instead of HashMap, I started using Map.ofEntries to build my map
Surprisingly, I got Null Pointer Exception when a Null key was passed to containsKey() method
String str = null;
Map<String,String> hashMap = new HashMap<>();
hashMap.put("k1", "v1");
System.out.print(hashMap.containsKey(str)); // This gives false
Map<String,String> ofEntriesMap = Map.ofEntries( Map.entry("k1", "v1"));
System.out.print(ofEntriesMap.containsKey(str)); // Why this gives Null Pointer Exception ?
I am unable to figure out, why it is behaving differently at Map.ofEntries.
What is the best way to handle this situation ?
The javadoc of Map says:
Unmodifiable Maps
The Map.of, Map.ofEntries, and Map.copyOf static factory methods provide a convenient way to create unmodifiable maps. The Map instances created by these methods have the following characteristics:
They are unmodifiable. Keys and values cannot be added, removed, or updated. Calling any mutator method on the Map will always cause UnsupportedOperationException to be thrown. However, if the contained keys or values are themselves mutable, this may cause the Map to behave inconsistently or its contents to appear to change.
They disallow null keys and values. Attempts to create them with null keys or values result in NullPointerException.
...
In contrast, the javadoc of HashMap says:
Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time.
...
instead of HashMap, I started using Map.ofEntries to build my map
Surprisingly, I got Null Pointer Exception when a Null key was passed
to containsKey() method
The documentation for java.util.Map says, in part:
Some map implementations have restrictions on the keys and values they may contain. For example, some implementations prohibit null
keys and values, and some have restrictions on the types of their
keys. Attempting to insert an ineligible key or value throws an
unchecked exception, typically NullPointerException or
ClassCastException. Attempting to query the presence of an
ineligible key or value may throw an exception, or it may simply
return false; some implementations will exhibit the former behavior
and some will exhibit the latter.
(Emphasis added.)
As #Andreas's answer already observes, the maps created via Map.ofEntries() are of such an implementation. Specifically, they disallow null keys and values. Although it is not documented whether their containsKey() methods exercise the option to throw when presented with a null argument, you need to use them with that possibility in mind.
On the other hand, as Andreas also shows, HashMap is documented to permit null keys and values, so its containsKey() method is expected to complete normally when passed a null argument.
What is the best way to handle this situation ?
You have two main choices:
If you want to continue to (directly) use a map created via Map.ofEntries() then you must avoid testing whether it contains null keys. Since you know that it cannot contain null keys, such tests are unnecessary.
If you want to rely on being able to test null keys' presence in your map, and especially if you want the option of having null keys or null values in it, then you must not use Map.ofEntries() to create it. You might, however, use Map.ofEntries() to initialize it. For example:
Map<String, String> myMap = Collections.unmodifiableMap(
new HashMap<String, String>(
Map.ofEntries(
Map.Entry("k1", "v1")
)
)
);
Note also that if you are putting fewer than 11 entries in your map, then Map.of() is a bit tidier than Map.ofEntries(). And, of course, if you don't care whether the map is modifiable then you don't have to put it into that unmodifiable wrapper.
This is implementation detail of the unmodifiable map, created by Map.ofEntries.
When you're adding null key to HashMap, it calculates hash of null equal to 0.
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
But Map.ofEntries creates ImmutableCollections.Map1 in case when only one pair was provided and ImmutableCollections.MapN otherwise.
Here is implementation of ImmutableCollections.Map1::containsKey
public boolean containsKey(Object o) {
return o.equals(k0); // implicit nullcheck of o
}
You can see that comment says that NullPointerException is expected behaviour. As for ImmutableCollections.MapN::containsKey it uses explicit null-check.
public boolean containsKey(Object o) {
Objects.requireNonNull(o);
return size > 0 && probe(o) >= 0;
}
If you refer Map::containsKey Javadoc, you can see that it's explicitly said that this method may or may not produce NPE.
Returns true if this map contains a mapping for the specified key. More
formally, returns true if and only if this map contains a mapping for a key k such that Objects.equals(key, k). (There can be at most one such mapping.)
Params:
key – key whose presence in this map is to be tested
Returns:
true if this map contains a mapping for the specified key
Throws:
ClassCastException – if the key is of an inappropriate type for this map (optional)
NullPointerException – if the specified key is null and this map does not permit null keys (optional)
Today I was fixing one defect and found a very interesting thing.
I was trying to put a key value pair in hashmap. (I was assuming that key is there but later it was found to be null).
So while retrieving the value using a key , I was not getting null every time.
Later I found that key is null , I corrected it.
But then I see the code for put method of Hashmap.
Why does it does not gives an exception when the key is null ?
It calls putForNullKey private method. What does it do?
But i am thinking it should give some exception if the key is null.
Why they have not implemented it like that?
Key Value pair is not useful if a key is null?
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/HashMap.java#HashMap.putForNullKey%28java.lang.Object%29
From the Javadoc for HashMap:
Hash table based implementation of the Map interface.
This implementation provides all of the optional map
operations, and permits null values and the null key
This is by design, and it is documented. The 'why' question is hard to answer, but it has been that way since HashMap (and before that, Hashtable) were introduced (Edit, actually, Hashtable did not allow nulls).
As to why it has the putForNullKey, well HashMap relies on the hashCode() of a key to place it, and, since it can't get the hashCode() from a null value, it has to do it as a special case.
HashMap allows only one null key. So whenever any one want to put some value with null it calls putForNullKey method inside the put method and in putForNullKey method it places the value at zero index.
For getting value by null key it also has a method getForNullKey which it calls from inside the get method.
It's one of the differences between a Hashtable and a HashMap.
Hashtable does not allow null keys or values. HashMap allows one null key and any number of null values.
Usually, we use the null key to represent the default case (i.e. the value that should be used if a given key isn't present)
Say you have a key class (KeyClass) with overridden equals, hashCode and clone methods. Assume that it has 2 primitive fields, a String (name) and an int (id).
Now you define
KeyClass keyOriginal, keyCopy, keyClone;
keyOriginal = new KeyClass("original", 1);
keyCopy = new KeyClass("original", 1);
keyClone = KeyClass.clone();
Now
keyOriginal.hashCode() == keyCopy.hashCode() == keyClone.hashCode()
keyOriginal.equals(keyCopy) == true
keyCopy.equals(keyClone) == true
So as far as a HashMap is concerned, keyOriginal, keyCopy and keyClone are indistinguishable.
Now if you put an entry into the HashMap using keyOriginal, you can retrieve it back using keyCopy or keyClone, ie
map.put(keyOriginal, valueOriginal);
map.get(keyCopy) will return valueOriginal
map.get(keyClone) will return valueOriginal
Additionally, if you mutate the key after you have put it into the map, you cannot retrieve the original value. So for eg
keyOriginal.name = "mutated";
keyOriginal.id = 1000;
Now map.get(keyOriginal) will return null
So my question is
when you say map.keySet(), it will return back all the keys in the map. How does the HashMap class know what are the complete list of keys, values and entries stored in the map?
EDIT
So as I understand it, I think it works by making the Entry key as a final variable.
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
(docjar.com/html/api/java/util/HashMap.java.html). So even if I mutate the key after putting it into the map, the original key is retained. Is my understanding correct? But even if the original key reference is retained, one can still mutate its contents. So if the contents are mutated, and the K,V is still stored in the original location, how does retrieval work?
EDIT
retrieval will fail if you mutate the key after putting into the hashmap. Hence it is not recommended that you have mutable hashmap keys.
HashMap maintains a table of entries, with references to the associated keys and values, organized according to their hash code. If you mutate a key, then the hash code will change, but the entry in HashMap is still placed in the hash table according to the original hash code. That's why map.get(keyOriginal) will return null.
map.keySet() just iterates over the hash table, returning the key of each entry it has.
If you change the entry but not the hashCode, you are safe. For this reason it is considered best practice to make all fields in the hashCode, equals and compareTo, both final and immutable.
Simply put, the HashMap is an object in your computer's memory that contains keys and values. Each key is unique (read about hashcode), and each key points to a single value.
In your code example, the value coming out of your map in each case is the same because the key is the same. When you changed your key, there is no way to get a value for it because you never added an item to your HashMap with the mutated key.
If you added the line:
map.put("mutated", 2);
Before mutating the key, then you will no longer get a null value.
Hashtable does not allow null keys or values, while HashMap allows null values and 1 null key.
Questions:
Why is this so?
How is it useful to have such a key and values in HashMap?
1. Why is this so?
HashMap is newer than Hashtable and fixes some of its limitations.
I can only guess what the designers were thinking, but here are my guesses:
Hashtable calculates a hash for each key by calling hashCode on each key. This would fail if the key were null, so this could be a reason for disallowing nulls as keys.
The method Hashtable.get returns null if the key is not present. If null were a valid value it would be ambiguous as to whether null meant that the key was present but had value null, or if the key was absent. Ambiguity is bad, so this could be a reason for disallowing nulls as values.
However it turns out that sometimes you do actually want to store nulls so the restrictions were removed in HashMap. The following warning was also included in the documentation for HashMap.get:
A return value of null does not necessarily indicate that the map contains no mapping for the key; it is also possible that the map explicitly maps the key to null.
2. How is it useful to have such a key and values in HashMap?
It is useful to explicitly store null to distinguish between a key that you know exists but doesn't have an associated value and a key that doesn't exist. An example is a list of registered users and their birthdays. If you ask for a specific user's birthday you want to be able to distinguish between that user not existing and the user existing but they haven't entered their birthday.
I can't think of any (good) reason for wanting to store null as a key, and in general I'd advise against using null as a key, but presumably there is at least one person somewhere that needs that keys that can be null.
Well, I think Mark Byers answered perfectly, so just a simple example where null values and keys can be useful:
Imagine you have an expensive function that always returns the same result for the same input. A map is a simple way for caching its results. Maybe sometimes the function will return null, but you need to save it anyway, because the execution is expensive. So, null values must be stored. The same applies to null key if it's an accepted input for the function.
HashTable is very old class , from JDK 1.0.The classes which are in place from JDK 1.0 are called Legacy classes and by default they are synchronized.
To understand this, first of all you need to understand comments written on this class by author.
“This class implements a hashtable, which maps keys to values. Any non-null object can be used as a key or as a value. To successfully store and retrieve objects from a hashtable, the objects used as keys must implement the hashCode method and the equals method.”
HashTable class is implemented on hashing mechanism, means to store any key-value pair, its required hash code of key object. HashTable calculates a hash for each key by calling hashCode on each key. This would fail If key would be null, it will not be able to give hash for null key, it will throw NullPointerException and similar is the case for value, throwing null if the value is null.
But later on it was realised that null key and value has its own importance, then revised implementations of HashTable were introduced like HashMap which allow one null key and multiple null values.
For HashMap, it allows one null key and there is a null check for keys, if the key is null then that element will be stored in a zero location in Entry array.
We cannot have more than one Null key in HashMap because Keys are unique therefor only one Null key and many Null values are allowed.
USE - Null key we can use for some default value.
Modified and better implementation of HashTable was later introduced as ConcurrentHashMap.
In addition to what answered by Mark Bayers,,
Null is considered as data and it has to be stored as a value for further checking. In many cases null as value can be used to check if key entry is there but no value is assigned to it, so some action can be taken accordingly. This can be done by first checking if key is there, and then getting value.
There is one more case in which just put whatever data is coming(without any check). All the checks are applied to it after getting it.
Whereas null as a key, i think can be used to define some default data. Usually null as a key does not make much sense.
Sir HashMap is also internally uses hashCode() method for inserting an element in HashMap, so I think this will be not the proper reason for "why HashTable allow null key"
It will make the Map interface easier to use / less verbose. null is a legitimate value for reference types. Making the map able to handle null keys and values will eliminate the need for null checking before calling the api. So the map api create less "surprises" during runtime.
For example, it is common that map will be used to categorize a collection of homogeneous objects based a single field. When map is compatible with null, the code will be more concise as it is just a simple loop without any if statement (of course you will need to make sure the collection does not have null elements). Fewer lines of code with no branch / exception handling will be more likely to be logically correct.
On the other hand, not allowing null will not make the map interface better/safer/easier to use. It is not practical to rely on the map to reject nulls - that means exception will be thrown and you have to catch and handle it. Or, to get rid of the exception you will have to ensure nothing is null before calling the map methods - in which case you don't care if map accepts null since you filtered the input anyway.
I'm profiling some old java code and it appears that my caching of values using a static HashMap and a access method does not work.
Caching code (a bit abstracted):
static HashMap<Key, Value> cache = new HashMap<Key, Value>();
public static Value getValue(Key key){
System.out.println("cache size="+ cache.size());
if (cache.containsKey(key)) {
System.out.println("cache hit");
return cache.get(key);
} else {
System.out.println("no cache hit");
Value value = calcValue();
cache.put(key, value);
return value;
}
}
Profiling code:
for (int i = 0; i < 100; i++)
{
getValue(new Key());
}
Result output:
cache size=0
no cache hit
(..)
cache size=99
no cache hit
It looked like a standard error in Key's hashing code or equals code.
However:
new Key().hashcode == new Key().hashcode // TRUE
new Key().equals(new Key()) // TRUE
What's especially weird is that cache.put(key, value) just adds another value to the hashmap, instead of replacing the current one.
So, I don't really get what's going on here. Am I doing something wrong?
edit
Ok, I see that in the real code the Key gets used in other methods and changes, which therefore get's reflected in the hashCode of the object in the HashMap. Could that be the cause of this behaviour, that it goes missing?
On a proper #Override of equals/hashCode
I'm not convinced that you #Override (you are using the annotation, right?) hashCode/equals properly. If you didn't use #Override, you may have defined int hashcode(), or boolean equals(Key), neither of which would do what is required.
On key mutation
If you are mutating the keys of the map, then yes, trouble will ensue. From the documentation:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.
Here's an example:
Map<List<Integer>,String> map =
new HashMap<List<Integer>,String>();
List<Integer> theOneKey = new ArrayList<Integer>();
map.put(theOneKey, "theOneValue");
System.out.println(map.containsKey(theOneKey)); // prints "true"
theOneKey.add(42);
System.out.println(map.containsKey(theOneKey)); // prints "false"
By the way, prefer interfaces to implementation classes in type declarations. Here's a quote from Effective Java 2nd Edition: Item 52: Refer objects by their interfaces
[...] you should favor the use of interfaces rather than classes to refer to objects. If appropriate interface types exist, then parameters, return values, variables, and fields should all be declared using interface types.
In this case, if at all possible, you should declare cache as simply a Map instead of a HashMap.
I'd recommend double and triple checking the equals and hashCode methods. Note that it's hashCode, not hashcode.
Looking at the (abstracted) code, everything seems to be in order. It may be that the actual code is not like your redacted version, and that this is more a reflection of how you expect the code to work and not what is happening in practice!
If you can post the code, please do that. In the meantime, here are some pointers to try:
After adding a Key, use exactly the same Key instance again, and verify that it produces a cache hit.
In your test, verify the hashcodes are equal, and that the objects are equal.
Is the Map implementation really a HashMap? WeakHashMap will behave in the way you describe once the keys are no longer reachable.
I'm not sure what your Key class is, but (abstractly similarly to you) what I'd do for a simple check is:
Key k1 = new Key();
Key k2 = new Key();
System.out.println("k1 hash:" + k1.hashcode);
System.out.println("k2 hash:" + k2.hashcode);
System.out.println("ks equal:" + k1.equals(k2));
getValue(k1);
getValue(k2);
if this code shows the anomaly -- same hashcode, equal keys, yet no cache yet -- then there's cause to worry (or, better, debug your Key class;-). The way you're testing, with new Keys all the time, might produce keys that don't necessarily behave the same way.