我不能在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];
由于这是不可能的,我使用以下作为解决方法:
- 创build了一个围绕Stack的非generics封装类(比如说MyStack)
- 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。