The following program compiles in Java 7 and in Eclipse Mars RC2 for Java 8:
import java.util.List;
public class Test {
static final void a(Class<? extends List<?>> type) {
b(newList(type));
}
static final <T> List<T> b(List<T> list) {
return list;
}
static final <L extends List<?>> L newList(Class<L> type) {
try {
return type.newInstance();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Using the javac 1.8.0_45 compiler, the following compilation error is reported:
Test.java:6: error: method b in class Test cannot be applied to given types;
b(newList(type));
^
required: List<T>
found: CAP#1
reason: inference variable L has incompatible bounds
equality constraints: CAP#2
upper bounds: List<CAP#3>,List<?>
where T,L are type-variables:
T extends Object declared in method <T>b(List<T>)
L extends List<?> declared in method <L>newList(Class<L>)
where CAP#1,CAP#2,CAP#3 are fresh type-variables:
CAP#1 extends List<?> from capture of ? extends List<?>
CAP#2 extends List<?> from capture of ? extends List<?>
CAP#3 extends Object from capture of ?
A workaround is to locally assign a variable:
import java.util.List;
public class Test {
static final void a(Class<? extends List<?>> type) {
// Workaround here
List<?> variable = newList(type);
b(variable);
}
static final <T> List<T> b(List<T> list) {
return list;
}
static final <L extends List<?>> L newList(Class<L> type) {
try {
return type.newInstance();
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
I know that type inference has changed a lot in Java 8 (e.g. due to JEP 101 "generalized target-type inference"). So, is this a bug or a new language "feature"?
EDIT: I have also reported this to Oracle as JI-9021550, but just in case this is a "feature" in Java 8, I've reported the issue also to Eclipse:
Disclaimer - I don't know enough about the subject, and the following is an informal reasoning of mine to try to justify javac's behavior.
We can reduce the problem to
<X extends List<?>> void a(Class<X> type) throws Exception
{
X instance = type.newInstance();
b(instance); // error
}
<T> List<T> b(List<T> list) { ... }
To infer T
, we have constraints
X <: List<?>
X <: List<T>
Essentially, this is unsolvable. For example, no T
exists if X=List<?>
.
Not sure how Java7 infers this case. But javac8 (and IntelliJ) behaves "reasonably", I'd say.
Now, how come this workaround works?
List<?> instance = type.newInstance();
b(instance); // ok!
It works due to wildcard capture, which introduces more type info, "narrowing" the type of instance
instance is List<?> => exist W, where instance is List<W> => T=W
Unfortunately, this is not done when instance
is X
, thus there is less type info to work with.
Conceivably, the language could be "improved" to do wildcard capture for X too:
instance is X, X is List<?> => exist W, where instance is List<W>