Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Undefined reference: .. ConcurrentHashMap.keySet()" when building in Java 8

i have a project, and i am build this project with jdk 6,7,8 and my target is 1.6

when i build jdk 8 i get this error:

Undefined reference: java.util.concurrent.ConcurrentHashMap.KeySetView java.util.concurrent.ConcurrentHashMap.keySet()

since i have this code in that line:

   final Iterator<CLASS_NAME> itr = hashMap.keySet().iterator();

how can avoid error, i made some search in internet, and since java 8 changed its return type keyset, i got error. is this any solution. i am using maven, and animal-sniffer-plugin gives this error, with signature error.

like image 408
Bilal Yasar Avatar asked Sep 06 '14 22:09

Bilal Yasar


1 Answers

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.

like image 192
Stuart Marks Avatar answered Oct 14 '22 21:10

Stuart Marks