集合 emptyList/singleton/singletonList/List/Set toArray [英] Collections emptyList/singleton/singletonList/List/Set toArray

查看:29
本文介绍了集合 emptyList/singleton/singletonList/List/Set toArray的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有这个代码:

String[] left = { "1", "2" };
String[] leftNew = Collections.emptyList().toArray(left);
System.out.println(Arrays.toString(leftNew));

这将打印[null, 2].这种排序是有道理的,因为我们有一个空列表,它以某种方式假设处理我们传递一个更大的数组并将第一个元素设置为空的事实.这可能是说空列表中不存在第一个元素,因此将其设置为 null.

This will print [null, 2]. This sort of makes sense, since we have an empty list it is somehow suppose to cope with the fact that we are passing an array that is bigger and sets the first element to null. This is probably saying that the first element does not exist in the empty list, thus it set to null.

但这仍然令人困惑,因为我们传递一个特定类型的数组只是为了帮助推断返回的数组的类型;但无论如何,这至少有一定的逻辑.但如果我这样做呢:

But this is still confusing, since we pass an array with a certain type only to help infer the type of the returned array; but anyway this is something that has at least a certain logic. But what if I do:

String[] right = { "nonA", "b", "c" };
// or Collections.singletonList("a");
// or a plain List or Set; does not matter
String[] rightNew = Collections.singleton("a").toArray(right);
System.out.println(Arrays.toString(rightNew));

以前面的例子作为参考,我希望这个例子显示:

Taking the previous example as a reference, I would expect this one to show:

["a", "b", "c"]

但是,对我来说有点出乎意料,它会打印:

But, a bit un-expected for me, it prints:

[a, null, c]

当然,我会查看明确说明这是预期的文档:

And, of course, I go to the documentation that explicitly says this is expected:

如果这个集合适合指定的数组并且有剩余空间(即该数组的元素比这个集合多),则紧跟该集合末尾的数组中的元素被设置为空.

If this set fits in the specified array with room to spare (i.e., the array has more elements than this set), the element in the array immediately following the end of the set is set to null.

好的,好的,这至少是有记录的.但它后来说:

OK, good, this is at least documented. But it later says:

仅当调用者知道此集合不包含任何空元素时,这才可用于确定此集合的长度.

This is useful in determining the length of this set only if the caller knows that this set does not contain any null elements.

这是文档中最让我困惑的部分:|

This is the part in the documentation that confuses me the most :|

还有一个更有趣的例子,但对我来说毫无意义:

And an even funner example that makes little sense to me:

String[] middle = { "nonZ", "y", "u", "m" };
List<String> list = new ArrayList<>();
list.add("z");
list.add(null);
list.add("z1");
System.out.println(list.size()); // 3

String[] middleNew = list.toArray(middle);
System.out.println(Arrays.toString(middleNew));

这将打印:

[z, null, z1, null]

所以它清除了数组中的最后一个元素,但为什么在第一个示例中不这样做?

So it clears the last element from the array, but why it would not do that in the first example?

有人可以在这里解释一下吗?

Can someone shed some light here?

推荐答案

Collection 上的 T[] toArray(T[] a) 方法很奇怪,因为它试图同时实现两个目的.

The <T> T[] toArray(T[] a) method on Collection is weird, because it's trying to fulfill two purposes at once.

首先,让我们看看toArray().这从集合中获取元素并在 Object[] 中返回它们.也就是说,返回数组的组件类型总是Object.这很有用,但它不能满足其他几个用例:

First, let's look at toArray(). This takes the elements from the collection and returns them in an Object[]. That is, the component type of the returned array is always Object. That's useful, but it doesn't satisfy a couple other use cases:

1) 如果可能,调用者希望重用一个现有的数组;和

1) The caller wants to re-use an existing array, if possible; and

2) 调用者想要指定返回数组的组件类型.

2) The caller wants to specify the component type of the returned array.

处理案例 (1) 被证明是一个相当微妙的 API 问题.调用者想要重用一个数组,所以显然需要传入. 与无参数 toArray() 方法不同,它返回一个大小合适的数组,如果调用者的数组是重新使用,我们需要一种方法来返回复制的元素数量.好的,让我们有一个看起来像这样的 API:

Handling case (1) turns out to be a fairly subtle API problem. The caller wants to re-use an array, so it clearly needs to be passed in. Unlike the no-arg toArray() method, which returns an array of the right size, if the caller's array is re-used, we need to a way to return the number of elements copied. OK, let's have an API that looks like this:

int toArray(T[] a)

调用者传入一个数组,该数组被复用,返回值是复制进去的元素个数.不需要返回数组,因为调用者已经有了对它的引用.但是如果数组太小怎么办?好吧,也许会抛出异常.事实上,这就是 Vector.copyInto 确实如此.

The caller passes in an array, which is reused, and the return value is the number of elements copied into it. The array doesn't need to be returned, because the caller already has a reference to it. But what if the array is too small? Well, maybe throw an exception. In fact, that's what Vector.copyInto does.

void copyInto​(Object[] anArray)

这是一个糟糕的 API.它不仅不返回复制的元素数量,而且如果目标数组太短,它还会抛出 IndexOutOfBoundsException.由于 Vector 是一个并发集合,在调用之前大小可能随时发生变化,因此调用者不能保证目标数组有足够的大小,也不能知道复制的元素数量.调用者唯一能做的就是锁定整个序列周围的向量:

This is a terrible API. Not only does it not return the number of elements copied, it throws IndexOutOfBoundsException if the destination array is too short. Since Vector is a concurrent collection, the size might change at any time before the call, so the caller cannot guarantee that the destination array is of sufficient size, nor can it know the number of elements copied. The only thing the caller can do is to lock the Vector around the entire sequence:

synchronized (vec) {
    Object[] a = new Object[vec.size()];
    vec.copyInto(a);
}

呃!

Collections.toArray(T[]) API 通过在目标数组太小时有不同的行为来避免这个问题.它不会像 Vector.copyInto() 那样抛出异常,而是分配一个合适大小的 new 数组.这交换了阵列重用情况以获得更可靠的操作.现在的问题是调用者无法判断它的数组是被重用还是分配了一个新数组.因此,toArray(T[]) 的返回值需要返回一个数组:参数数组,如果它足够大,或者新分配的数组.

The Collections.toArray(T[]) API avoids this problem by having different behavior if the destination array is too small. Instead of throwing an exception like Vector.copyInto(), it allocates a new array of the right size. This trades away the array-reuse case for more reliable operation. The problem is now that caller can't tell whether its array was reused or a new one was allocated. Thus, the return value of toArray(T[]) needs to return an array: the argument array, if it was large enough, or the newly allocated array.

但是现在我们有另一个问题.我们不再有办法告诉调用者从集合中复制到数组中的元素数量.如果目标数组是新分配的,或者数组恰好是正确的大小,则数组的长度是复制的元素数.如果目标数组大于复制的元素数,则该方法尝试通过将 null 写入数组位置 一个多 从集合中复制的最后一个元素.如果知道源集合没有空值,这将使调用者能够确定复制的元素数.调用后,调用者可以搜索数组中的第一个空值.如果有,它的位置决定了复制的元素数量.如果数组中没有空值,则知道复制的元素数等于数组的长度.

But now we have another problem. We no longer have a way to tell the caller the number of elements that were copied from the collection into the array. If the destination array was newly allocated, or the array happens to be exactly the right size, then the length of the array is the number of elements copied. If the destination array is larger than the number of elements copied, the method attempts to communicate to the caller the number of elements copied, by writing a null to the array location one beyond the last element copied from the collection. If it's known that the source collection has no null values, this enables the caller to determine the number of elements copied. After the call, the caller can search for the first null value in the array. If there is one, its position determines the number of elements copied. If there is no null in the array, it knows that the number of elements copied equals the length of the array.

坦率地说,这很蹩脚.但是,考虑到当时语言的限制,我承认我没有更好的选择.

Quite frankly, this is pretty lame. However, given the constraints on the language at the time, I admit I don't have a better alternative.

我认为我从未见过任何以这种方式重用数组或检查空值的代码.这可能是早期内存分配和垃圾收集很昂贵的遗留问题,因此人们希望尽可能多地重用内存.最近,使用这种方法的惯用语是上面描述的第二个用例,即建立所需的数组组件类型,如下所示:

I don't think I've ever seen any code that reuses arrays or that checks for nulls this way. This is probably a holdover from the early days when memory allocation and garbage collection were expensive, so people wanted to reuse memory as much as possible. More recently, the accepted idiom for using this method has been the second use case described above, that is, to establish the desired component type of the array as follows:

MyType[] a = coll.toArray(new MyType[0]);

(为此目的分配一个零长度的数组似乎很浪费,但事实证明,这种分配可以通过 JIT 编译器优化掉,而明显的替代方法 toArray(new MyType[coll.size()]) 实际上更慢.这是因为需要将数组初始化为空值,然后用集合的内容填充它.请参阅 Alexey Shipilev 关于此主题的文章,远古智慧阵列.)

(It seems wasteful to allocate a zero-length array for this purpose, but it turns out that this allocation can be optimized away by the JIT compiler, and the obvious alternative toArray(new MyType[coll.size()]) is actually slower. This is because of the need to initialize the array to nulls, and then to fill it in with the collection's contents. See Alexey Shipilev's article on this topic, Arrays of Wisdom of the Ancients.)

然而,许多人发现零长度数组违反直觉.在 JDK 11 中,有一个新的 API 允许人们改用数组构造函数引用:

However, many people find the zero-length array counterintuitive. In JDK 11, there is a new API that allows one to use an array constructor reference instead:

MyType[] a = coll.toArray(MyType[]::new);

这让调用者指定数组的组件类型,但它让集合提供大小信息.

This lets the caller specify the component type of the array, but it lets the collection provide the size information.

这篇关于集合 emptyList/singleton/singletonList/List/Set toArray的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆