y 在 T 型展开链表中。
这是不可能的。
泛型是编译器想象出来的。一旦您的 Java 代码被转换为类文件,或者如果它们没有(签名中的泛型),泛型完全消失,JVM 会将其视为注释。它完全没有效果。相反,编译器使用它来生成错误或警告并注入不可见的强制转换。这个:
List<String> x = new ArrayList<String>();
x.add("Hello");
String y = x.get(0);
在类代码中与编译没有区别:
List x = new ArrayList();
x.add("Hello");
String y = (String) x.get(0);
如果您难以理解这个想法,请尝试一下。编写两者,编译它,运行javap -c -v 来查看字节码。完全相同。
x.add(5) 不能替代x.add("Hello") 的原因仅仅是因为javac 不会让它发生。如果你破解 javac 以允许它,你会得到一个类文件就好了,它验证就好了。 x.add(5) 甚至会执行得很好。你会在下一行得到一个 ClassCastException,仅仅是因为你将 Integer 的一个实例强制转换为 String。
因此,无法分辨运行时new ArrayList<String>(); 和new ArrayList<Integer>() 之间的区别。明显地;泛型消失了;这两个都只是new ArrayList(),就是这样。
相比之下,数组是“具体化”的:它们不是 javac 想象的虚构。你实际上可以在运行时得到这些东西。 new String[0]和new Integer[0]是有区别的:
Object[] arr1 = new String[0];
System.out.println(arr1.getClass().getComponentType()); // prints 'String'
不可能为泛型编写相同的代码:
List<?> list1 = new ArrayList<String>();
System.out.println(--nothing you can write here will print String--);
因此,在您的“使用 T 展开的代码”中,T 不是您可以转换为实际运行时类型的东西,这意味着不可能创建一个 T 数组。
仍然很难相信这一点?细读java.util.List 的API,特别是它包含的各种toArray 方法。
查看无参数的:toArray()。这里有两种解释:
- 这个类的设计者是个彻头彻尾的白痴,因为它返回
Object[],这很愚蠢,因为显然应该返回T[]。
- 或者,也许正在发生其他事情,他们“知道”实际上不可能在那里返回
T[]。
正如本文的其余部分希望已经提出的那样,这是第二个原因。
幸运的是,还有另外 2 个 toArray 方法,这两个 do 都返回 T[],如您所愿。它们都基于 调用者 努力为您提供 T 类型的概念。
第一个版本是toArray(T[] in)。如果 toArray 代码足够大,它将使用提供的数组,但如果不是,它只会创建一个大小合适的新数组并返回它。在实践中,您总是调用 listOfStrings.toArray(new String[0])(您可能认为 new String[list.size()] 会更快 - 不,那会更慢1。一个很好的例子说明为什么编写更复杂的代码,因为它看起来更快是不好的想法。JVM 过于复杂,无法像这样预测性能)。
这里的技巧是,列表的 toArray 中的代码将获取该数组,获取其类(将创建的数组抛在一边),从中获取组件类型,然后使用它来创建一个新数组。
还有一个:toArray(IntFunction<T[]> arrayCreator)(您需要查看javadoc of Collection 才能看到它;它是继承的)。
这里我们要求调用者提供创建新数组的代码。你可以这样使用它:listOfStrings.toArray(String[]::new)。
选择你的毒药,或者两者都加。任何一种技巧都可以在这里发挥作用:
public T[][] getArrayOfBlocks(T[] dummy) {
Class<?> componentType = dummy.getClass().getComponentType();
@SuppressWarnings("unchecked")
T[][] arr = (T[][]) java.lang.reflect.Array.newInstance(componentType, this.nNodes, this.arraySize);
.. code continues here ..
}
或:
public T[][] getArrayOfBlocks(BiFunction<Integer, Integer, T[][]> arrayMaker) {
T[][] arr = arrayMaker.apply(this.nNodes, this.arraySize);
.. code continues here ..
}
Yes, they are both annoying. There are other options but they have significant downsides - the above 2 options are your best bet. That or forget about arrays. Why do you even want a `T[][]` in the first place? Arrays can't grow or shrink, assuming it's not a primitive array (and this isn't, by definition; generics cannot be primitive) they are not more performant, and their toString/equals/hashCode implementations are surprising (that's programmer-ese for 'basically broken'). Their API is non-existent. Why would you want to offer it?
1) In case you desire explanations for this one: It's because the toArray code is hotspot intrinsiced and knows it doesn't need to wipe out the memory space, whereas with `new String[100]`, those 100 references all need to be nulled out first because java guarantees you can't 'see' uninitialized memory.