尝试在泛型中使用基本类型时出现奇怪的编译时行为

时间:2010-03-16 10:00:06

标签: java arrays generics primitive type-erasure

import java.lang.reflect.Array;

public class PrimitiveArrayGeneric {
    static <T> T[] genericArrayNewInstance(Class<T> componentType) {
        return (T[]) Array.newInstance(componentType, 0);
    }

    public static void main(String args[]) {
        int[] intArray;
        Integer[] integerArray;

        intArray = (int[]) Array.newInstance(int.class, 0);
        // Okay!

        integerArray = genericArrayNewInstance(Integer.class);
        // Okay!

        intArray = genericArrayNewInstance(int.class);
        // Compile time error:
           // cannot convert from Integer[] to int[]

        integerArray = genericArrayNewInstance(int.class);
        // Run time error:
           // ClassCastException: [I cannot be cast to [Ljava.lang.Object;
    }    
}

我正在努力完全理解泛型如何在Java中运行。在上面的代码片段中,第3项任务对我来说有点奇怪:编译器抱怨Integer[]无法转换为int[]。当然,该陈述是100%正确,但我想知道为什么编译器正在进行投诉。

如果你评论那一行,并按照编译器的“建议”进行第4次分配,编译器实际上是满意的!!! NOW 代码编译得很好!当然,这很疯狂,因为与运行时行为一样,int[]无法转换为Object[](这是T[]在运行时被类型擦除的内容。)

所以我的问题是:为什么编译器“建议”我分配给Integer[]而不是第3次分配?编译器如何推理(错误的!)结论?


到目前为止,这两个答案中存在很多混淆,所以我创建了另一个令人困惑的例子来说明这里的根本问题:

public class PrimitiveClassGeneric {    
    static <T extends Number> T test(Class<T> c) {
        System.out.println(c.getName() + " extends " + c.getSuperclass());
        return (T) null;
    }
    public static void main(String args[]) {
        test(Integer.class);
        // "java.lang.Integer extends class java.lang.Number"

        test(int.class);
        // "int extends null"
    }
}

我是唯一一个认为编译器让上面的代码编译完全疯狂的人吗?

例如,因为我指定了c.getSuperclass().getName(),所以在上面的代码中打印T extends Number并不合理。当然,getName() NullPointerException会在c == int.class之后c.getSuperclass() == null投放 int.class.cast(null);

对我来说,这是一个非常好的理由,首先拒绝编译代码。


也许是最终的疯狂:

{{1}}

该代码编译 AND 运行正常。

3 个答案:

答案 0 :(得分:4)

int.class的类型为Class<Integer>,因此推断genericArrayNewInstance()会返回Integer[]。但该函数实际上创建了一个int[],因此在返回时它会有一个类强制转换异常。基本上,在这种情况下,函数内部的转换为T[]是不合法的,因为int[]不是T[](基元不能在类型变量中使用)。您无法一般地处理原始数组类型;所以你要么让你的方法只返回类型Object,要么你必须为引用类型和原始类型制作单独的方法。

答案 1 :(得分:3)

几点:

  1. 原语在需要时对其对象(包装器)autoboxed
  2. 原始数组是对象,因此它们不会自动装箱。
  3. 泛型不能使用基元作为类型参数
  4. 对于您的示例,以下是我的假设:

    在3中,自动装箱发生在类型参数上,但不会发生在返回的阵列上 在4中,自动装箱发生在类型参数中,但不会发生在方法参数上,因此实际上会生成int[],但是{{1预期

    在类型参数的情况下,自动装箱可能不完全是 autoboxing ,但具有相同的想法。

    更新:你的第二个例子没有错。 Integer[]int.class,因此编译器没有理由拒绝它。

答案 2 :(得分:0)

我同意原始海报。这太疯狂了。为什么我不能使用带有泛型的原语?这可能不是编译器问题,但它是语言的问题。从泛型中跳过原始类型是完全错误的。

为此:

intArray =(int [])Array.newInstance(int.class,0);

int.class只是一个Class对象。所以可以通过。 “int”是一种类型,所以它不好,因为它显然是原始的。并不是说这是创造语言的“最佳”方式,只是为了坚持语言。

这太疯狂了,我无法使用泛型为基元的内存(数组)分配创建包装器。如果使用对象,这对于庞大的集合来说是如此臃肿,这是浪费。创建Java语言/机器的人显然在他们的大脑中有一点限制。他们第一次做错了,但修复它需要十年时间,而且做得不对。