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