无法制作从 T 型展开链表中创建二维数组的方法

having trouble making a method that creates a 2d array out of a T type unrolled linked list

public T[][] getArrayOfBlocks() {
    Node node = this.first;
    @SuppressWarnings("unchecked")
    T[][] result = (T[][]) new Object[this.nNodes][this.arraySize];
    for(int i = 0; i < this.nNodes; i++)
    {
        for(int j = 0; j < this.arraySize; j++)
            if(node.a[j] != null)
                result[i][j] = node.a[j];
        node = node.next;
    }
    return result;
}

(我是java的新手所以我的措辞会有点奇怪) 我试图制作一种方法,从 T 类型展开的链表中创建一个二维数组。当我使用 Integer class 而不是 T 类型测试上面的方法时,我得到一个错误

Exception in thread "main" java.lang.ClassCastException: class [[Ljava.lang.Object; cannot be cast to class [[Ljava.lang.Integer; ([[Ljava.lang.Object; and [[Ljava.lang.Integer; are in module java.basa of loader 'bootstrap')

所以是的,我想知道是否有任何方法可以在不更改 return 类型的情况下解决此错误。 提前致谢:)

y out of a T type unrolled linked list.

这是不可能的。

泛型是编译器想象力的产物。泛型 完全消失 一旦您的 java 代码被转换为 class 文件,或者如果它们没有(签名中的泛型),JVM 将其视为一条评论。完全没有效果。相反,编译器使用它来生成错误或警告并注入不可见的强制转换。这个:

List<String> x = new ArrayList<String>();
x.add("Hello");
String y = x.get(0);

最终出现在 class 代码中,与编译没有区别:

List x = new ArrayList();
x.add("Hello");
String y = (String) x.get(0);

如果您很难理解这个想法,请尝试一下。两个都写,编译吧,运行 javap -c -v 看字节码。相同。

x.add(5) 不能替代 x.add("Hello") 的原因很简单,因为 javac 不会让它发生。如果你 hack javac 允许它,你会得到一个 class 文件就好了,它验证也很好。 x.add(5) 甚至可以正常执行。您会在下一行收到 ClassCastException,这仅仅是因为您正在将 Integer 的实例转换为 String。

因此,无法在 运行 时区分 new ArrayList<String>();new ArrayList<Integer>() 之间的区别。明显地;泛型消失;这些都是 new ArrayList(),仅此而已。

相比之下,数组是 'reified':它们不是 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--);

因此,在您的 'unrolled code with T' 中,T 不是您可以转换为实际 运行时间类型的东西,这意味着不可能制作 T 的数组。

仍然难以相信这一点?仔细阅读 java.util.List 的 API,特别是其中包含的各种 toArray 方法。

看看无参数的:toArray()。这里有两种解释:

  • 这个 class 的设计者是个彻头彻尾的白痴,因为那个 return 的 Object[] 很愚蠢,因为显然应该 return T[].
  • 或者,也许发生了其他事情,他们 'know' 实际上不可能 return 一个 T[] 那里。

正如本文的其余部分 post 希望已经建议的那样,这是第二个原因。

幸运的是,还有另外 2 种 toArray 方法,而这两种 do 都 return T[] 如您所愿。它们都基于 调用者 努力为您提供 T 类型的概念。

第一个版本是toArray(T[] in)。如果提供的数组足够大,则 toArray 代码将使用提供的数组,但如果不够大,它只会创建一个大小合适的新数组并 returns 。在实践中,你总是调用 listOfStrings.toArray(new String[0])(你可能认为 new String[list.size()] 会更快 - 不,那更慢 1。一个很好的例子说明为什么要编写更复杂的代码因为它看起来更快是个坏主意。JVM 太复杂了,无法像这样预测性能。

这里的技巧是列表的 toArray 中的代码将获取该数组,获取它的 class(将创建的数组扔到一边),从中获取组件类型,然后使用它来创建一个新的数组。

还有一个: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.