Infinite loop when using a key multiple times in HashMap - java

HashMap falls into an infinite loop.
I am not able to understand why HashMap throws stackoverflow error when the same key is used multiple times.
Code:
import java.util.HashMap;
public class Test {
public static void main(String[] args) {
HashMap hm = new HashMap();
hm.put(hm, "1");
hm.put(hm, "2");
}
}
Error:
Exception in thread "main" java.lang.StackOverflowError

It is not possible to add to a Map itself as a key. From javadoc:
A special case of this prohibition is that it is not permissible for a map to contain itself as a key.
The problem is that you are using as key not a standard object (any object with well defined equals and hashCode methods, that is not the map itself), but the map itself.
The problem is on how the hashCode of the HashMap is calculated:
public int hashCode() {
int h = 0;
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext())
h += i.next().hashCode();
return h;
}
As you can see, to calculate the hashCode of the map, it iterates over all the elements of the map. And for each element, it calculates the hashCode. Because the only element of the map has as key is the map itself, it becomes a source of recursion.
Replacing the map with another object that can be used as key (with well defined equals and hashCode) will work:
import java.util.HashMap;
public class Test {
public static void main(String[] args) {
HashMap hm = new HashMap();
String key = "k1";
hm.put(key, "1");
hm.put(key, "2");
}
}

Problem is not that hash map blows up the stack for "same key" entered twice, but because your particular choice of map key. You are adding hash map to itself.
To explain better - part of Map contract is that keys must not change in a way that affects their equals (or hashCode for that matter) methods.
When you added map to itself as a key, you changed key (map) in a way that is making it return different hashCode than when you first added map.
For more information this is from JDK dock for Map interface:
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.

In order to locate a key in the HashMap (which is done whenever you call put or get or containsKey), hashCode method is called for the key.
For HashMap, hashCode() is a function of the hashCode() of all the entries of the Map, and each entry's hashCode is a function of the key's and value's hashCodes. Since your key is the same HashMap instance, computing the hashCode of that key causes an infinite recursion of hashCode method calls, leading to StackOverflowError.
Using a HashMap as a key to a HashMap is a bad idea.

You are using same object name as a key so it is infinite loop.
so it is stack overflow.

You use as key the hashmap itself -> that means recursion -> no exit condition -> StackOverflow.
Just use a key (Long, String, Object anything else you want).
And yeah, as Seek Addo suggest, add types with <> brackets.

Related

What keys should I use for HashMap?

What key is the best for HashMap?
I used just decimal, each key is previous++, but it's just my idea i don't know if it efficient.
I read about hashCode, this value commonly used for hash tables, but people say to not misuse hashCode() as a key.
Waiting for your answers and links to resources.
Here's code snippet:
Identifier identifier = new Identifier();
identifier.setName(getString(currentToken));
identifier.setLine(currentLineNumber);
int key = identifier.hashCode();
tableOfIdentifiers.put(key, identifier);
It is extremely rare for user's code to call hashCode directly outside of implementations of hashCode methods for custom objects. In particular, in your case the call is unnecessary, because HashMap and HashSet rely upon calling hashCode internally.
From your example it does not appear that you need HashMap: a HashSet should be sufficient.
private Set<Identifier> tableOfIdentifiers = new HashSet<Identifier>();
...
if (!tableOfIdentifiers.add(identifier)) {
... // Duplicate identifier is detected
}
Ideally, maps are used to have some key values pairs. The keys should be unique and understandable. Maps might have duplicate values for different keys, but if use hashcode as the key the map will overwrite your previous value. Try using logical names as keys. could be empcode, studentRollNumber etc.

Java Modifying key object inside map

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.

How could a LinkedHashMap fail to find an entry produced by an iterator?

Under what circumstances given a correct implementation of hashCode and equals() can the following code return false?
myLinkedHashMap.containsKey(myLinkedHashMap.keySet().iterator().next())
Most likely scenario I can think of would be even though hashCode is "deterministic", it may be based on mutable fields. If you change the fields used to compute hashCode after it's put in the Map, then you won't be able to find it anymore.
Edit: should clarify you 'usually' won't be able to find it anymore. Occasionally it will still work since two numbers can still rehash into the same bucket. This, of course, only adds to the confusion when it happens!
Every hash algorithm I have seen is "deterministic", in that for a given set of input values, you get the same hash value.
If the hash code is computed based on mutable properties of the object, the hash code will change after it's in the hash map if any of those mutable properties are changed.
It's not clear what you mean by "deterministic", but any hash-changing mutation to the key after it's been inserted into the hash map could easily have that effect.
import java.util.*;
public class Test {
public static void main(String[] args) {
List<String> strings = new ArrayList<String>();
Map<List<String>, String> map = new LinkedHashMap<List<String>, String>();
map.put(strings, "");
System.out.println(map.containsKey(map.keySet().iterator().next())); // true
strings.add("Foo");
System.out.println(map.containsKey(map.keySet().iterator().next())); // false
}
}
The hash code of ArrayList<T> is deterministic, but that doesn't mean it won't change if the contents of the list changes.
If the hashCode() is based on instance attributes that are mutable and those attributes are changed after the insertion, the hashCode() call during the iteration will return something different. And the equals() should be based on these same attributes, it will be expected to fail as well.
When another thread has removed all the next items from an Map in the middle of an iteration, there will be no more next().
I would not use the hashCode() values as keys, I would you the objects themselves.
If your hashCode and equals don't agree with one another, this could return false. For example, if the equals method always returns false, this will return false, since there isn't any object that would ever compare equal to the keys in the map.
Hope this helps!
You might want to check hasNext() first.
You could remove the first key in another thread between getting the first keys and calling containsKey.

How does Java HashMap store entries internally

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.

HashMap key problems

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.

Categories