如何使用JDK 11为Collection.toArray()提供一个生成器函数?

时间:2018-08-28 06:15:43

标签: java eclipse-photon java-11

我已经升级了Eclipse Photon 4.8(http://download.eclipse.org/eclipse/downloads/drops4/S-4.9M2-201808012000/)以支持JDK 11(https://marketplace.eclipse.org/content/java-11-support-eclipse-photon-49)。似乎工作正常(版本:4.9 内部版本号:I20180801-2000)。

在JDK 11中,Java.util.Collection中有一个方法toArray()的新覆盖:

default <T> T[] toArray(IntFunction<T[]> generator) {
    return toArray(generator.apply(0));
}

这是默认方法,但不会被覆盖。它所做的只是将提供的生成器函数返回的值(使用硬编码参数0)传递到toArray()的另一个替代项,该替代项随后将Collection的内容作为数组返回。

如该方法的Javadoc中所述,可以这样调用:

String[] y = x.toArray(String[]::new);

这很好,然后返回一个与Collection<String>相对应的适当长度的String数组。

Javadoc还声明“ 默认实现以零调用生成器函数,然后将结果数组传递给toArray(T [])”。

如果我提供了自己的生成器函数,则确实会调用它(如println()控制台输出所示),但是其apply()方法的返回值似乎被忽略了。就像我调用toArray(String[]::new)一样,无论生成器函数返回的数组内容如何。

这是MCVE:

package pkg;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.IntFunction;

public class App {
    public static void main(String[] args) {

        IntFunction<String[]> intFunc = (int sz) -> {
            System.out.println("intFunc: sz: " + sz);
            if (sz == 0) {
                sz = 3;
            }
            String[] array = new String[sz];
            for (int i = 0; i < sz; i++) {
                array[i] = Character.toString('A' + i);

            }
            System.out.println("intFunc: array to be returned: " + Arrays.toString(array));
            return array;
        };

        Collection<String> coll = List.of("This", "is", "a", "list", "of", "strings");

        // Correctly returns the collection as an array, as described in JDK11 Javadoc.
        String[] array1 = coll.toArray(String[]::new);
        System.out.println("array1: " + Arrays.toString(array1) + '\n');

        // Use generator function to return a different collection as an array - doesn't work.      
        String[] array2 = coll.toArray(intFunc);
        System.out.println("array2: " + Arrays.toString(array2) + '\n');

        // Use generator function to return a different collection as an array - doesn't work.
        String[] array3 = coll.toArray(intFunc.apply(coll.size()-2));
        System.out.println("array3: " + Arrays.toString(array3));
    }
}

这是通过运行MCVE产生的控制台输出:

  

array1:[这是一个字符串列表]

     

intFunc:sz:0

     

intFunc:要返回的数组:[A,B,C]

     

array2:[这是一个字符串列表]

     

intFunc:sz:4

     

intFunc:要返回的数组:[A,B,C,D]

     

array3:[这是一个字符串列表]

输出显示我的生成器函数做什么无关紧要-不使用它返回的数组。

我的问题是我该如何使用toArray()的新实现来使用生成器函数返回的数组,或者我正在尝试某些不可能的事情?


根据评论和Nicolai的回答进行更新:

我的示例代码的问题不在于生成器,而在于我的测试用例。它们碰巧使生成器返回的元素数组少于集合的数组,因此分配了一个新数组,以精确存储集合中元素的数量。

一个返回比集合大的数组的测试用例可以正常工作。例如此代码:

    String[] array4 = coll.toArray(intFunc.apply(coll.size() + 3));
    System.out.println("array4: " + Arrays.toString(array4));

提供以下控制台输出:

  

intFunc:sz:9

     

intFunc:要返回的数组:[A,B,C,D,E,F,G,H,I]

     

array4:[这是一个字符串列表,为null,H,I]

SO问题Collections emptyList/singleton/singletonList/List/Set toArray解释了为什么返回的数组中存在空值。

2 个答案:

答案 0 :(得分:3)

正如您所指出的,toArray(IntFunction<T[]>)a default method,它只是转发到toArray(T[])(在使用给定函数创建数组之后)。如果您仔细研究一下该方法,就会发现问题的答案-从the JDK 10 Javadoc(重点是我的):

  

返回包含此集合中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。 如果集合适合指定的数组,则将其返回。否则,将使用指定数组的运行时类型和该集合的大小分配新的数组。

要使用要创建的数组,该数组必须足够长以容纳集合的元素,例如:

public static void main(String[] args) {
    var createdArray = new AtomicReference<String[]>();
    var usedArray = List.of("A", "B", "C").toArray(__ -> {
        createdArray.set(new String[5]);
        return createdArray.get();
    });

    var message = String.format(
            "%s (length: %d; identical with created array: %s)",
            Arrays.toString(usedArray), usedArray.length, usedArray == createdArray.get());
    System.out.println(message);
}

答案 1 :(得分:2)

生成器函数返回的数组不会被忽略。使用返回数组的组件类型。假设您有一个字符串集合,例如您的示例:

jshell> Collection<String> coll = List.of("This", "is", "a", "list", "of", "strings")
coll ==> [This, is, a, list, of, strings]

您的生成器函数是这样的:

jshell> IntFunction<CharSequence[]> g = x -> {
   ...>   var a = new CharSequence[x];
   ...>   System.out.println(a);
   ...>   return a;
   ...> }
g ==> $Lambda$28/0x00000008000d8040@17d677df

(请注意,这会打印出数组的默认表示形式,其中包括其标识哈希码,而不是数组的内容。数组的内容符合预期。)

然后,您可以将集合中的字符串复制到CharSequence数组中:

jshell> System.out.println(coll.toArray(g))
[Ljava.lang.CharSequence;@7d70d1b1
[Ljava.lang.CharSequence;@2a742aa2

现在假设源是一个空集合:

jshell> System.out.println(List.of().toArray(g))
[Ljava.lang.CharSequence;@3dfc5fb8
[Ljava.lang.CharSequence;@3dfc5fb8

您可以看到生成器返回的零大小数组具有足以包含零个元素的大小,因此只需返回即可。