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 ?

asked Jan 7, 2016 at 15:33
8
  • 1
    So, are you saying that this code compiles with JDK 7 but does not with JDK 8? Commented Jan 7, 2016 at 15:57
  • Yes, exactly that. More so, compiler settings in Maven set to source being 1.7 in both cases Commented Jan 7, 2016 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. Commented Jan 7, 2016 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. Commented Jan 7, 2016 at 16:05
  • 1
    @Tunaki: Guava is irrelevant here. This example can be simplified to reproduce the problem without any 3rd party library. Commented Jan 7, 2016 at 17:33

1 Answer 1

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.

answered Jan 7, 2016 at 19:35
Sign up to request clarification or add additional context in comments.

3 Comments

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 ?
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.<Map<String, Object>>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 explains some differences.
Thank you for the reference to that answer.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.