【问题标题】:Why is it wrong to cast Object[] array?为什么强制转换 Object[] 数组是错误的?
【发布时间】:2015-09-15 13:47:51
【问题描述】:

我对 Java 中的泛型编程非常陌生。

我不明白为什么不能创建泛型类型的数组。

T[] a = new T[size]; //why does this give an error?

如果泛型类型意味着泛型占位符T将在运行时被类名替换,是什么阻止我们创建具有泛型引用的数组?

经过一番搜索,我找到了一种解决方法

T[] a = (T[])new Object[size];   //I don't get how this works?

虽然我找到了解决方案,但我仍然无法理解是什么阻止了创建泛型数组。 假设我创建了一个返回 Object 数组的函数。

public Object[] foo(){
        return new Object[12];
}

然后拨打电话

String[] a = (String[])foo();

给出一个 ClassCastException 。但为什么? 它看起来不像我将 Object 数组转换为 T 数组的第一行代码吗?

 T[] a = (T[])new Object[size];

如果这没有故障,那为什么不呢?

【问题讨论】:

  • 你一共问了六个问题:)
  • @SURESH ATTA 我觉得它们都是相关的:/
  • @Djack:泛型类型并不意味着“泛型占位符 T 将在运行时替换为类名”。它被Object 替换before 运行时。 Java 泛型与 C++ 模板的工作方式不同。

标签: java arrays object generics


【解决方案1】:

部分原因是反过来看。你不能做(String[]) new Object[10];因为Object 数组不是String 数组。因为

String[] array = new String[10];
array[0] = "foo";
String foo = array[0];

很好,但是

Object[] objectArray = new Object[10];
objectArray[0] = 10;
String[] stringArray = (String[]) objectArray;
String foo = stringArray[0];

...正在尝试将Integer 分配给String,这首先是不允许的。因此,当您将Object[] 转换为String[] 时,此代码将失败。该代码必须在某处抛出ClassCastException

这对于 Java 来说都是一样的,甚至在泛型被发明之前。接受所有首先然后转到泛型。

现在,Java 泛型的实现方式实现意味着当您编译代码时,T 会默默地重写为Object。所以T[] array = (T[]) new Object[10] 是被允许的,因为它实际上被重写为Object[] array = new Object[10]。但是一旦你拿出来,事情就出错了。例如,

private static <T> T[] newArray() {
   return (T[]) new Object[10];
}

如果您拨打String[] array = newArray(),您将在呼叫站点获得ClassCastException,而不是在newArray() 内。这就是为什么 Java 会在 (T[]) new Object[10] 处给你一个警告,而这个警告很可能会在以后导致一个真正的 ClassCastException

一般来说,不要混合使用数组和泛型。解决这一切的方法是正确使用List

【讨论】:

  • 您的回答很有帮助,但是如果 T 被重写为 Object 那么当我创建一个实例时会发生什么?像Hello&lt;String&gt; = new Hello&lt;&gt;() 这样T[] 是否只保留Object[] 而不会变成String[]
  • 是的。是的,T[] 仅保留Object[],然后当您尝试将其作为String[] 取出时,您会得到ClassCastException。 Java 将在您提取泛型并将它们分配给实际类型的任何地方插入类型转换。
  • 因此,如果在编译时将通用占位符重写为Object,那么说T[] = new T[size] 应该没有问题,因为它将被转换为Object[] = new Object[size]。但是编译器却报错了?
  • 不,你根本不能写new T[size],因为编译器被写成强制你只用它们真正的实际类型创建数组,所以你可以写(T[]) new Object[n],但是你收到警告。 Java 编译器不允许您假装new T[] 是您可以做的真实事情。
  • 这正是困扰我很久的事情。如果您能澄清这一点,我将非常高兴。 “..因为编写编译器是为了强制您仅使用它们的真实实际类型创建数组”以这种方式编写编译器的真正需要是什么?将 T[size] 转换为 Object[size] 会出现什么问题。
【解决方案2】:

在处理数组时有几点需要注意。

首先,数组被认为是协变的;也就是说,类型化数组将保持其继承链。因此,Integer[]Object[],与 IntegerObject 的方式相同。

这就是您的最后一个示例失败的原因。您想通过fooObject[] 转换为String[]

String[] a = (String[])foo();

Object[] 永远不会是 String[],因为 Object 不是 String(但总是相反)。

其次,数组和泛型不能很好地混合。泛型被认为是不变的;也就是说,他们不会维护他们的继承链。 List&lt;Integer&gt;被认为与List&lt;Object&gt; 相同。

至于您的特定示例失败的原因,这是由于编译时的类型擦除。数组需要在编译时知道它们的具体类型,如果没有这些信息,它们就不能被实例化。由于泛型不存储该信息,因此您无法像实例化非泛型数组一样实例化泛型数组。

也就是说,你必须使用演员表:

T[] a = (T[]) new Object[size];

You can read a bit more about generic arrays in this answer,因为它涵盖了您在处理它们时需要了解的大部分要点。

【讨论】:

  • 你说:“...需要在编译时知道它们的具体类型,没有这些信息,它们就不能被实例化” 但是实例化不会在编译时发生,而是在运行时和运行时发生我们确实知道类型参数的具体类型,然后在运行时它可以实例化该具体类型的数组。如果我错了,请纠正我。
  • @vvtx:是的,你错了。您无法具体化泛型参数。数组需要可重构性。
  • 是的,我了解到数组需要可重构性,但这是在实例化时需要的,即在运行时,并且在运行时你知道你的具体类型。你说:“你不能具体化一个泛型参数”但是当参数类型是非数组时,它在 byecode 中被具体化为 Object 类型,即 T e 在编译后被替换为 Object e 那么什么是停止 T[] e 到 Object [] e?
【解决方案3】:

数组在运行时知道它们的类型。 String[] 知道它是 Strings 的数组。

与此相反,泛型类型参数在运行时被擦除,因此运行时的 List&lt;String&gt; 只是 List

由于类型参数T 在运行时不可用new T[10](无法编译)不可能创建一个真正的T[]

事实并非如此

T[] a = (T[])new Object[size];

不能抛出异常。它可以。 Louis Wasserman 的示例表明它可以在调用站点引发异常,但该行也可以直接引发异常。例如

public static void main(String[] args) {
    foo();
}

static <T extends Number> void foo() {
    T[] array = (T[]) new Object[42];
}

这里,T 的下限是Number,所以在运行时,它会尝试将Object[] 转换为Number[],这会抛出ClassCastException

如果您有一个Class&lt;T&gt; 对象clazz,您可以创建一个T[],例如使用

Array.newInstance(clazz, length);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-04
    • 1970-01-01
    • 2011-05-09
    • 1970-01-01
    • 1970-01-01
    • 2021-05-28
    • 2012-02-10
    相关资源
    最近更新 更多