【问题标题】:Collections emptyList/singleton/singletonList/List/Set toArray集合 emptyList/singleton/singletonList/List/Set toArray
【发布时间】:2018-08-17 20:18:41
【问题描述】:

假设我有这个代码:

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

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

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

如果此集合适合指定的数组并有剩余空间(即,数组的元素比此集合多),则数组中紧跟集合末尾的元素设置为 null。

好的,好的,这至少有文档记录。但它后来说:

只有当调用者知道这个集合不包含任何空元素时,这对确定这个集合的长度很有用。

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

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

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]

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

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

【问题讨论】:

  • 我不明白这个问题。他们都将以下元素设置为 null。哪里不一致?
  • @shmosel 没有矛盾,真正的问题是为什么会发生这种情况,是否有(并且可能会有)代码实际利用了这一点?
  • 我猜假设您可能正在覆盖目标数组中的数据,在这种情况下,空值可以告诉您上限。看起来不是很有用,tbh。
  • @shmosel 这个答案和 cmets 怎么样? stackoverflow.com/a/51902457/1059372 这更有意义吗?我认为它回答了我的问题...
  • 我仍然不完全确定您的问题是什么。如果您问如果前面有空值,将下一个元素设置为空值有什么用处,那么,它不是。这正是文档在您的第二个引用中所说的内容。

标签: java arraylist collections


【解决方案1】:

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

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

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

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

处理案例 (1) 是一个相当微妙的 API 问题。调用者要重用一个数组,所以显然需要传入。不像无参数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() 那样抛出异常,而是分配了一个大小合适的 new 数组。这牺牲了阵列重用案例以获得更可靠的操作。现在的问题是调用者无法判断它的数组是被重用还是分配了一个新数组。因此,toArray(T[]) 的返回值需要返回一个数组:参数数组,如果它足够大,或者是新分配的数组。

但是现在我们遇到了另一个问题。我们不再有办法告诉调用者从集合中复制到数组中的元素数量。如果目标数组是新分配的,或者数组恰好是正确的大小,那么数组的长度就是复制的元素数。如果目标数组大于复制的元素数,则该方法尝试通过将null 写入数组位置超出最后复制的元素,向调用者传达复制的元素数从收藏。如果已知源集合没有空值,则调用者可以确定复制的元素数量。调用后,调用者可以在数组中搜索第一个空值。如果有,它的位置决定了复制元素的数量。如果数组中不存在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);

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

【讨论】:

  • 我们不再有办法告诉调用者从集合复制到数组中的元素数量。 为什么不检查源集合的大小?
  • 在 JDK 11 中,有一个新的 API 允许人们改用数组构造函数引用。 太好了。出于兼容性原因,Java 8 中没有拒绝吗?
  • @shmosel 为什么不检查源集合的大小? 调用者可以在调用 toArray() 之前或之后调用 size()。但是,如果另一个线程修改了源集合,则大小值可能不等于 toArray() 复制的实际元素数。只有 toArray() 知道复制的元素的确切数量。当然,如果你是单线程的,这不是问题,但一般情况是源是并发集合。
  • @shmosel 这在 Java 8 中不是因为兼容性原因被拒绝了吗?我不这么认为。因为我们没时间了,它掉到了 8 分。在 9 或 10 个开发阶段,人们就这是否是最自然的 API 以及性能进行了讨论——明显的覆盖实际上变得更慢了!我们在 11 开发过程中解决了这些问题。
【解决方案2】:

它只会清除原始列表中最后一个元素之后的索引中的元素,因此在第一个示例中,列表为空,因此它将索引零处的元素(第一个"1")。

在你的最后一个例子中,最后一个元素恰好是原始列表中最后一个元素之后的那个。知道最后一种情况并不能真正帮助确定列表的大小,因为它确实允许空值。

但如果列表不允许 null(例如 immutable lists introduced in Java 9),那么这很有用,因为如果您循环返回的数组,您不会想要处理额外的元素 ,在这种情况下,您可以在第一个 null 元素处停止迭代器。

【讨论】:

  • 你说得有道理,所以你是说如果那个特定的List/Set 不允许nulls,你会打电话给toArrayarray 的大小比@987654327 大@,当你遇到第一个 null 时循环遍历 array - 你会确定你已经完成了。这正是 java-9 中禁止空值的不可变集合的情况。如果你要添加这部分,我会接受它
  • 但这对于ArrayList 来说非常令人困惑(它确实接受空值),当你遇到第一个空值时,你不知道你是否完成了。好吧,至少,在文档中很清楚。
  • @Eugene 是的,你是对的,因此它说“如果调用者知道这个集合不包含任何空元素。”
  • @Eugene 没什么大不了的,Stuart 的回答确实涵盖得更好,并且来自原始开发人员。
【解决方案3】:

来自ArrayList的JDK 9源代码:

@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

Arrays.ArrayList 中,List 实现由Arrays.asList 返回:

@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
    int size = size();
    if (a.length < size)
        return Arrays.copyOf(this.a, size,
                             (Class<? extends T[]>) a.getClass());
    System.arraycopy(this.a, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

如果要转换为数组的列表大小为size,那么它们都将a[size]设置为null

对于一个空列表,size0 所以a[0] 被设置为null,并且其他元素没有被触及。

对于单例列表,size1,所以 a[1] 设置为 null,其他元素不会被触及。

如果列表的大小比数组的长度小一,a[size]指的是数组的最后一个元素,所以设置为null。在您的示例中,您在第二个位置(索引 1)中有一个 null,因此将其设置为 null 作为一个元素。如果有人正在寻找null 来计算元素,他们会停在这里而不是另一个null,这是将列表内容之外的下一个元素设置为null 产生的null。这些nulls 无法区分。

【讨论】:

  • 你是对的,只是另一个答案(措辞明智)更接近我的理解。谢谢!
【解决方案4】:

(例如)ArrayList的toArray(T[] a)的代码很清楚:

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        // Make a new array of a's runtime type, but my contents:
        return (T[]) Arrays.copyOf(elementData, size, a.getClass());
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

如果输入数组的大小大于这个列表的大小(这意味着我们可以将列表的所有内容复制到这个数组中,因为它的长度足够大),那么在复制所有列表内容之后数组中的下一个元素引用(实际上是index 等于列表的大小)将被设置为指向 null。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-19
    • 2014-05-25
    • 2011-12-08
    • 2011-09-26
    • 2012-04-14
    • 2019-08-26
    • 1970-01-01
    相关资源
    最近更新 更多