未经检查的强制类型转换有什么问题?

15

我正在阅读 J. Bloch 的《Effective Java》书籍,现在正处于数组 vs 列表的部分。以下是他提供的未经检查的转换示例:

interface Function<T> {
    T apply(T arg1, T arg2);
}

public class Main{
    public static void main( String[] args ){
        Function<String> f = null;
        List<String> str = Arrays.asList("asd");
        //staff
        reduce(str, f, ""); //E's deduced to String. Where is type-unsafe?
    }
    static <E> E reduce(List<E> list, Function<E> f, E initVal) {
        E[] snapshot = (E[]) list.toArray(); // Unchecked cast
        E result = initVal;
        for (E e : snapshot)
            result = f.apply(result, e);
        return result;  
    }
}

他说这种方法不是类型安全的,我们很容易遇到ClassCastException异常。但我并不明白为什么会这样。哪里不安全了?类型变量E将始终被推导为适当的类型,因此我们不必担心ClassCastException异常。
你能否举个抛出ClassCastException异常的例子吗?
4个回答

13

无法在编译时保证 list.toArray() 返回类型为 E[] 的数组。而且,它几乎总是返回类型为 Object[] 的数组。因此,根据后续对该数组的使用,可能会出现 ClassCastException。例如,请看以下代码:

public static void main( String[] args ){
    List<String> str = Collections.singletonList("asd");
    String[] array = test(str);
}

static <E> E[] test(List<E> list) {
    E[] snapshot = (E[]) list.toArray(); // Unchecked cast
    return snapshot;
}

在这里,你返回了一个E[]数组,而接收者期望返回一个String[]数组。但实际上它是一个Object[]数组,因此当隐式地将返回的泛型类型转换为String[]后,你会在main方法中遇到ClassCastException异常。

在你的代码中,你可以确信该数组以安全的方式使用。但编译器不够智能以进行这种分析,因此只是发出警告。


4

Object[] toArray()方法返回一个数组,该数组按正确的顺序(从第一个到最后一个元素)包含列表中的所有元素。

我们将其转换为E[]以推断泛型类型,因此强制类型转换是未经检查的,因为JVM不知道E将是什么类型,所以会出现警告。

举个例子,假设E是String类型(就像你的代码一样)。 我们正在尝试将Object[]强制转换为String[],这也可以很好地将Object[]转换为Integer[]。 JVM无法在编译/运行时测试此有效性,因此会出现问题。

 public static void main( String[] args ){
    List<String> str = Arrays.asList("asf");
    //staff

    System.out.println(reduce(str, 2)); //E's deduced to String. Where is type-unsafe?
}
static <E, T> E reduce(List<E> list, T initVal) {
    Object snapshot = list.size(); // Unchecked cast   
    return (E) snapshot;
}

这将会引发类转换异常。


4
你在这里使用的list.toArray惯用语没有按照你的List参数化类型的数组进行参数化,因此它返回 Object[]
例如,对于你的List<String> str,你可以调用:String[] foo = str.toArray(new String[str.size()]); 而不需要进行强制类型转换。
问题在于,由于Java泛型的设计,你永远不能初始化一个新的new E[],因此必须进行类型转换为(E[])
我认为这不会抛出ClassCastException
正如其他人所提到的,“美容”解决方法是在你的toArray调用之前添加@SuppressWarnings("unchecked"),这将抑制警告。

2
我认为这不会抛出ClassCastException异常,就像我一样,但是J. Bloch说它很容易被更改以获得一个异常。 - St.Antario

3

你的代码没有问题,而是与Java泛型有关:

static <E> E reduce(List<E> list, Function<E> f, E initVal) {
    @SuppressWarnings({"unchecked"}) //nevermind
    E[] snapshot = (E[]) list.toArray(); //Unchecked cast
    E result = initVal;
    for (E e : snapshot)
        result = f.apply(result, e);
    return result;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接