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

查看:119
本文介绍了集合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的事实。这可能是说第一个元素不存在于空列表中,因此设置为 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:


如果此集合适合指定的数组,并且有空余空间(即数组的元素多于此集合),则元素紧接在set结束后的数组中设置为null。

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:


这只有在调用者知道这个集合不包含任何null时才有用于确定此集合的长度元素。

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?

推荐答案

< T>收集上的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问题。调用者想要重用一个数组,所以显然需要传入它。与no-arg 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是并发集合,因此调用之前的大小可能会随时更改,因此调用者无法保证目标数组的大小足够大,也无法知道复制的元素数。调用者唯一能做的就是将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 写入数组位置超越从集合中复制的最后一个元素。如果已知源集合没有空值,则可以使调用者确定复制的元素数。调用之后,调用者可以搜索数组中的第一个空值。如果有,则其位置确定复制的元素数。如果数组中没有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(新的MyType [coll.size()])实际上更慢。这是因为需要将数组初始化为null,然后用集合的内容填充它。请参阅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天全站免登陆