【问题标题】:Weird compile-time behavior when trying to use primitive type in generics尝试在泛型中使用原始类型时出现奇怪的编译时行为
【发布时间】:2010-03-16 10:00:05
【问题描述】:
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 中的工作原理。在上面 sn-p 的第三个赋值中,事情对我来说有点奇怪:编译器抱怨 Integer[] 不能转换为 int[]。当然,这句话是 100% 正确的,但我想知道为什么编译器会提出这个投诉。

如果您注释该行,并按照编译器的“建议”进行第四次赋值,编译器实际上是满意的!!! 现在代码编译得很好!当然,这很疯狂,因为就像运行时行为所暗示的那样,int[] 不能转换为 Object[](这是 T[] 在运行时被类型擦除的内容)。

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


到目前为止,这两个答案存在很多混淆,因此我创建了另一个令人费解的示例来说明这里的根本问题:

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() 会在c == int.class 时抛出NullPointerException,因为c.getSuperclass() == null

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


也许是终极疯狂:

    int.class.cast(null);

该代码编译并且运行良好。

【问题讨论】:

    标签: java arrays generics primitive type-erasure


    【解决方案1】:

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

    【讨论】:

    • 我可能是错的,但我认为从技术上讲 int 没有类,但是当您使用 int.class 时,编译器框 intInteger
    • @matt-b, int.class 是一个 real 类。检查 Class.isPrimitive 的 javadoc。自 1.1 以来一直存在。在拳击之前。整数类!= int.class。 Integer.TYPE == int.class.
    • "int.class 的类型是Class&lt;Integer&gt;" -- 我认为关键在这里
    【解决方案2】:

    几点:

    1. 在需要时,原语是 autoboxed 到它们的对象对应物(包装器)
    2. 原始数组是对象,因此不会自动装箱。
    3. 泛型不能使用原语作为类型参数

    对于您的示例,以下是我的假设:

    在 3 中,自动装箱发生在 类型参数,但不会发生在返回的数组上
    在 4 中,自动装箱发生在 type 参数 上,但不会发生在 method 参数 上,因此实际上生成了 int[],但预期 Integer[]

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

    更新:您的第二个示例没有任何问题。 int.classClass,所以编译器没有理由拒绝它。

    【讨论】:

    • 这些都是好点,但它们没有回答我的问题。此外,我的示例中没有自动装箱。如果您在genericArrayNewInstance 中打印componentType,它会按原样打印传递的.class 对象。
    • @polygenelubricants 这就是我所说的 - 自动装箱不会发生在方法参数上 - 它已经发生(好吧,它可能不完全是自动装箱,但有类似的想法)类型参数。类型参数不能是原始的,因此使用了包装器。
    • “你的第二个例子没有错”——但我的泛型类型规范要求T extends Number,其中int.class extends null。另请参阅 Q 的更新。
    • 这完全符合我关于自动装箱类型参数但不自动装箱方法参数的解释。我仍然没有发现问题。
    【解决方案3】:

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

    为此:

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

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

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

    【讨论】:

      猜你喜欢
      • 2020-05-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-02
      • 2017-08-27
      • 1970-01-01
      • 1970-01-01
      • 2011-07-23
      相关资源
      最近更新 更多