Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is generic of a return type erased when there is an unchecked conversion of a method parameter in Java 8?

Consider the following code sample:

import java.util.ArrayList; import java.util.List;  public class Main {     public static void main(String[] args) {         List list = new ArrayList<Integer>();         String response = getProducer(list).get();     }     static Producer<String> getProducer(List<Integer> list) {         return new Producer<String>();     } } class Producer<T> {     T get() {         return null;     } } 

When compiled in Java 7 it just produces an expected warning for getProducer(list):

Warning:(7, 39) java: unchecked conversion required: java.util.List<java.lang.Integer> found: java.util.List

However, when compiled in Java 8 it produces the following error for the response = getProducer(list).get() assignment:

Error:(7, 48) java: incompatible types: java.lang.Object cannot be converted to java.lang.String

So apparently the type returned from getProducer(list) is not Producer<String>, but erased Producer (which is also confirmed by means of the 'extract variable' feature in the IDE). This is very puzzling because getProducer method always returns Producer<String>.

Oddly enough it could be fixed by avoiding unchecked conversion while calling getProducer method, either by:

  • Change parameter type of getProducer from List<Integer> to List
  • Change type of list variable from List to List<Integer>

Updates

  • Java used is Oracle JDK 1.8.0_40
  • I have also tried using source and target options from 1.5 through 1.7 with the Java 8 compiler and the result was the same.

Questions

  • How could the generic type of the passed argument affect a generic type of the method return value while the generic type of the return value is fixed in the method signature?
  • Why is there such a backward-incompatible change in behavior between Java 7 and Java 8?
like image 861
Max Myslyvtsev Avatar asked Jun 18 '15 14:06

Max Myslyvtsev


People also ask

What is unchecked conversion?

The warning message “unchecked conversion” implies that we should check the conversion before the assignment. To check the type conversion, we can go through the raw type collection and cast every element to our parameterized type.

What is unchecked warning in Java?

An unchecked warning tells a programmer that a cast may cause a program to throw an exception somewhere else. Suppressing the warning with @SuppressWarnings("unchecked") tells the compiler that the programmer believes the code to be safe and won't cause unexpected exceptions.

Is it possible to return a generic type in Java?

(Yes, this is legal code; see Java Generics: Generic type defined as return type only.) The return type will be inferred from the caller.

What is type inference in Java 8?

Type inference is a Java compiler's ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that make the invocation applicable.


1 Answers

This looks like a known compatibility issue reported here and here.

From the second link:

The following code which compiled, with warnings, in JDK 7 will not compile in JDK 8:

import java.util.List; class SampleClass {       static class Baz<T> {          public static List<Baz<Object>> sampleMethod(Baz<Object> param) {              return null;          }      }       private static void bar(Baz arg) {          Baz element = Baz.sampleMethod(arg).get(0);      } } 

Compiling this code in JDK 8 produces the following error:

SampleClass.java:12: error:incompatible types: Object cannot be converted to Baz

Baz element = Baz.sampleMethod(arg).get(0); 

Note: SampleClass.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. 1 error

Deriving from this, the OP's code can be fixed by replacing this line (the type declartion on the right hand side threw me off - I read it as a typed array list which it is not):

List list = new ArrayList<Integer>(); 

with

List<Integer> list = new ArrayList<Integer>(); 

which will not result in type being being erased from return type of method getProducer(List<Integer> list)

Quote from second link again:

In this example, a raw type is being passed to the sampleMethod(Baz<Object>) method which is applicable by subtyping (see the JLS, Java SE 7 Edition, section 15.12.2.2). An unchecked conversion is necessary for the method to be applicable, so its return type is erased (see the JLS, Java SE 7 Edition, section 15.12.2.6). In this case the return type of sampleMethod(Baz<Object>) is java.util.List instead of java.util.List<Baz<Object>> and thus the return type of get(int) is Object, which is not assignment-compatible with Baz.

like image 120
6ton Avatar answered Sep 29 '22 02:09

6ton