我不能在Java中创build通用数组types的原因是什么?

Java不允许我们这样做的原因是什么?

private T[] elements = new T[initialCapacity]; 

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

是什么原因?

这是因为Java的数组(不同于generics)在运行时包含有关其组件types的信息。 所以当你创build数组时,你必须知道组件的types。 由于您不知道运行时的T ,所以无法创build数组。

引用:

genericstypes的数组是不允许的,因为它们不健全。 问题是由于Java数组的交互作用,这些数组不是静态的,而是被dynamic地检查的,generics是静态的,并且没有dynamic检查。 这里是你如何利用漏洞:

 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! } } 

我们曾经build议使用静态安全数组(也称为方差)bute来解决这个问题,这个bute被Tiger拒绝了。

– gafter

(我相信这是Neal Gafter ,但我不确定)

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

由于没有提供一个体面的解决scheme,你只是最后更糟糕的恕我直言。

共同的工作如下。

 T[] ts = new T[n]; 

被replace(假设T扩展了Object而不是另一个类)

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

我更喜欢第一个例子,然而更多的acedemictypes似乎更喜欢第二个,或者只是不喜欢它的事情。

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

注意:这是集合库自身不能在没有警告的情况下编译的原因之一。 如果你的这个用例不能在没有警告的情况下被支持,那么在generics模型恕我直言中,某些东西就会被破坏。

这是不可能的原因是Java在编译器级别上纯粹实现了它的generics,并且每个类只有一个类文件。 这被称为types擦除 。

在运行时,编译的类需要使用相同的字节码处理所有的用途。 所以, new T[capacity]完全不知道需要实例化哪种types。

答案已经给出,但如果你已经有一个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

数组是协变的

数组被认为是协变的,这基本上意味着,根据Java的子types规则,一个T []types的数组可以包含Ttypes的元素或T的任何子types。

 Number[] numbers = newNumber[3]; numbers[0] = newInteger(10); numbers[1] = newDouble(3.14); numbers[2] = newByte(0); 

但是不仅如此,Java的子types规则还规定,如果S是T的子types,则数组S []是数组T []的子types,因此类似这样的事情也是有效的:

 Integer[] myInts = {1,2,3,4}; Number[] myNumber = myInts; 

因为根据Java中的子types规则,数组Integer []是数组Number []的子types,因为Integer是Number的子types。

但是这种分类规则会导致一个有趣的问题:如果我们试图这样做会发生什么?

 myNumber[0] = 3.14; //attempt of heap pollution 

最后一行会编译得很好,但是如果我们运行这个代码,我们会得到一个ArrayStoreException,因为我们试图把一个double放到一个整型数组中。 我们通过Number引用访问数组的事实在这里是无关紧要的,重要的是数组是一个整数数组。

这意味着我们可以欺骗编译器,但是我们不能欺骗运行时types系统。 这是因为数组就是我们所说的可修饰types。 这意味着在运行时,Java知道这个数组实际上被实例化为一个整数数组,而这个数组恰好是通过Number []types的引用来访问的。

所以,正如我们所看到的,一件事是对象的实际types,另一件事是我们用来访问它的引用types,对吧?

Javagenerics的问题

现在,Java中的genericstypes的问题是在编译代码完成之后编译器会丢弃types参数的types信息; 因此此types信息在运行时不可用。 这个过程被称为types擦除。 在Java中实现类似这样的generics有很好的理由,但是这是一个很长的故事,它与二进制兼容性与现有的代码有关。

这里重要的一点是,由于在运行时没有types信息,所以没有办法确保我们不会堆积污染。

现在考虑下面的不安全的代码:

 List<Integer> myInts = newArrayList<Integer>(); myInts.add(1); myInts.add(2); List<Number> myNums = myInts; //compiler error myNums.add(3.14); //heap polution 

如果Java编译器不能阻止我们这样做,那么运行时types系统也不能阻止我们,因为在运行时没有办法确定这个列表应该只是一个整数列表。 Java运行时会让我们把我们想要的任何东西放到这个列表中,当它只包含整数时,因为它在创build时被声明为一个整数列表。 这就是为什么编译器拒绝第4行,因为它是不安全的,如果允许的话可以打破types系统的假设。

因此,Java的devise者确保我们不能欺骗编译器。 如果我们不能愚弄编译器(就像我们可以用数组做的那样),那么我们也不能欺骗运行时types系统。

因此,我们说generics是不可确定的,因为在运行时我们不能确定generics的真实性质。

我跳过了这个答案的一些部分,你可以在这里阅读完整的文章: https : //dzone.com/articles/covariance-and-contravariance

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

这里有一个很好的概述。

我喜欢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中创build通用数组types。 我误解了这个问题吗?

就我而言,我只是想要一堆栈,像这样:

 Stack<SomeType>[] stacks = new Stack<SomeType>[2]; 

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

  1. 创build了一个围绕Stack的非generics封装类(比如说MyStack)
  2. MyStack []堆栈=新的MyStack [2]工作得很好

丑,但Java很高兴。

注意:正如BrainSlugs83在问题的评论中提到的那样,在.NET中完全可以使用generics数组

我知道我在这里参加派对有点晚,但是我想我可以帮助任何未来的谷歌,因为这些答案都没有解决我的问题。 尽pipeFerdi265的回答非常有帮助。

我试图创build自己的链接列表,所以下面的代码是为我工作的:

 package myList; import java.lang.reflect.Array; public class MyList<TYPE> { private Node<TYPE> header = null; public void clear() { header = null; } public void add(TYPE t) { header = new Node<TYPE>(t,header); } public TYPE get(int position) { return getNode(position).getObject(); } @SuppressWarnings("unchecked") public TYPE[] toArray() { TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size()); for(int i=0 ; i<size() ; i++) result[i] = get(i); return result; } public int size(){ int i = 0; Node<TYPE> current = header; while(current != null) { current = current.getNext(); i++; } return i; } 

在toArray()方法中,为我创build一个genericstypes的数组:

 TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size()); 

从Oracle教程 :

您不能创build参数化types的数组。 例如,下面的代码不能编译:

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

以下代码演示了将不同types插入到数组中时会发生什么情况:

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

如果你用一个通用列表尝试同样的事情,会出现一个问题:

 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. 

如果允许参数化列表数组,则以前的代码将无法抛出所需的ArrayStoreException。

对我来说,这听起来很脆弱。 我认为任何对generics有足够理解的人都会非常好,甚至可以期望在这种情况下抛出ArrayStoredException。

肯定有一个好方法(也许使用reflection),因为在我看来,这正是ArrayList.toArray(T[] a)所做的。 我引用:

public <T> T[] toArray(T[] a)

以正确的顺序返回包含此列表中所有元素的数组; 返回数组的运行时types是指定数组的运行时types。 如果列表符合指定的数组,则返回其中。 否则,将使用指定数组的运行时types和此列表的大小分配一个新数组。

所以围绕它的一个方法就是使用这个函数,即在ArrayList中创build你想要的对象的ArrayList ,然后使用toArray(T[] a)来创build实际的数组。 这不会很快,但你没有提到你的要求。

那么有谁知道如何实现toArray(T[] a)

这是因为generics被加到java之后,所以它有点笨重,因为java的原始制造者认为在创build数组的时候会指定types。 所以这不适用于generics,所以你必须做E [] array =(E [])new Object [15]; 这编译,但它给出了警告。

如果我们不能实例化generics数组,为什么这个语言有generics数组types? 没有对象的types有什么意义?

我能想到的唯一原因是varargs – foo(T...) 。 否则,他们可能已经彻底清理了generics数组types。 (好吧,他们并不需要使用数组来保存可变参数,因为可变参数在1.5之前是不存在的,这可能是另一个错误。

所以这是一个谎言,你可以实例化通用数组,通过可变参数!

当然,通用数组的问题仍然存在,例如

 static <T> T[] foo(T... args){ return args; } static <T> T[] foo2(T a1, T a2){ return foo(a1, a2); } public static void main(String[] args){ String[] x2 = foo2("a", "b"); // heap pollution! } 

我们可以用这个例子来真正的展示generics数组的危险。

另一方面,我们已经使用了通用可变参数十年了,天空还没有落下。 所以我们可以说这个问题正在被夸大, 这不是什么大不了的事情。 如果允许显式的generics数组创build,我们会在这里和那里有bug。 但我们已经习惯了擦除的问题,我们可以忍受。

我们可以指出, foo2驳斥了规范使我们摆脱了他们声​​称阻止我们离开的问题的说法。 如果Sun有1.5的时间和资源,我相信他们可以达到更满意的解决scheme。