I ran into a very unexpected error today and while I was able to find a way to fix the problem as a whole I'm not sure I completely understand why it did what it did.
The code I'm working with was originally written with a JDK 7 environment of course targeting JRE 7. In the code I was using a ConcurrentHashMap
and needed to iterate over the keys in the map. For this I was using the map.keySet()
which according to the JavaDocs should return a Set<K>
. This worked fine until our build environment switched to JDK8.
When we moved to JDK8 I ensured that I was calling a target/source for 1.7 when calling the javac. So I was pretty surprised when the code started failing right when it wanted to iterate through the keys of the map. No error was thrown, no exception, the thread just simply stopped. After doing some research I found that Java8's implementation for ConcurrentHashMap
the .keySet()
method returns a KeySetView<K,V>
.
I fixed the problem by switching from using the map.keySet()
to getting an Enumeration<K>
using map.keys()
.
Now my guess as to the problem is that although the project was compiled targeting Java7 since the JDK8 was used the Java8 libraries were included, but why didn't it thrown an error or an exception when it hit the mismatch?
As asked here is a code snippet:
class MapProcessing
{
private ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<String, Object>();
public MapProcessing()
{
map.put("First",new Object());
map.put("Second",new Object());
map.put("Third",new Object());
}
public void processing()
{
// when calling this type of loop causes a freeze on our system.
for(String key : map.keySet())
{
System.out.println(key);
}
}
public void working()
{
// This is what I had to do to fix the problem.
Enumeration<String> keys = map.keys();
while(keys.hasMoreElements())
{
String key = keys.nextElement();
System.out.println(key);
}
}
}
We are compiling using Oracle JDK 8 build 40 using a target for 1.7 and source 1.7 in the javac on a Windows 2012 server.
The code is running using Oracle JVM 7 build 25 running on Windows 2012 server.
If i compile your code with Java 8 and javac -source 1.7 -target 1.8 and then run it with Java 7 i get an
Exception in thread "main" java.lang.NoSuchMethodError: java.util.concurrent.ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView; at stackoverflowt.Test.processing(Test.java:20) at stackoverflowt.Test.main(Test.java:27)
This is because the the byte code looks like
public void processing(); Code: 0: aload_0 1: getfield #4 // Field map:Ljava/util/concurrent/ConcurrentHashMap; 4: invokevirtual #10 // Method java/util/concurrent/ConcurrentHashMap.keySet:()Ljava/util/concurrent/ConcurrentHashMap$KeySetView; 7: invokevirtual #11 // Method java/util/concurrent/ConcurrentHashMap$KeySetView.iterator:()Ljava/util/Iterator; 10: astore_1
and referring explicitly to ConcurrentHashMap$KeySetView which is not present in Java 7. I am on Mac with Java 1.7.0_79 and 1.8.0_45
If you change the code to (only use the Map Interface):
private Map<String, Object> map = new ConcurrentHashMap<String, Object>();
then it work's for me. Bytecode then looks like
public void processing(); Code: 0: aload_0 1: getfield #4 // Field map:Ljava/util/Map; 4: invokeinterface #10, 1 // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set; 9: invokeinterface #11, 1 // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator; 14: astore_1