为什么我无法在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个回答

1
由于泛型是在 Java 制作完成后添加的,因此它有些笨拙,因为 Java 的原始制作者认为在创建数组时会指定类型。所以这在泛型中不起作用,因此您必须执行以下操作: E[] array=(E[]) new Object[15]; 这可以编译,但会出现警告。

0

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

我能想到的唯一的理由,就是可变参数 - foo(T...)。否则他们完全可以摒弃泛型数组类型。(嗯,他们在使用可变参数之前并不一定非得使用数组,这可能是另一个错误。)

因此这是个谎言,你可以通过可变参数实例化泛型数组!

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

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

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

另一方面,我们已经使用了十年的通用可变参数,天空还没有塌下来。所以我们可以认为问题被夸大了;这不是什么大问题。如果允许显式通用数组创建,我们会遇到偶尔的错误;但我们已经习惯了擦除的问题,我们可以应对它。

我们可以指向foo2来反驳规范防止他们声称防止我们出现问题的说法。如果Sun有更多时间和资源进行1.5的开发,我相信他们可以达到更令人满意的解决方案。


0

T vals[]; // 可行

但是,您无法实例化T的数组 // vals = new T[10]; // 无法创建T的数组

无法创建T的数组的原因是编译器无法知道要创建什么类型的数组。


0

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

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

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

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

那么,有人知道如何实现toArray(T[] a)吗?


3
List.toArray(T[]) 方法之所以能够正常工作,是因为在运行时你实际上提供了组件类型 T(你提供了一个期望的数组类型实例,方法可以从中获取数组类,然后获取组件类 T)。在运行时知道实际的组件类型,你就可以使用 Array.newInstance() 创建该类型的数组。你会发现,在许多问题中都有提到如何使用这种方式创建编译时未知的数组类型。但是 OP 具体询问的是 为什么 不能使用 new T[] 语法,这是另外一个问题。 - newacct

0

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

但这并不推荐。

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

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

更直接的例子可以在Effective Java: Item 25中找到。


协变性:如果S是T的子类型,那么类型为S[]的数组是类型为T[]的数组的子类型。


-2

试试这个:

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

这实际上是有效的,并且不会产生任何编译时错误。有人可以解释一下吗? - Aditya Mayukh Som
1
@AdityaMayukhSom 另外,List[] array = new List[4]也可以工作,因为"List"是可具体化的。也就是说,在擦除后,它仍然是一个"真实"的类型。根据JLS 15.10,数组创建表达式中的类型必须是"可具体化"的。 根据JLS 4.7,如果类型参数是通配符,那么类型可以是可具体化的。 通配符意味着你不知道它是一个什么类型的List,限制了你只能进行安全操作,就像原始类型一样。 - Ben

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