【问题标题】:converting Java stream to array as per Collection.toArray(T[] array) contract根据 Collection.toArray(T[] array) 合同将 Java 流转换为数组
【发布时间】:2019-04-16 13:33:20
【问题描述】:

请大家在看到“流”和“数组”之前先阅读这个问题,然后假设这个问题是重复的。 我知道如何将流转换为数组。这个问题很具体:如何遵守Collection.toArray(T[] array)的合约。所有这些人都说这是重复的:如果现有数组足够大,其他答案是否会重新使用它?如果现有数组太大,他们会用null 标记元素吗? (没有也没有。)

我知道Java Stream<> 接口提供了几种将流转换为数组的方法。但是Collection.toArray(T[] array) 方法有点不同。它有一些(当时)巧妙的要求,包括:

  • 如果你传递的数组足够大,必须使用你传递的数组;否则必须创建一个新的。
  • 如果数组比需要的大,null 必须添加到最后一个元素之后。

因此,如果我的Collection<T> 实现从一些Stream<FooBar> 检索其值(使用转换为T 的转换器策略,我如何从我的流转换为Collection.toArray(T[] array) 所需的数组?

没有多想,看来我必须这样做:

@Override
public <T> T[] toArray(T[] array) {
  try (final Stream<FooBar> stream = getStream()) {
    T[] result = stream.map(converter::toT).toArray(length ->
        (T[])Array.newInstance(array.getClass(), length));
    if(result.length <= array.length) {
      System.arraycopy(result, 0, array, 0, result.length);
      if(result.length < array.length) {
        array[result.length] = null;
      }
      result = array;
    }
  }
  return result;
}

但是有没有更简洁的方法来做到这一点?如果可能的话,有什么方法可以将流直接传输到给定的数组中? Stream&lt;&gt; API 是否已经提供了这样的功能:按照Collection&lt;&gt;.toArray(T[] array) API 的预期创建数组?

【问题讨论】:

  • 在这里你可以找到它:stackoverflow.com/questions/23079003/…
  • @nullpointer,这不是重复的。请解释这是如何重复的。我知道如何将流转换为数组。我在问如何使用Collections.toArray(T[] array) 提供的附加限制最有效地创建流到数组。您是否阅读了问题的那部分内容?
  • 最简单的方法是让您的集合扩展 AbstractCollection,并让默认实现完成其工作。
  • 如果性能不是主要问题,您可以按照 JDK:getStream().collect(Collectors.toList()).toArray(array)
  • 好的,请问您为什么需要它? Holger 在他的回答中已经说过 java-11 中发生了什么,您甚至可以从方法创建者himself 那里阅读答案...那您为什么需要呢?

标签: java arrays collections java-stream


【解决方案1】:

非常推荐阅读文章Arrays of Wisdom of the Ancients

简而言之,与直觉相反,将预先确定大小的数组传递给Collections.toArray(T[]) 方法的效率低于传递零大小的数组,后者仅用于确定结果类型,但让集合分配结果数组。

这就是为什么 Java 11 的新 default 方法 &lt;T&gt; T[] toArray​(IntFunction&lt;T[]&gt; generator) 不使用该函数分配集合大小的数组,而是分配一个大小为零的数组以传递给 &lt;T&gt; T[] toArray​(T[] a)

因此,值得重新考虑您是否真的需要这样的方法合同,或者您真正想要优化哪些实际用例(因为您不能一次提供所有服务)。

例如考虑到传递一个大小为零的数组是最有效的选择,您可以针对这种情况进行优化

@Override
public <T> T[] toArray(T[] array) {
    T[] template = array.length == 0? array: Arrays.copyOf(array, 0);
    try(Stream<FooBar> stream = getStream()) {
        T[] result = stream.map(converter::toT)
            .toArray(length -> Arrays.copyOf(template, length));
        if(result.length > array.length) return result;
        System.arraycopy(result, 0, array, 0, result.length);
        if(result.length < array.length) array[result.length] = null;
        return array;
    }
}

请注意,当您因为要实现 Collection 而必须实现该方法时,JDK 中有很多有用的抽象基类已经提供了实现。

您甚至可以在不实现集合时使用这样的实现,例如

public <T> T[] toArray(T[] array) {
    try(final Stream<FooBar> stream = getStream()) {
        return new AbstractCollection<XYZ>() {
            public Iterator<XYZ> iterator() {
                return stream.map(converter::toT).iterator();
            }
            public int size() { return 0; } // don't know beforehand
        }.toArray(array);
    }
}

您必须将XYZ 替换为converter.toT(FooBar) 方法的返回类型。

这导致了更大的问题,converter::toT 应该如何在不知道 T 是什么的情况下转换为正确的类型。

【讨论】:

    猜你喜欢
    • 2021-11-26
    • 2013-01-23
    • 2014-08-16
    • 2014-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多