JDK-8172951 : Nested HashMap.computeIfAbsent leads to discrepancy size() vs. content
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util:collections
  • Affected Version: 8u112
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_7
  • CPU: x86_64
  • Submitted: 2017-01-17
  • Updated: 2017-01-18
  • Resolved: 2017-01-18
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_112"
Java(TM) SE Runtime Environment (build 1.8.0_112-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.112-b15, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]

A DESCRIPTION OF THE PROBLEM :
Consider the attached example application that demonstrates the issue.

Having a HashMap with two computeIfAbsent calls with different keys having the same hashCode (but not equals).

For HashMap there is nothing specified about attempting to update other mappings of the map while in the computation function.
E.g. ConcurrentHashMap does not allow this (see javadoc: "computation should be short and simple, and must not attempt to update any other mappings of this map").

Eventhough the second item is not inserted but the size() of the map is increased.

See also for a similar issue with the ConcurrentHashMap:
https://bugs.openjdk.java.net/browse/JDK-8062841
https://bugs.openjdk.java.net/browse/JDK-8074374


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
the second item is successfully inserted in the map and also returned by the iterator.

OR

there should be an exception indicating the wrong usage (like e.g. ConcurrentModificationException)

ACTUAL -
The map reports a size() of 2 but iterating over the entries does only result in one item.


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class ComputeIfAbsentDemo {

	public static void main(String[] args) {
		Map<Key, String> m = new HashMap<>();
		m.computeIfAbsent(new Key("firstKey"), k -> {
			m.computeIfAbsent(new Key("secondKey"), sk -> "secondKey"); // XXX this nested computeIfAbsent is missing in result but present in size()
			return "firstValue";
		});

		System.out.println("Map.size(): " + m.size()); // size is reported to be 2

		 // iterator only returns one item:
		System.out.println("Map.entrySet().toArray().length: "+m.entrySet().toArray().length);
		for(Key k : m.keySet()) {
			System.out.println(k.value);
		}
	}

	private static class Key {
		private final String value;

		private Key(String val) {
			value = val;
		}

		@Override
		public int hashCode() {
			return 1; // ensure we have a collision
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			}
			if (obj == null) {
				return false;
			}
			if (getClass() != obj.getClass()) {
				return false;
			}
			Key other = (Key) obj;
			return Objects.equals(value, other.value);
		}
	}
}

---------- END SOURCE ----------


Comments
This is fixed in JDK 9 with JDK-8071667 . When the test case is run in JDK 9-ea, it gives a ConcurrentModification Exception. java version "9-ea" Java(TM) SE Runtime Environment (build 9-ea+148) Java HotSpot(TM) 64-Bit Server VM (build 9-ea+148, mixed mode) Exception in thread "main" java.util.ConcurrentModificationException at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1138) at JI9047034.main(JI9047034.java:9)
18-01-2017