简体   繁体   English

无法创建从 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;
}

(Im a newbie in java so my wording will be a bit weird) Im trying to make a method that creates a 2d array out of a T type unrolled linked list. (我是 Java 新手,所以我的措辞会有点奇怪)我试图制作一种方法,该方法可以从 T 型展开链表中创建一个二维数组。 When i test out the method above using the Integer class instead of the T type i get an error that says当我使用 Integer 类而不是 T 类型测试上述方法时,我收到一个错误消息

Exception in thread "main" java.lang.ClassCastException: class [[Ljava.lang.Object;线程“main”中的异常 java.lang.ClassCastException: class [[Ljava.lang.Object; cannot be cast to class [[Ljava.lang.Integer;不能转换为类 [[Ljava.lang.Integer; ([[Ljava.lang.Object; and [[Ljava.lang.Integer; are in module java.basa of loader 'bootstrap') ([[Ljava.lang.Object; 和 [[Ljava.lang.Integer; 在加载器‘bootstrap’的 java.basa 模块中)

So yeah i would like to know if theres any way to solve this error without changing the return type.所以是的,我想知道是否有任何方法可以在不更改返回类型的情况下解决此错误。 Thanks in advance :)提前致谢 :)

y out of a T type unrolled linked list. y 出一个 T 型展开的链表。

This is impossible.这是不可能的。

Generics are a figment of the compiler's imagination.泛型是编译器想象力的虚构。 Generics completely disappear once your java code has been turned into a class file, or if they don't (generics in signatures), the JVM treats it as a comment.一旦您的 Java 代码变成类文件,泛型就会完全消失,或者如果它们没有(签名中的泛型),JVM 会将其视为注释。 It has absolutely no effect.它完全没有效果。 Instead, the compiler uses it to generate errors or warnings and inject invisible casts.相反,编译器使用它来生成错误或警告并注入不可见的强制转换。 This:这个:

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

ends up in class code as indistinguishable from compiling:最终在类代码中与编译无法区分:

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

Try it if you're having a hard time getting your head around this idea.如果您很难理解这个想法,请尝试一下。 Write both, compile it, run javap -c -v to see the bytecode.两者都写,编译,运行javap -c -v查看字节码。 Identical.完全相同的。

The reason x.add(5) would not work as replacement for x.add("Hello") is simply because javac won't let it happen. x.add(5)不能替代x.add("Hello")原因仅仅是因为javac不会让它发生。 If you hack javac to allow it, you get a class file just fine and it verifies just fine.如果你 hack javac 允许它,你会得到一个很好的类文件,它验证也很好。 The x.add(5) will even execute just fine. x.add(5)甚至会执行得很好。 You'd get a ClassCastException on the next line, simply because you're casting an instance of Integer to String.您会在下一行得到 ClassCastException,这仅仅是因为您将 Integer 的实例转换为 String。

As a consequence, there is no way to tell the difference between a new ArrayList<String>();因此,无法区分new ArrayList<String>(); and a new ArrayList<Integer>() at runtime.和一个new ArrayList<Integer>()在运行时。 Obviously;明显地; generics disappears;泛型消失; those are both just new ArrayList() , that's it.那些都只是new ArrayList() ,就是这样。

In contrast, arrays are 'reified': They aren't a figment of javac's imagination.相比之下,数组是“具体化的”:它们不是 javac 的想象。 You can actually get this stuff at runtime.你实际上可以在运行时得到这些东西。 There is a difference between new String[0] and new Integer[0] : new String[0]new Integer[0]之间有区别:

Object[] arr1 = new String[0];
System.out.println(arr1.getClass().getComponentType()); // prints 'String'

It is impossible to write the identical code for generics:不可能为泛型编写相同的代码:

List<?> list1 = new ArrayList<String>();
System.out.println(--nothing you can write here will print String--);

Hence, in your 'unrolled code with T', T is not something you can translate to an actual runtime type, and that means it is impossible to make an array of T.因此,在您的“使用 T 展开的代码”中,T 不是您可以转换为实际运行时类型的东西,这意味着不可能制作 T 数组。

Still having a hard time believing this?仍然很难相信这一点? Peruse the API of java.util.List , specifically the various toArray methods it contains.仔细阅读java.util.List的 API,特别是它包含的各种toArray方法。

Look at the no-args one: toArray() .看看无参数的: toArray() There are two explanations here:这里有两种解释:

  • The designer of this class was an utter idiot, because that returns Object[] , which is stupid, because clearly that should return T[] .这个类的设计者是一个彻头彻尾的白痴,因为它返回Object[] ,这是愚蠢的,因为显然应该返回T[]
  • Or, perhaps something else is going on and they 'know' that it is in fact impossible to return a T[] there.或者,也许其他事情正在发生并且他们“知道”实际上不可能在那里返回T[]

It's, as the rest of this post hopefully already suggested, the second reason.正如这篇文章的其余部分希望已经暗示的那样,这是第二个原因。

Fortunately, there are 2 other toArray methods and those two do both return T[] as you desire.幸运的是,还有另外 2 个toArray方法,并且这两个方法可以根据需要返回T[] They are both based around the notion that the caller puts in the effort of providing that T type for you.它们都基于调用者努力为您提供 T 类型的概念。

The first version is toArray(T[] in) .第一个版本是toArray(T[] in) The toArray code will use the provided array if it is large enough, but if not, it just makes a new one that is the right size and returns it.如果提供的数组足够大,toArray 代码将使用提供的数组,但如果没有,它只会创建一个大小合适的新数组并返回它。 In practice, you always call listOfStrings.toArray(new String[0]) (you may think new String[list.size()] would be faster - no, that is slower 1 . A nice example of why writing more complex code because it seems faster is a bad idea. JVMs are far too complex to predict performance like this).在实践中,你总是调用listOfStrings.toArray(new String[0]) (你可能认为new String[list.size()]会更快——不,那会更慢1 。一个很好的例子,说明为什么要编写更复杂的代码,因为似乎更快是一个坏主意。JVM 太复杂了,无法像这样预测性能)。

The trick here is that the code in list's toArray will take that array, grab its class (tossing the created array aside), get the component type from that, and then use that to make a new array.这里的技巧是列表的 toArray 中的代码将获取该数组,获取它的类(将创建的数组扔到一边),从中获取组件类型,然后使用它来创建一个新数组。

There is another one, too: toArray(IntFunction<T[]> arrayCreator) (you need to look at the javadoc of Collection to see it; it is inherited).还有另一个: toArray(IntFunction<T[]> arrayCreator) (你需要查看Collectionjavadoc才能看到它;它是继承的)。

Here we ask the caller to provide code that makes a new array.这里我们要求调用者提供创建新数组的代码。 You use it like this: listOfStrings.toArray(String[]::new) .你可以这样使用它: listOfStrings.toArray(String[]::new)

Pick your poison, or add both.选择你的毒药,或者两者都加。 Either trick will work here:任何一种技巧都可以在这里工作:

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

or:或者:

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.

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM