2

What I did was simple: I would like to creat a HashMap<Pair, ArrayList<Integer>> with Pair as the key and ArrayList<Integer> as the value. The Pair is self-defined class containing elements l (left) and r (right).

At first, I did this as following:

Map<Pair, ArrayList<Integer>> hashmap = new HashMap<>();
ArrayList<String> stringList = new ArrayList<>();
stringList.add("a");
stringList.add("b");
stringList.add("c");
stringList.add("a");
Pair<String, Integer> aPair = new Pair<>(" ", 1); // HERE will be changed!

for (String aString: stringList) {
  aPair.setLeft(aString);
  if (!hashmap.containsKey(aPair)){
    hashmap.put(aPair, new ArrayList<Integer>());
  }
  hashmap.get(aPair).add(1);
}

for (Map.Entry<Pair, ArrayList<Integer>> entry: hashmap.entrySet()) {
  out.println(entry.getKey().getLeft() + " " + entry.getKey().getRight() + " " + entry.getValue());
}

But the output is:

a 1 [1]
a 1 [1]
a 1 [1, 1]

However, if I changed the above code into the following:

Map<Pair, ArrayList<Integer>> hashmap = new HashMap<>();
ArrayList<String> stringList = new ArrayList<>();
stringList.add("a");
stringList.add("b");
stringList.add("c");
stringList.add("a");

for (String aString: stringList) {
  Pair<String, Integer> aPair = new Pair<>(aString, 1); // HERE changed!
  if (!hashmap.containsKey(aPair)){
    hashmap.put(aPair, new ArrayList<Integer>());
  }
  hashmap.get(aPair).add(1);
}

for (Map.Entry<Pair, ArrayList<Integer>> entry: hashmap.entrySet()) {
  out.println(entry.getKey().getLeft() + " " + entry.getKey().getRight() + " " + entry.getValue());
}

The change made was putting the declaration of Pair<String, Integer> aPair into the for-loop. The results new are what I wanted as following:

c 1 [1]
b 1 [1]
a 1 [1, 1]

Why it is like this? Here is a similar question. But it is still different.

EDIT: as mentioned in the comments below by @Eran, the self-defined Pair override the methods hashCode() and equals()

@Override
public int hashCode() { return left.hashCode() ^ right.hashCode(); }

@Override
public boolean equals(Object o) {
  if (!(o instanceof Pair)) return false;
  Pair<?, ?> pairo = (Pair<?, ?>) o;
  return this.left.equals(pairo.getLeft()) &&
         this.right.equals(pairo.getRight());
}
Community
  • 1
  • 1
fluency03
  • 2,319
  • 5
  • 26
  • 59

4 Answers4

1

You're treading in dangerous waters, since your keys are mutable, read this why it is not a good idea - Are mutable hashmap keys a dangerous practice?.

Well, your example alone shows why it isn't a good idea. You add 1 instance of key in your map, and then modify it, effectively modifying all key-value pairs in your hashmap.

Community
  • 1
  • 1
radoh
  • 4,034
  • 5
  • 29
  • 38
1

Your first snippet doesn't work because you are mutating the same Pair instance that already serves as a key in the HashMap. This allows the same Pair instance to appear as a key in multiple entries of the HashMap (since the new hashCode computed after you modified the Pair instance is mapped to a new bucket of the HashMap that doesn't contain any entry) and effectively break the HashMap.

You shouldn't mutate an instance that serves as key of a HashMap (unless you remove it from the HashMap before updating it and put the updated version in the Map later).

Eran
  • 359,724
  • 45
  • 626
  • 694
1

If an object’s hashCode() value can change based on its state, then we must be careful when using such objects as keys in hash-based collections to ensure that we don’t allow their state to change when they are being used as hash keys. All hash-based collections assume that an object’s hash value does not change while it is in use as a key in the collection. If a key’s hash code were to change while it was in a collection, some unpredictable and confusing consequences could follow. This is usually not a problem in practice — it is not common practice to use a mutable object like a List as a key in a HashMap.

From this answer

Community
  • 1
  • 1
Thanga
  • 6,775
  • 3
  • 14
  • 34
0

In first code snippet, you are actually using the same key object for every entry of map. You just modifying it's left value, but it still points to the same memory address. Map needs to have unique keys (every key must point to different memory address) that's why you need to put new instance of pair for each map entry.

callOfCode
  • 709
  • 6
  • 10