#!/usr/bin/env bash
# Tested on Ubuntu with WebUpd8 install of Oracle JDK 8
/usr/lib/jvm/java-8-oracle/bin/javac -source 1.7 -target 1.7 HelloCovariance.java
/usr/lib/jvm/java-1.7.0-openjdk-amd64/bin/java HelloCovariance
Interaction of Covariance and Java Cross-compile
================================================
Here is a Java class with a compatibility problem:
HelloCovariance.java:
```java
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class HelloCovariance {
public static void main(String[] args) {
ConcurrentHashMap<String, String> properties = new ConcurrentHashMap<>();
Set<String> keySet = properties.keySet();
}
}
```
Here is a session log that seems implausible:
```plain
$ /usr/lib/jvm/java-8-oracle/bin/javac -source 1.7 -target 1.7 HelloCovariance.java
warning: [options] bootstrap class path not set in conjunction with -source 1.7
1 warning
$ /usr/lib/jvm/java-1.7.0-openjdk-amd64/bin/java HelloCovariance
Exception in thread "main" java.lang.NoSuchMethodError: java.util.concurrent.ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;
at HelloCovariance.main(HelloCovariance.java:7)
```
Why does this **NoSuchMethodError** happen?
Compare the JavaDoc for **ConcurrentHashMap#keySet()** in Java 1.7 and 1.8:
* http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ConcurrentHashMap.html#keySet()
* http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html#keySet--
Notably the Java 1.7 **ConcurrentHashMap#keySet()** returns a `Set<K>` while the 1.8 **ConcurrentHashMap#keySet()** returns a `ConcurrentHashMap.KeySetView<K,V>`.
How can this work? It turns out that a overriding method is allowed to return a sub-type of the parent methods return type. This is due to covariance. This is useful in certain cases where you know the concrete types and want to avail of them somehow.
You'll notice that there is a warning about bootstrap classpath. It turns out those bootstrap classpath warnings have a lot of merit. The safest fix is clearly to compile with a bootstrap classpath.
Failing that you can avoid the unnecessary use of a concrete type for a variable here.
Using the general **Map** interface in place of the concrete **ConcurrentHashMap** type here side-steps the coupling to the Java 8 return type and will allow this code to be compiled with Java 8 and run on Java 7.
HelloSidestep.java:
```java
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class HelloSidestep {
public static void main(String[] args) {
Map<String, String> properties = new ConcurrentHashMap<>();
Set<String> keySet = properties.keySet();
}
}
```
```plain
$ /usr/lib/jvm/java-8-oracle/bin/javac -source 1.7 -target 1.7 HelloCovariance.java
warning: [options] bootstrap class path not set in conjunction with -source 1.7
1 warning
$ /usr/lib/jvm/java-1.7.0-openjdk-amd64/bin/java HelloCovariance
```
Hey look! No crash. This is not really the right fix, and you will be playing whack-a-mole with similar bugs in a large system. The correct and recommended fix is to use a bootstrap classpath.
```plain
$ /usr/lib/jvm/java-8-oracle/bin/javac -source 1.7 -target 1.7 HelloCovariance.java -bootclasspath /usr/lib/jvm/java-1.7.0-openjdk-amd64/jre/lib/rt.jar
$ /usr/lib/jvm/java-1.7.0-openjdk-amd64/bin/java HelloCovariance
```
Now we get no warnings and no crash.
This is not a new problem, but it seems like there is still a lot of confusion around it.
Here are some useful resources to consider:
* https://blogs.oracle.com/darcy/entry/how_to_cross_compile_for
* http://docs.oracle.com/javase/6/docs/technotes/tools/solaris/javac.html#crosscomp-example
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class HelloCovariance {
public static void main(String[] args) {
ConcurrentHashMap<String, String> properties = new ConcurrentHashMap<>();
Set<String> keySet = properties.keySet();
}
}