为什么我无法在Java中创建通用数组类型?

319
Java为什么不允许我们做某些事情?
private T[] elements = new T[initialCapacity];

我能理解为什么.NET不允许我们这样做,因为在.NET中,你有值类型,在运行时可以有不同的大小,但是在Java中,所有类型T都将是对象引用,因此具有相同的大小(如果我说错了,请纠正我)。

这是什么原因呢?


39
你在说什么?你完全可以在.NET中做到。-- 我在这里试图弄清楚为什么我无法在Java中做到。 - BrainSlugs83
@BrainSlugs83 - 请添加一个代码示例或教程的链接,以证明这一点。 - MasterJoe
另请参见-https://dev59.com/4KTms4cB2Jgan1znew89 - MasterJoe
1
@MasterJoe2 我指的是问题中的代码。它在C#中运行良好,但在Java中不行。-- 问题陈述了它在两者中都无法运行,这是不正确的。-- 不确定进一步讨论是否有价值。 - BrainSlugs83
因为这是一个还没有被修复的错误。 - Meech
16个回答

252

这是因为Java的数组(与泛型不同)在运行时包含有关其组件类型的信息。因此,在创建数组时必须知道组件类型。由于您不知道在运行时T是什么,因此无法创建数组。


34
抹去(erasure)呢?为什么不适用? - Qix - MONICA WAS MISTREATED
17
那么 ArrayList <SomeType> 是如何实现的呢? - Thumbz
13
你的意思是 new ArrayList<SomeType>() 吗?泛型类型在运行时不包含类型参数,创建时也不使用类型参数。无论是 new ArrayList<SomeType>()new ArrayList<String>() 还是 new ArrayList() 生成的代码都没有任何区别。 - newacct
11
我更想了解ArrayList<T>如何使用它的private T[] myArray。在代码的某个地方,它必须有一个通用类型为T的数组,那么是如何实现的呢? - Thumbz
23
它没有运行时类型为 T[] 的数组。它有一个运行时类型为 Object[] 的数组,要么1)源代码包含一个变量 Object[](这是在最新的Oracle Java源代码中的情况);或2)源代码包含一个类型为 T[] 的变量,这是虚假的,但由于 T 在类的作用域内被擦除,所以不会引起问题。 - newacct
显示剩余17条评论

152

引用:

Arrays of generic types are not allowed because they're not sound. The problem is due to the interaction of Java arrays, which are not statically sound but are dynamically checked, with generics, which are statically sound and not dynamically checked. Here is how you could exploit the loophole:

class Box<T> {
    final T x;
    Box(T x) {
        this.x = x;
    }
}

class Loophole {
    public static void main(String[] args) {
        Box<String>[] bsa = new Box<String>[3];
        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3); // error not caught by array store check
        String s = bsa[0].x; // BOOM!
    }
}

We had proposed to resolve this problem using statically safe arrays (aka Variance) bute that was rejected for Tiger.

-- gafter

我认为是Neal Gafter,但不确定。

在此上下文中查看:http://forums.sun.com/thread.jspa?threadID=457033&forumID=316


3
请注意,我将此设置为CW,因为答案不是我的。请注意,我将其设置为CW,因为答案不是我的。 - Bart Kiers
12
这就解释了为什么它可能不是类型安全的。但编译器可以警告这种类型安全问题。事实上,几乎出于与无法执行new T()相同的原因,甚至不可能执行此操作。在Java中,每个数组都按设计存储其中的组件类型(即T.class);因此,在运行时需要T的类来创建这样的数组。 - newacct
2
你仍然可以使用 new Box<?>[n],这在某些情况下可能足够,尽管它在你的示例中没有帮助。 - Bartosz Klimek
1
@BartKiers 我不明白...这仍然无法编译(java-8):Box<String>[] bsa = new Box<String>[3];在java-8及以上版本中是否有任何更改? - Eugene
1
@Eugene,特定泛型类型的数组是不允许的,因为它们可能会导致类型安全性的丧失,就像示例中所演示的那样。在任何版本的Java中都不允许这样做。答案开始于“不允许使用泛型类型的数组,因为它们不安全。” - garnet
@BartKiers - 我们可以这样做吗,"T[ ] elements = new T[num]"? - MasterJoe

67

在没有提供一个合适的解决方案时,你最终只会得到更糟糕的结果。

通常的解决方法如下。

T[] ts = new T[n];

被替换为(假设T扩展自Object而不是其他类)

T[] ts = (T[]) new Object[n];

我更喜欢第一个例子,但似乎更多学术类型的人更喜欢第二个例子,或者只是不愿意考虑这个问题。

大多数关于为什么不能仅仅使用Object[]的例子同样适用于List或Collection(它们都被支持),所以我认为它们是非常糟糕的论点。

注意:这就是Collections库本身无法编译而出现警告的原因之一。如果这种用例无法在没有警告的情况下得到支持,那么在我的看来,泛型模型本身就存在根本性问题。


6
第二个需要小心。如果你将以这种方式创建的数组返回给期望得到一个 String[] 的人(或者如果你将其存储在可公开访问的类型为 T[] 的字段中,然后有人检索它),那么他们将会得到一个 ClassCastException。 - newacct
7
我把这个回答投了反对票,因为你提供的首选示例在Java中不被允许,而你的第二个示例可能会抛出ClassCastException异常。 - José Roberto Araújo Júnior
5
很清楚第一个例子需要被替换为第二个例子。你最好解释一下为什么第二个例子可能会抛出 ClassCastException,因为这对于每个人来说都不太明显。 - Peter Lawrey
4
@PeterLawrey 我创建了一个自问自答的问题,展示了为什么 T[] ts = (T[]) new Object[n]; 是个不好的想法:https://dev59.com/4KTms4cB2Jgan1znew89 - José Roberto Araújo Júnior
1
@MarkoTopolnik 我应该因为回答你所有的评论来解释我已经说过的同样的事情而被授予奖章,从我的原始理由中唯一改变的是我认为他说 T[] ts = new T[n]; 是一个有效的例子。我会保留投票,因为他的答案可能会给其他开发人员带来问题和困惑,而且也与主题无关。此外,我将停止对此发表评论。 - José Roberto Araújo Júnior
显示剩余2条评论

37
这是不可能的原因在于Java纯粹在编译器级别上实现其泛型,每个类只生成一个类文件。这被称为类型擦除。在运行时,编译后的类需要使用相同的字节码处理所有用途。因此,new T[capacity]将完全不知道需要实例化哪种类型。

22
答案已经给出,但如果您已经有 T 类型的实例,则可以这样做:
T t; //Assuming you already have this object instantiated or given by parameter.
int length;
T[] ts = (T[]) Array.newInstance(t.getClass(), length);

希望我能帮到你, Ferdi265

1
这是一个不错的解决方案。但是这将会得到未经检查的警告(从Object到T[]的强制转换)。另一个“更慢”但“无警告”的解决方案是:T[] ts = t.clone(); for (int i=0; i<ts.length; i++) ts[i] = null; - midnite
1
另外,如果我们保存的是 T[] t,则应为 (T[]) Array.newInstance(t.getClass().getComponentType(), length);。我花了一些时间来理解 getComponentType()。希望这能帮助其他人。 - midnite
1
@midnite t.clone() 不会返回 T[]。因为在这个答案中,t 不是一个数组。 - xmen

6
主要原因是Java中的数组是协变的。

这里有一个很好的概述在这里


我不明白即使使用不变数组,你也如何支持“new T [5]”。 - Dimitris Andreou
3
@DimitrisAndreou 嗯,整件事其实是Java设计上的一场笑话。这一切都始于数组协变性。有了数组协变性之后,你可以将String[]强制转换为Object并在其中存储一个Integer。所以他们必须添加一个运行时类型检查来防止数组存储(ArrayStoreException),因为这个问题在编译时无法被捕获。(否则,一个Integer实际上可能会被卡在String[]中,当你尝试检索它时就会出现错误,这将是可怕的。) ... - Radon Rosborough
3
@DimitrisAndreou... 首先,当你在运行时进行了一个远比较稳妥的编译时检查之后,你会遇到类型擦除(这也是一个不幸的设计缺陷--只包括用于向后兼容)。类型擦除意味着你 不能 为泛型类型进行运行时类型检查。因此,为了避免数组存储类型问题,你就不能有泛型数组。如果一开始就将数组作为不变量处理,那么我们就可以进行编译时类型检查,而不会受到类型擦除的影响。 - Radon Rosborough
我刚刚发现了评论的五分钟编辑期。我的第一条评论中应该是 Object[] 而不是 Object - Radon Rosborough

3
在我的情况下,我只是想要一个栈的数组,就像这样:
Stack<SomeType>[] stacks = new Stack<SomeType>[2];

由于这是不可能的,所以我采用以下方法作为解决方案:

  1. 在Stack周围创建一个非通用的包装类(称为MyStack)
  2. MyStack [] stacks = new MyStack [2] 运行得非常好

虽然不太美观,但Java很高兴。

注意:正如BrainSlugs83在问题的评论中提到的那样,在.NET中完全可以有泛型数组。


3

来自Oracle教程

You cannot create arrays of parameterized types. For example, the following code does not compile:

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

The following code illustrates what happens when different types are inserted into an array:

Object[] strings = new String[2];
strings[0] = "hi";   // OK
strings[1] = 100;    // An ArrayStoreException is thrown.

If you try the same thing with a generic list, there would be a problem:

Object[] stringLists = new List<String>[];  // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>();   // OK
stringLists[1] = new ArrayList<Integer>();  // An ArrayStoreException should be thrown,
                                            // but the runtime can't detect it.

If arrays of parameterized lists were allowed, the previous code would fail to throw the desired ArrayStoreException.

在我看来,这听起来很弱。我认为任何具有足够泛型理解的人都会非常好地理解,并且甚至期望在这种情况下不会抛出ArrayStoredException异常。

这是真的,通用化的数组不能被创建,不是因为类型擦除已经存在...因为类型在编译时被擦除,List类的新实例化只能在没有其泛型类型的情况下进行...因此。 - mahee96
如何允许这样的用法...一个方法是停止使用类型擦除,并为不同的泛型类型保留类字节码的不同版本,就像在C++模板中一样...然后上述泛型化数组实例化语法就完全有效了............List<Integer>[] arrayOfLists = new List[2]; // 因为在编译器擦除泛型参数类型信息后,List接口本身是唯一剩余的东西。 - mahee96

3

我喜欢Gafter间接给出的答案。然而,我认为这是错误的。我稍微修改了Gafter的代码。它可以编译并运行一段时间,但会在Gafter预测的位置崩溃。

class Box<T> {

    final T x;

    Box(T x) {
        this.x = x;
    }
}

class Loophole {

    public static <T> T[] array(final T... values) {
        return (values);
    }

    public static void main(String[] args) {

        Box<String> a = new Box("Hello");
        Box<String> b = new Box("World");
        Box<String> c = new Box("!!!!!!!!!!!");
        Box<String>[] bsa = array(a, b, c);
        System.out.println("I created an array of generics.");

        Object[] oa = bsa;
        oa[0] = new Box<Integer>(3);
        System.out.println("error not caught by array store check");

        try {
            String s = bsa[0].x;
        } catch (ClassCastException cause) {
            System.out.println("BOOM!");
            cause.printStackTrace();
        }
    }
}

输出结果为:
I created an array of generics.
error not caught by array store check
BOOM!
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at Loophole.main(Box.java:26)

据我所知,在Java中可以创建通用的数组类型。我是否误解了问题?


希望编译器能够给你一个类型安全警告,是吗? - Matt McHenry
1
是的,我收到了类型安全警告。是的,我看到我的例子对这个问题没有反应。 - emory
1
你没有在这里创建一个通用数组。编译器为你创建了一个(非通用)数组。 - newacct
如果你只是使用对象数组引用将协变 Object[] 数组分配给非框类型,例如:oa[0] = new String("different object"); 那么你就会得到一个数组存储异常... - mahee96
你最终为什么会得到类型转换异常呢?这是因为你之前在编译时使用了泛型,指示你的bsa[]数组是泛型类型String,由于数组是协变的,编译器假定你总是将字符串存储到该数组中。因此,当访问bsa[0].x时,并没有引起编译错误。 - mahee96
显示剩余24条评论

2

class可以声明一个类型为T[]的数组,但不能直接实例化这样的数组。一种常见的方法是实例化一个类型为Object[]的数组,然后进行缩小转换为类型T[],如下所示:

  public class Portfolio<T> {
  T[] data;
 public Portfolio(int capacity) {
   data = new T[capacity];                 // illegal; compiler error
   data = (T[]) new Object[capacity];      // legal, but compiler warning
 }
 public T get(int index) { return data[index]; }
 public void set(int index, T element) { data[index] = element; }
}

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