简体   繁体   English

将通用列表转换为数组。 为什么我需要使用克隆?

[英]Converting a generic List to an Array. Why do I need use clone?

I faced a problem yesterday, when I was writing my homework.昨天我在写作业时遇到了一个问题。 I finished the homework, but I still don't really understand why my code works.我完成了作业,但我仍然不明白为什么我的代码有效。 I had to write a sort function that takes an varargs of any comparable generic object as an argument and return the argument.我必须编写一个排序函数,它将任何可比较的通用对象的可变参数作为参数并返回该参数。 The problem was that I had to return an array of sorted objects.问题是我必须返回一个排序对象数组。 So I had to learn more about varargs lists and arrays.所以我必须学习更多关于可变参数列表和数组的知识。

The function was defined like this.函数是这样定义的。

public <T extends Comparable<T>> T[] stableSort(T ... items)

and inside the function I made a list, which I would sort and do all the work on.在函数内部,我列出了一个列表,我将对其进行排序并完成所有工作。

List<T> list = new ArrayList<T>(Arrays.asList(items));

and at the end of the function I was returning list toArray so that it matched the output type T[].在函数结束时,我将列表返回到数组,以便它与输出类型 T[] 匹配。

list.toArray(items.clone());

My question is since I already made the list from the varargs, why do I have to do items.clone() inside the toArray function.我的问题是因为我已经从可变参数中创建了列表,为什么我必须在 toArray 函数中执行 items.clone() 。 That seemed like doing two same things to me.这对我来说就像是在做两件同样的事情。 I thought arrays.asList() would clone the values of array to list and I don't get why am I doing it again at the end of the code in toArray().我认为 arrays.asList() 会将数组的值克隆到列表中,但我不明白为什么我在 toArray() 中的代码末尾再次这样做。 I know that this was the correct way to write it, because I finished the homework yesterday and found out this way from forums of the class, but I still don't understand why.我知道这是正确的写法,因为我昨天完成了作业,从班级的论坛上发现了这种方式,但我仍然不明白为什么。

EDIT编辑

The task required me to create a new array with sorted files and return it instead.该任务要求我创建一个包含排序文件的新数组并返回它。 Due to Type Erasure, it is not possible to instantiate an array of a generic type without a reference to a class that fits the generic.由于类型擦除,无法在没有引用适合泛型的类的情况下实例化泛型类型的数组。 However, the varargs array has type T, so I should have cloned an array of a type which fits the generic constraints.但是,可变参数数组的类型为 T,所以我应该克隆一个适合泛型约束的类型的数组。 Which I didn't know how to do in time.我不知道该怎么做。 So I decided to use list to make my time easier till the deadline.所以我决定使用列表来让我的时间更轻松,直到截止日期。

My question is since I already made the list from the varargs, why do I have to do items.clone()我的问题是因为我已经从可变参数中列出了列表,为什么我必须做 items.clone()

You are right.你是对的。 Unfortunately, the compiler will be unable to determine the type of the array if you simply use the toArray() method.不幸的是,如果您仅使用toArray()方法,编译器将无法确定数组的类型。 You should get a compilation error saying Cannot convert from Object[] to T[] .您应该收到一个编译错误,提示无法从 Object[] 转换为 T[] The call to item.clone() is required to assist the compiler in type-inference.需要调用item.clone()来帮助编译器进行类型推断。 An alternate approach would be to say return (T[])list.toArray另一种方法是说return (T[])list.toArray

That said, I would not recommend either of the approaches.也就是说,我不会推荐任何一种方法。 It doesn't really make sense to convert an array to a list and convert it back to an array in the first place.首先将数组转换为列表并将其转换回数组并没有真正意义。 I don't see any significant take-aways that you would even understand from this code.我没有看到您甚至可以从这段代码中理解的任何重要内容。

It seems to me there are a few questions here, that may have come together to create some confusion as to why what needs to be done.在我看来,这里有几个问题,这些问题汇集在一起​​可能会造成一些关于为什么需要做的混乱。

I thought arrays.asList() would clone the values of array to list and I don't get why am I doing it again at the end of the code in toArray().我认为 arrays.asList() 会将数组的值克隆到列表中,但我不明白为什么我在 toArray() 中的代码末尾再次这样做。

This is probably just the way it is typed, but it should be made clear that you don't clone the objects in the array, but only make a new List with the references to the objects in the array.这可能只是它的输入方式,但应该明确的是,您不克隆数组中的对象,而只使用对数组中对象的引用创建一个新 List。 The objects themselves will be the same ones in the array as in the List.对象本身将与数组中的对象相同。 I believe that is probably what you meant, but terminology can be tricky here.我相信这可能就是你的意思,但术语在这里可能很棘手。

I thought arrays.asList() would clone the values of array to list...我认为 arrays.asList() 会克隆数组的值以列出......

Not really.并不真地。 Using Arrays.asList(T[] items) will provide a view onto the array items that implements the java.util.List interface.使用Arrays.asList(T[] items)将提供实现 java.util.List 接口的数组items视图 This is a fixed-size list.这是一个固定大小的列表。 You can't add to it.你不能添加它。 Changes to it, such as replacing an element or sorting in-place, will pass through to the underlying array.对它的更改,例如替换元素或就地排序,将传递到底层数组。 So if you do this所以如果你这样做

List<T> l = Arrays.asList(T[] items);
l.set(0, null);

... you've just set the element at index 0 of the actual array items to null. ...您刚刚将实际数组items索引 0 处的元素设置为 null。

The part of your code where you do this执行此操作的代码部分

List<T> list = new ArrayList<T>(Arrays.asList(items));

could be written as this:可以写成这样:

List<T> temp = Arrays.asList(items);
List<T> list = new ArrayList<T>(temp);

The first line is the "view", the second line will effectively create a new java.util.ArrayList and fill it with the values of the view in the order they are returned in by their iterator (which is just the order in the array).第一行是“视图”,第二行将有效地创建一个新的java.util.ArrayList并按照它们的迭代器返回的顺序(这只是数组中的顺序)用视图的值填充它)。 So any changes to list that you make now don't change array items , but keep in mind that it's still just a list of references.因此,您现在对list所做的任何更改都不会更改 array items ,但请记住,它仍然只是一个引用列表。 items and list are referencing the same objects, just with their own order. itemslist引用相同的对象,只是按照它们自己的顺序。

My question is since I already made the list from the varargs, why do I have to do items.clone() inside the toArray function.我的问题是因为我已经从可变参数中创建了列表,为什么我必须在 toArray 函数中执行 items.clone() 。

There could be two reasons here.这里可能有两个原因。 The first is as CKing said in his/her answer.首先是CKing在他/她的回答中所说的。 Because of type erasure and the way arrays are implemented in Java (there are separate array types depending on whether it's an array of primitives or references) the JVM would not know what type of array to create if you just called toArray() on the list, which is why that method has a return type of Object[] .由于类型擦除和数组在 Java 中的实现方式(有单独的数组类型取决于它是原始数组还是引用数组),如果您只是在列表中调用toArray() ,JVM 将不知道要创建什么类型的数组,这就是该方法的返回类型为Object[] So in order to get an array of a specific type, you must provide an array to the method that can be used at run-time to determine the type from.因此,为了获取特定类型的数组,您必须向方法提供一个数组,该数组可在运行时用于确定类型。 This is a piece of the Java API where the fact that generics work via type-erasure, aren't retained at run-time and the particular way in which arrays work all come together to surprise the developer.这是 Java API 的一部分,其中泛型通过类型擦除工作的事实不会在运行时保留,并且数组工作的特定方式都让开发人员感到惊讶。 A bit of abstraction is leaking there . 一些抽象正在那里泄漏

But there might be a second reason.但可能还有第二个原因。 If you go check the toArray(T[] a) method in the Java API , you'll notice this part:如果您检查Java API 中toArray(T[] a)方法,您会注意到这一部分:

If the list fits in the specified array, it is returned therein.如果列表适合指定的数组,则在其中返回。 Otherwise, a new array is allocated with the runtime type of the specified array and the size of this list.否则,将使用指定数组的运行时类型和此列表的大小分配一个新数组。

Suppose some code by another dev is using your stableSort method like this:假设另一个开发人员的一些代码正在使用您的 stableSort 方法,如下所示:

T[] items;
// items is created and filled...
T[] sortedItems = stableSort(items);

If you didn't do the clone, what would happen in your code would be this:如果你不做克隆,你的代码会发生什么:

List<T> list = new ArrayList<T>(Arrays.asList(items));
// List is now a new ArrayList with the same elements as items
// Do some things with list, such as sorting
T[] result = list.toArray(items);
// Seeing how the list would fit in items, since it has the same number of elements,
// result IS in fact items

So now the caller of your code gets sortedItems back, but that array is the same array as the one he passed in, namely items .所以现在你的代码的调用者得到sortedItems ,但该数组他传入的数组相同,即items You see, varargs are nothing more than syntactic sugar for a method with an array argument, and are implemented as such.你看,varargs 只不过是一个带有数组参数的方法的语法糖,并且是这样实现的。 Perhaps the caller didn't expect the array he passed in as an argument to be changed, and might still need the array with the original order.也许调用者没想到他作为参数传入的数组会发生变化,可能仍然需要原始顺序的数组。 Doing a clone first will avoid that and makes the effect of the method less surprising.首先进行克隆将避免这种情况,并使该方法的效果不那么令人惊讶。 Good documentation on your methods is crucial in situations like this.在这种情况下,关于您的方法的良好文档至关重要。

It's possible that code testing your assignment's implementation wants a different array back, and it's an actual acquirement that your method adheres to that contract.测试您的分配实现的代码可能需要返回一个不同的数组,并且您的方法遵守该合同是实际获得。

EDIT:编辑:

Actually, your code could be much simpler.实际上,您的代码可以简单得多。 You'll achieve the same with:您将通过以下方式实现相同的目标:

T[] copy = items.clone();
Arrays.sort(copy);
return copy;

But your assignment might have been to actually implement a sorting algorithm yourself, so this point may be moot.但是您的任务可能是自己实际实现排序算法,因此这一点可能没有实际意义。

You need to use this:你需要使用这个:

List<T> list = new ArrayList<T>(Arrays.asList(items));

when you want to do an inline declaration .当你想做一个内联声明时

For example:例如:

List<String> list = new ArrayList<String>(Arrays.asList("aaa", "bbb", "ccc"));

By the way, you didn't have to use return list.toArray(items.clone());顺便说一句,您不必使用return list.toArray(items.clone()); You could have used, for example, return list.toArray(Arrays.copyOf(items, 0));例如,您可以使用return list.toArray(Arrays.copyOf(items, 0)); , where you are passing to list.toArray() an empty array that contains none of the arguments from items . ,您将向list.toArray()传递一个空数组,其中不包含来自items任何参数。

The whole point of passing an argument to the version of list.toArray() that takes an argument, is to provide an array object whose actual runtime class is the actual runtime class of the array object it wants to return.将参数传递给采用参数的list.toArray()版本的list.toArray()在于提供一个数组对象,其实际运行时类是它想要返回的数组对象的实际运行时类。 This could have been achieved with items.clone() , or with items itself (though that would cause list.toArray() to write the resulting elements into the original array pointed to by items which you may not want to happen), or with, as I showed above, an empty array that has the same runtime class.这可以通过items.clone()或使用items本身来实现(尽管这会导致list.toArray()将结果元素写入您可能不希望发生的items指向的原始数组中),或者使用,正如我上面展示的,一个具有相同运行时类的空数组。

By the way, the need to pass the argument to list.toArray() is not a generics type issue at all.顺便说一下,需要将参数传递给list.toArray()根本不是泛型类型的问题。 Even if you had written this with pre-generics Java, you would have had to do the same thing.即使您是使用前泛型 Java 编写的,您也必须做同样的事情。 This is because the version of List::toArray() that took no arguments always returns an array object whose actual runtime class is Object[] , as the List doesn't know at runtime what its component type is.这是因为没有参数的List::toArray()版本总是返回一个数组对象,它的实际运行时类是Object[] ,因为List在运行时不知道它的组件类型是什么。 To have it return an array object whose actual runtime class is something different, you had to give it an example array object of the right runtime class to follow.要让它返回一个数组对象,它的实际运行时类是不同的,你必须给它一个正确的运行时类的示例数组对象以供遵循。 That's why pre-generics Java also had the version of List::toArray() that took one argument;这就是为什么前泛型 Java 也有一个接受一个参数的List::toArray()版本; even though in pre-generics, both methods were declared to return Object[] , they are different as the actual runtime class returned is different.即使在前泛型中,两种方法都被声明为返回Object[] ,但它们是不同的,因为返回的实际运行时类是不同的。

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

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