是什么原因我不能'在Java中创建泛型数组类型?

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

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

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

原因是什么?

213232 次浏览

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

有一个很好的概述在这里

引用:

泛型类型数组不是 允许,因为它们不可靠。的 问题是由于相互作用造成的 Java数组,它不是静态的 声音,但动态检查, 使用静态的泛型 声音和未动态检查。 下面是如何利用 漏洞:< / p >

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!
}
}
我们已经提议解决这个问题 使用静态安全数组的问题 (又名方差),但被拒绝 老虎。< / p >

——gafter

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

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

这是不可能的,因为Java完全在编译器级别上实现了泛型,并且每个类只生成一个类文件。 这被称为类型擦除

在运行时,已编译的类需要用相同的字节码处理它的所有使用。因此,new T[capacity]绝对不知道需要实例化什么类型。

我喜欢间接给出的答案 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中创建泛型数组类型。我误解了这个问题吗?

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

在我看来,如果你不能提供一个像样的解决方案,你只会落得更糟糕的结果。

常见的工作方法如下。

T[] ts = new T[n];

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

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

我更喜欢第一个例子,然而更多的学院派似乎更喜欢第二个例子,或者干脆不去想它。

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

注意:这是Collections库本身不会在没有警告的情况下编译的原因之一。如果在没有警告的情况下不能支持这个用例,那么恕我直言,泛型模型从根本上就有问题了。

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

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

返回一个包含所有 此列表中的元素按正确顺序排列;的运行时类型 返回的数组是指定数组的数组。如果列表符合 指定的数组,则在其中返回。否则,新数组为 的运行时类型分配 这个列表。< / p >

因此,一种解决方法是使用这个函数,即在数组中创建一个你想要的对象的ArrayList,然后使用toArray(T[] a)创建实际的数组。不会很快,但你没提你的要求。

有人知道toArray(T[] a)是如何实现的吗?

答案已经给出了,但如果你已经有一个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 < / p >
这是因为泛型是在他们创建后添加到java的,所以它有点笨拙,因为java的原始制作者认为,当创建数组时,类型将在创建时指定。这对泛型不起作用,所以你必须这么做 E[] array=(E[]) new Object[15]; 这将编译,但会给出警告。< / p >

在我的例子中,我只是想要一个堆栈数组,就像这样:

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

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

  1. 创建一个非泛型包装器类围绕堆栈(例如MyStack)
  2. MyStack[] stacks = new MyStack[2]工作得很好

丑陋,但Java是快乐的。

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

如果我们不能实例化泛型数组,为什么语言有泛型数组类型?没有对象的类型有什么意义呢?

我能想到的唯一原因是varargs - foo(T...)。否则,它们可以完全删除泛型数组类型。(好吧,他们并不真的需要为可变参数使用数组,因为在1.5之前不存在可变参数。这可能是另一个错误。)

所以这是一个谎言,你可以实例化泛型数组,通过varargs!

当然,泛型数组的问题仍然存在,例如。

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

我们可以用这个例子来实际演示通用的数组的危险。

另一方面,我们已经使用泛型变参数10年了,现在还没有崩溃。所以我们可以说问题被夸大了;这没什么大不了的。如果允许显式的泛型数组创建,我们会到处都有bug;但我们已经习惯了擦除的问题,我们可以接受它。

我们可以指出foo2来反驳这种说法,即规范让我们远离他们声称让我们远离的问题。如果Sun在1.5上有更多的时间和资源,我相信他们可以达成一个更令人满意的解决方案。

甲骨文教程:

不能创建参数化类型的数组。例如,以下代码不能编译:

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

下面的代码演示了在数组中插入不同类型时会发生什么:

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。

对我来说,这听起来很软弱。我认为任何对泛型有充分理解的人,都完全可以理解,甚至期望,在这种情况下ArrayStoredException不会被抛出。

正如其他人已经提到的,你当然可以通过一些技巧来创建。

但不建议这样做。

因为数组中的类型擦除,更重要的是数组中的covariance,它只允许子类型数组可以被赋值给超类型数组,这迫使你在试图取回值时使用显式类型强制转换,导致运行时ClassCastException,这是泛型试图消除的主要目标之一:在编译时更强的类型检查

Object[] stringArray = { "hi", "me" };
stringArray[1] = 1;
String aString = (String) stringArray[1]; // boom! the TypeCastException

一个更直接的例子可以在有效Java:第25项中找到。


协方差:如果S是T的子类型,则类型S[]的数组是T[]的子类型

试试这个:

List<?>[] arrayOfLists = new List<?>[4];

类可以声明类型为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; }
}

T vals [];/ /好吧

但是,你不能实例化T的数组 // vals = new T[10];//不能创建T

的数组 你不能创建T的数组的原因是没有办法 编译器了解实际创建的数组类型