gpt4 book ai didi

java - 集合 emptyList/singleton/singletonList/List/Set toArray

转载 作者:搜寻专家 更新时间:2023-10-30 21:05:08 25 4
gpt4 key购买 nike

假设我有这个代码:

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

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

但这仍然令人困惑,因为我们传递特定类型的数组只是为了帮助推断返回数组的类型;但无论如何,这至少有一定的逻辑。但如果我这样做:
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));

以前面的示例作为引用,我希望这个示例显示:
["a", "b", "c"]

但是,对我来说有点出乎意料,它打印:
[a, null, c]

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

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.



好的,好的,这至少是有记录的。但后来又说:

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



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

还有一个对我来说意义不大的更有趣的例子:
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]

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

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

最佳答案

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

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

1) 如果可能,调用者想要重用现有的数组;和

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

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

int toArray(T[] a)

调用者传入一个数组,该数组被重用,返回值是复制到其中的元素数。不需要返回数组,因为调用者已经有了对它的引用。但是如果数组太小怎么办?好吧,也许会抛出异常。事实上,这就是 Vector.copyInto确实。
void copyInto​(Object[] anArray)

这是一个糟糕的 API。它不仅不返回复制的元素数,还抛出 IndexOutOfBoundsException如果目标数组太短。由于 Vector 是一个并发集合,在调用之前大小可能随时发生变化,因此调用者无法保证目标数组有足够的大小,也无法知道复制的元素数量。调用者唯一能做的就是在整个序列周围锁定 Vector:
synchronized (vec) {
Object[] a = new Object[vec.size()];
vec.copyInto(a);
}

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

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

坦率地说,这非常蹩脚。然而,考虑到当时语言的限制,我承认我没有更好的选择。

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

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

然而,许多人发现零长度数组违反直觉。在 JDK 11 中,有一个新的 API 允许使用数组构造函数引用:
MyType[] a = coll.toArray(MyType[]::new);

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

关于java - 集合 emptyList/singleton/singletonList/List/Set toArray,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51902362/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com