代码之家  ›  专栏  ›  技术社区  ›  Eugene

集合emptyList/singleton/singletonList/List/Set to数组

  •  12
  • Eugene  · 技术社区  · 7 年前

    假设我有这个代码:

    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]
    

    当然,我也会查阅明确指出这一点的文档:

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

    只有当调用方知道此集合不包含任何空元素时,这在确定此集合的长度时才有用。

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

    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]
    

    有人能在这里发光吗?

    4 回复  |  直到 7 年前
        1
  •  12
  •   Stuart Marks    7 年前

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

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

    1) 打电话的人想 现有阵列(如有可能);以及

    2) 调用方希望指定返回数组的组件类型。

    处理case(1)是一个相当微妙的API问题。调用方希望重用数组,因此显然需要传入它。不像没有参数 toArray()

    int toArray(T[] a)
    

    调用方传入一个可重用的数组,返回值是复制到该数组中的元素数。不需要返回数组,因为调用方已经有了对它的引用。但是如果数组太小呢?好吧,也许可以抛出一个例外。事实上,这就是 Vector.copyInto 做。

    void copyInto​(Object[] anArray)
    

    IndexOutOfBoundsException 如果目标数组太短。因为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]);
    

    toArray(new MyType[coll.size()]) 实际上更慢。这是因为需要将数组初始化为空,然后用集合的内容填充它。参见Alexey Shipilev关于这个话题的文章, Arrays of Wisdom of the Ancients .)

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

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

        2
  •  3
  •   M A    7 年前

    刚好在…之后 原始列表中的最后一个元素,因此在第一个示例中,列表是空的,因此它将索引为0的元素(第一个元素是 "1"

    在上一个示例中,最后一个元素恰好位于原始列表中最后一个元素的后面。知道上一个场景并不能真正帮助确定列表的大小,因为 允许的空值。

    但如果列表不允许空值(例如。 immutable lists introduced in Java 9 ),则这很有用,因为在循环返回的数组时, 您不想处理额外的元素

        3
  •  2
  •   rgettman    7 年前

    从JDK 9源代码 ArrayList :

    @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 .

    大小 0 a[0] 设置为 无效的

    有了单子列表, 大小 1 所以 a[1] 无效的 ,其他元素则不会被触碰。

    如果列表的大小比数组的长度小一个, a[尺寸] 引用数组的最后一个元素,因此将其设置为 . 在你的例子中,你有一个 无效的 无效的 作为一个元素。如果有人在找 要计算元素,它们会停在这里而不是另一个 ,这是 将列表内容之外的下一个元素设置为 . 这些 无效的 我们分不开。

        4
  •  0
  •   nyarian    7 年前

    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;
    }
    

    如果输入数组的大小大于此列表的大小(这意味着我们可以将列表的所有内容复制到此数组中,因为它的长度足够大),则复制所有列表内容(实际上索引等于列表的大小)后,数组中的下一个元素引用将设置为空。