问题
Last Thursday someone at work showed me a compile error that I wasn't able to fix in a clean way and it has been bothering me ever since.
The problem is generics related and I've reconstructed a simplified version of the code that generates the compile error. The error occurs in the very last line of code shown below.
I've been looking all over the interwebs but can't seem to find a decent explanation why the Java compiler doesn't accept the code. I guess that if it were to allow the code, it would be possible the create a class cast issue in Bar.operationOnBar(), but I don't see how.
Could someone please enlighten me why this doesn't compile?
public interface Interface {
}
public class Type implements Interface {
}
public class Bar<T> {
public Bar(Class<T> clazz) {
}
public void operationOnBar(Class<T> arg){
}
}
public class Foo {
public <T> Bar<T> bar(Class<T> clazz){
return new Bar<T>(clazz);
}
public static void main(String[] args) {
Class<? extends Interface> extendsInterfaceClazz = Type.class;
new Foo().bar(extendsInterfaceClazz).operationOnBar(Type.class);
}
}
Compile Error on the second line of Foo.main():
The method operationOnBar(Class<capture#1-of ? extends Interface>) in the type Bar<capture#1-of ? extends Interface> is not applicable for the arguments (Class<Type>)
Btw. I've solved it by downcasting Type.class to Class, this way the compiler is unable to see that the generic type of Class is "Type" instead of "? extends Interface".
回答1:
A little advice: when you are not sure why compiler prohibits some generic-related conversion, replace generic classes in question with List<T>
. Then it would be easy to find an example that breaks type safety.
This replacement is correct since currently Java doesn't provide a way to conduct any a priory knowledge about possible behaviours of generic classes (i.e. it lacks a way to specify covariance and contravariance of generic classes in their declarations, as in C# 4 and Scala). Therefore Class<T>
and List<T>
are equivalent for the compiler with respect to their possible behaviours, and compiler has to prohibit conversions that can cause problems with List<T>
for other generic classes as well.
In your case:
public class Bar<T> {
private List<T> l;
public Bar(List<T> l) {
this.l = l;
}
public void operationOnBar(List<T> arg) {
l.addAll(arg);
}
}
List<Type1> l1 = new ArrayList<Type1>();
List<? extends Interface> l2 = l1;
List<Type2> l3 = Arrays.asList(new Type2());
new Foo().bar(l2).operationOnBar(l3);
Type1 t = l1.get(0); // Oops!
回答2:
You also can change the signature of the method operationOnBar
to:
public void operationOnBar(Class<? extends Interface> arg){
回答3:
You would agree that this shouldn't compile:
1 Class<? extends Interface> clazz = AnotherType.class;
2 new Foo().bar(clazz).operationOnBar(Type.class);
The problem is javac is a little dumb; when compiling line#2, all it knows about variable clazz
is its declared type; it forgets the concrete type it was assigned to. So what is assigned to clazz
at line#1 doesn't matter, compiler must reject line#2.
We can imagine a smarter compiler that can track the concrete types, then your code can be compiled, as it is obviously safe and correct.
Since that's not the case, sometimes programmers know more about types than the compiler, it is necessary that programmers do casts to convince the compiler.
回答4:
The general way to deal with these sorts of problems is to introduce a generic argument for the repeated type, which generally means introducing a new generic method (a class would do as well, but isn't necessary).
public static void main(String[] args) {
fn(Type.class);
}
private static <T extends Interface> void fn(Class<T> extendsInterfaceClazz) {
new Foo().bar(extendsInterfaceClazz).operationOnBar(extendsInterfaceClazz);
}
Not really related to the question, but I would suggest using reflection sparingly. It is very, very rarely a good solution.
来源:https://stackoverflow.com/questions/8021900/java-generics-compilation-error-the-method-methodclasscapture1-of-extends