You should be able to use chm.keySet()
with ConcurrentHashMap
.
Between Java 7 and Java 8, the ConcurrentHashMap.keySet()
method did change from returning Set<K>
to returning ConcurrentHashMap.KeySetView<K,V>
. This is a covariant override, since KeySetView<K,V>
implements Set<K>
. It's also both source and binary compatible. That is, the same source code should work fine when built and run on 7 and when built and run on 8. The binary built on 7 should also run on 8.
Why the undefined reference? I suspect there is a build configuration problem that mixes bits from Java 7 and Java 8. Typically this occurs because the when attempting to compile for a previous release, the -source
and -target
options are specified, but the -bootclasspath
option isn't specified. For example, consider the following:
/path/to/jdk8/bin/javac -source 1.7 -target 1.7 MyClass.java
If MyClass.java contains any dependencies on JDK 8 APIs, this will not work when run using JDK 7; it will result in a NoSuchMethodError
being thrown at runtime.
Note that this error won't occur immediately; it will occur only when an attempt is made to invoke the method in question. This is because linkage in Java is done lazily. Thus, you can have references to nonexistent methods lurking around in your code for an arbitrary amount of time, and errors won't occur unless the execution path attempts to invoke such a method. (This is both a blessing and a curse.)
It's not always easy to discern dependencies on newer JDK APIs by looking at the source code. In this case, if you compile using JDK 8, the call to keySet()
will compile in a reference to the method that returns a ConcurrentHashMap.KeySetView
because that's the method that's found in the JDK 8 class library. The -target 1.7
option makes the resulting class file compatible with JDK 7, and the -source 1.7
option limits the language level to JDK 7 (which doesn't apply in this case). But the result is actually neither fish nor fowl: the class file format will work with JDK 7, but it contains references to the JDK 8 class library. If you try to run this on JDK 7, of course it can't find the new stuff introduced in 8, so the error occurs.
You can attempt to work around this by modifying the source code to avoid dependencies on newer JDK APIs. So if your code is this:
ConcurrentHashMap<K, V> hashMap = ... ;
final Iterator<CLASS_NAME> itr = hashMap.keySet().iterator();
You could change it to this:
ConcurrentHashMap<K, V> hashMap = ... ;
final Iterator<CLASS_NAME> itr = ((Map<K, V>)hashMap).keySet().iterator();
This can be made to work, but I don't recommend it. First, it garbages up your code. Second, it's not at all obvious where changes like this need to be applied, so it's hard to tell when you've gotten all the cases. Third, it's hard to maintain, as the reason for this cast isn't at all obvious and is easily refactored away.
The correct solution is to make sure you have a build environment that is purely based on the oldest JDK version you want to support. The binary should then run unchanged on later JDKs. For example, to compile for JDK 7 using JDK 8, do this:
/path/to/jdk8/bin/javac -source 1.7 -target 1.7 -bootclasspath /path/to/jdk7/jre/lib/rt.jar MyClass.java
In addition to specifying the -source
and -target
options, specifying the -bootclasspath
option will limit all dependencies to the APIs found at that location, which of course must match the version specified for the other options. This will prevent any inadvertent dependencies on JDK 8 from creeping into the resulting binaries.
In JDK 9 and later, a new option --release
has been added. This effectively sets the source, target, and bootclasspath to the same JDK platform level all at once. For example:
/path/to/jdk9/bin/javac --release 7 MyClass.java
When using the --release
option, it's no longer necessary to have the older JDK's rt.jar
around for build purposes, as javac
includes tables of public APIs for earlier releases.
**
More generally, this kind of issue tends to occur when the JDK introduces covariant overrides in a new JDK release. This happened in JDK 9 with the various Buffer
subclasses. For example, ByteBuffer.limit(int)
was overridden and now returns ByteBuffer
, whereas in JDK 8 and earlier this method was inherited from Buffer
and returned Buffer
. (Several other covariant overrides were added in this area.) Systems compiled on JDK 9 using only -source 8 -target 8
will run into exactly the same issue with NoSuchMethodError
. The solution is the same: use --release 8
.