转换泛型 class 在 eclipse 中是警告,但在 javac 中是错误

Casting generic class is warning in eclipse but error in javac

对于以下代码,我发现 Eclipse 和 javac 之间的行为存在重大差异:

public class TestIncompatibleTypes {

    private static <V> void libraryMethod(Class<? extends List<V>> in) {}

    public static void main(String[] args) {
        // Eclipse warns about 'Unchecked cast'
        // Maven fails with 'incompatible types'
        Class<? extends List<String>> l = (Class<? extends List<String>>) ArrayList.class;
        libraryMethod(l);
    }
}

Eclipse 对上述代码发出 'unchecked cast' 警告,但编译成功。 Javac 生成错误:

$ java -version
openjdk version "1.8.0_72-internal"
OpenJDK Runtime Environment (build 1.8.0_72-internal-b15)
OpenJDK 64-Bit Server VM (build 25.72-b15, mixed mode)
$ javac -version
javac 1.8.0_72-internal
$ javac -source 8 TestIncompatibleTypes.java
TestIncompatibleTypes.java:13: error: incompatible types: Class<ArrayList> cannot be converted to Class<? extends List<String>>
        Class<? extends List<String>> l = (Class<? extends List<String>>) ArrayList.class;
                                                                                   ^
1 error

谁能解释为什么 javac 不允许转换?我的理解是类型擦除后它们应该是完全等价的。

是否有解决方法可以在两个编译器上进行编译?

看起来您正在将 Class<ArrayList>> 转换为 Class<? extends List<String>>,但是 ArrayList class 没有那种泛型。我不确定您的 Class<? extends List>

是否需要 <String> 通用

Is there a workaround to get this to compile on both compilers?

Class<? extends List<String>> clazz;
// casting to interim type
clazz = (Class<? extends List<String>>) (Class<? extends List>) ArrayList.class;
// raw types .......
clazz = (Class) ArrayList.class;

然而!这段代码需要解释其工作原理以及何时可能不安全:

  • java.util.ArrayList 对其类型变量没有特别的限制。
  • java.lang.Class 是不可变的,只能用于创建新实例。

如果您使用 ArrayListClass 以外的不同类型,同样的转换可能会导致堆污染,例如丢弃重要信息:

class IntegerList extends ArrayList<Integer> {}
// now we can create a new instance and put String in a List<Integer>
Class<? extends List<String>> clazz =
    (Class<? extends List<String>>) (Class<? extends List>) IntegerList.class;

(我还要提醒一下,使用 ArrayList.class 仍然有可能导致堆污染,我只是没有认真考虑过。)

在 Java 8 中,我们应该更喜欢 lambdas 而不是反射来创建实例,这样可以避免整个问题:

static <V> void libraryMethod(Supplier<? extends List<V>> in) {}
void somewhere() {
    Supplier<? extends List<String>> supplier = ArrayList::new;
    libraryMethod(supplier);
}

当然,如果您出于某种原因不能使用它,那么您偶尔会用 Class 做一些笨拙的事情。

Can anyone explain why javac disallows the cast?

Java 不允许 "sideways" 转换,例如:

Number n = ...;
String s = (Number) n; // compiler error

我解释了原始类型参数的工作原理 and here

简短的解释是存在这样的子类型关系:

           Class<? extends List>
            ╱                 ╲
Class<? extends List<String>>  Class<ArrayList>

两者都是 Class<? extends List> 的子类型,并且两者都不是另一个的子类型或超类型。