【问题标题】:Casting generic class is warning in eclipse but error in javac投射泛型类在 Eclipse 中是警告,但在 javac 中是错误的
【发布时间】:2016-04-21 15:31:51
【问题描述】:

对于以下代码,我发现 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 对上述代码发出“未经检查的强制转换”警告,但编译成功。 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&lt;? extends List&lt;String&gt;&gt; l = (Class) ArrayList.class; 呢?

标签: java eclipse javac


【解决方案1】:

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

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 做偶尔的笨拙的事情。

谁能解释为什么 javac 不允许强制转换?

Java 不允许“横向”强制转换,例如:

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

我解释了原始类型参数 herehere 的工作原理。

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

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

两者都是Class&lt;? extends List&gt;的子类型,既不是另一个的子类型也不是另一个的超类型。

【讨论】:

  • 反应很好。您的子类型树非常清楚地说明了错误的原因,尽管考虑到强制转换具有内在风险(至少从编译器的角度来看),禁止横向强制转换似乎很奇怪。
  • 横向转换不会为非泛型类型编译,因为您可以确定转换总是会失败(null 除外)。确实,泛型类型并非总是如此。
【解决方案2】:

看起来您将Class&lt;ArrayList&gt;&gt; 转换为Class&lt;? extends List&lt;String&gt;&gt;,但ArrayList 类没有那种泛型。我不确定你的Class&lt;? extends List&gt; 是否需要&lt;String&gt; 泛型

【讨论】:

  • @JoopEggen 试试看它是否有效。它适用于libraryMethod(Class&lt;? extends List&gt;),但不适用于libraryMethod(Class&lt;? extends List&lt;V&gt;&gt;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多