4

Here's the simple code

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class SimpleTest {

    public static void main(String[] args) {
    final ArrayList<Map<String, Object>> maps = newArrayList(
        createMap("1", "a", Collections.EMPTY_MAP, Collections.EMPTY_MAP),
        createMap("2", "b", Collections.EMPTY_MAP, Collections.EMPTY_MAP),
        createMap("3", "c", Collections.EMPTY_MAP, Collections.EMPTY_MAP)
    ); 

    System.out.println(" maps = " + maps);
    }

    public static Map<String, Object> createMap(String value1, String value2, Map<String, Object> object1, Map<String, Object> object2) {
       Map<String, Object> map = new HashMap<>();
       map.put("value1", value1);
       map.put("value1", value1);
       map.put("object1", object1);
       map.put("object2", object2);
       return map;
    }    

    public static <E> ArrayList<E> newArrayList(E... elements) {
    ArrayList<E> list = new ArrayList<E>(elements.length);
    Collections.addAll(list, elements);
    return list;
    }
}

When JAVA_HOME points to JDK 8 and I use javac -source 1.7 SimpleTest.java I get

SimpleTest.java:9: error: incompatible types: ArrayList<Map> cannot be converted to ArrayList<Map<String,Object>>
        final ArrayList<Map<String, Object>> maps = newArrayList(
                                                                ^

When I use -source 1.8 or no -source option everything works ok. Now, when JAVA_HOME points to JDK 7 the code always compiles whether I use -source 1.7 or not.

Initially this question was about a piece of software where POM file has a <source> and <target> set to 1.7 and the build was failing on JDK 8 but was ok on JDK 7.

Now for the question - what causes it to happen ? It seems to me as a major overlook of some sort. Why compiling on JDK 8 with source set to 1.7 fails ?

EvgeniySharapov
  • 3,078
  • 3
  • 27
  • 38
  • 1
    So, are you saying that this code compiles with JDK 7 but does not with JDK 8? – Tunaki Jan 07 '16 at 15:57
  • Yes, exactly that. More so, compiler settings in Maven set to source being 1.7 in both cases – EvgeniySharapov Jan 07 '16 at 15:58
  • 1
    Please define explicitely what we are comparing here. Are you compiling with a JDK 8 compiler or 7? Or are you compiling with a JDK 8 compiler with source 1.8 for Maven and JDK 7 compiler with source 1.7? Also, the code you posted doesn't have a `java.util.ArrayList< java.util.Map>` (with raw types) so I'm not sure how you got that error. – Tunaki Jan 07 '16 at 16:02
  • Works for me on JDK 8 with source set to 1.8, but gives exactly the same error when source set to 1.7 (same JDK). NetBeans 8.0.2 here. – Sergei Tachenov Jan 07 '16 at 16:05
  • In both cases source is set to 1.7. However same code compiles when JAVA_HOME points to JDK 8 and does not compile when JAVA_HOME points to JDK 7. As for the `ArrayList` not being in the code, that what `Lists.newArrayList` creates. This is a class from Google Guava. – EvgeniySharapov Jan 07 '16 at 16:06
  • @SergeyTachenov, that is what I am looking for an answer for. Why source 1.7 doesn't work for JDK 8. – EvgeniySharapov Jan 07 '16 at 16:08
  • Which version of Guava are you using? – Tunaki Jan 07 '16 at 17:30
  • 1
    @Tunaki: Guava is irrelevant here. This example can be simplified to reproduce the problem without any 3rd party library. – Holger Jan 07 '16 at 17:33

1 Answers1

6

You can simplify the code to a self-contained example which doesn’t need 3rd-party libraries:

public class Test2 {
    @SafeVarargs
    static <T> ArrayList<T> newArrayList(T... arg) {
        return new ArrayList<T>(Arrays.asList(arg));
    }
    private final List<Map<String, Object>> maps = newArrayList(
        createMap(null, Collections.EMPTY_MAP)
     );

    public static Map<String, Object> createMap(String id, Map<String,String> m) {
        return null;
    }
}

This can be compiled using javac from jdk1.7, but not with javac from jdk1.8 using -source 1.7.

The point here is that javac from jdk1.8 still is a different compiler than the one included in the previous version and the option -source 1.7 doesn’t tell it to mimic the old implementation’s behavior but to be compatible with the Java 7 specification. If the old compiler has a bug, the newer compiler doesn’t have to try to reproduce the bug.

Since the code uses Collections.EMPTY_MAP rather than Collections.<String,String>emptyMap(), the raw type Map will be passed to createMap, making it an unchecked invocation having the raw result type Map.

This is mandated by JLS §15.12.2.6:

The result type of the chosen method is determined as follows:

  • If the chosen method is declared with a return type of void, then the result is void.

  • Otherwise, if unchecked conversion was necessary for the method to be applicable, then the result type is the erasure (§4.6) of the method's declared return type.

It seems that this behavior has not been (fully) implemented in javac of jdk1.7. Interestingly, it will do it correctly when adding a type parameter like

public static <T> Map<String, Object> createMap(String id, Map<String,String> m)

making it a generic method. Then, jdk1.7 will produce the same error. But the cited rule applies to all method invocations.


In contrast, the fact that compiling it with Java 8 compliance succeeds stems from the new target type inference, which has entirely different rules.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
  • I guess, I edited the question as you were posting your answer with suggestion. One question that's left is why `-source 1.8` makes JDK 8 compiler compile it ? – EvgeniySharapov Jan 07 '16 at 20:18
  • 1
    That's right, I saw the edit after submitting the answer. But the last sentence summarizes it, the Java 8 rules are totally different. The generic signature of the `newArrayList` invocation is derived from the target type. Note that when you change the invocation to `Test2.>newArrayList(...)` it will also work in Java 7 conformingly, though there are still some subtle differences to how it works in Java 8. Explaining the Java 8 rules completely would exceed the scope of the answer. [This answer](http://stackoverflow.com/a/26285613/2711488) explains some differences. – Holger Jan 08 '16 at 09:42
  • Thank you for the reference to that answer. – EvgeniySharapov Jan 08 '16 at 17:15