【问题标题】:How to create an infinite Stream<E> out of an Iterator<E>?如何从 Iterator<E> 创建无限 Stream<E>?
【发布时间】:2014-03-24 06:30:34
【问题描述】:

看看我制作的以下课程:

public class FibonacciSupplier implements Iterator<Integer> {
    private final IntPredicate hasNextPredicate;

    private int beforePrevious = 0;
    private int previous = 1;

    private FibonacciSupplier(final IntPredicate hasNextPredicate) {
        this.hasNextPredicate = hasNextPredicate;
    }

    @Override
    public boolean hasNext() {
        return hasNextPredicate.test(previous);
    }

    @Override
    public Integer next() {
        int result = beforePrevious + previous;
        beforePrevious = previous;
        previous = result;
        return result;
    }

    public static FibonacciSupplier infinite() {
        return new FibonacciSupplier(i -> true);
    }

    public static FibonacciSupplier finite(final IntPredicate predicate) {
        return new FibonacciSupplier(predicate);
    }
} 

以及它的用法:

public class Problem2 extends Problem<Integer> {
    @Override
    public void run() {
        result = toList(FibonacciSupplier.finite(i -> (i <= 4_000_000)))
                .stream()
                .filter(i -> (i % 2 == 0))
                .mapToInt(i -> i)
                .sum();
    }

    @Override
    public String getName() {
        return "Problem 2";
    }

    private static <E> List<E> toList(final Iterator<E> iterator) {
        List<E> list = new ArrayList<>();
        while (iterator.hasNext()) {
            list.add(iterator.next());
        }
        return list;
    }
}

我怎样才能创建一个无限 Stream&lt;E&gt;

如果我使用Stream&lt;Integer&gt; infiniteStream = toList(FibonacciSupplier.infinite()).stream(),我可能会感到惊讶,永远不会获得无限流。
相反,代码将在底层方法中创建 list 时永远循环。

到目前为止,这纯粹是理论上的,但如果我想先跳过无限流中的前 x 个数字,然后将其限制为最后 y 个数字,我绝对可以理解它的必要性,例如:

int x = MAGIC_NUMBER_X;
int y = MAGIC_NUMBER_y;
int sum = toList(FibonacciSupplier.infinite())
    .stream()
    .skip(x)
    .limit(y)
    .mapToInt(i -> i)
    .sum();

代码永远不会返回结果,应该怎么做?

【问题讨论】:

  • 您问的是如何使用 Java 8 的新功能编写生成器?
  • @chrylis 这不一定是我要问的,但它很可能是一个非常好的和正确的答案。
  • 我试图澄清您所说的“无限流”是否与生成器模式相同。
  • @chrylis 是的,我就是这个意思。

标签: java iterator java-8 java-stream


【解决方案1】:

您可以使用低级流支持原语和Spliterators 库从Iterator 创建流。

StreamSupport.stream() 的最后一个参数表示流不是并行的。一定要让它这样,因为你的斐波那契迭代器依赖于以前的迭代。

return StreamSupport.stream( Spliterators.spliteratorUnknownSize( new Iterator<Node>()
{
    @Override
    public boolean hasNext()
    {
        // to implement
        return ...;
    }

    @Override
    public ContentVersion next()
    {
        // to implement
        return ...;
    }
}, Spliterator.ORDERED ), false );

【讨论】:

    【解决方案2】:

    要添加另一个答案,也许 AbstractSpliterator 是一个更好的选择,特别是考虑到示例代码。生成是不灵活的,因为除了使用限制之外,没有[好的]方法可以给出停止条件。 Limit 只接受一些项目而不是一个谓词,所以我们必须知道我们想要生成多少项目——这可能是不可能的,如果生成器是一个传递给我们的黑盒子怎么办?

    AbstractSpliterator 是在编写整个拆分器和使用 Iterator/Iterable 之间的中途之选。 AbstractSpliterator 仅缺少 tryAdvance 方法,我们首先检查谓词是否已完成,如果未完成,则将生成的值传递给操作。下面是一个使用 AbstractIntSpliterator 的斐波那契数列示例:

    public class Fibonacci {
        private static class FibonacciGenerator extends Spliterators.AbstractIntSpliterator
        {
            private IntPredicate hasNextPredicate;
            private int beforePrevious = 0;
            private int previous = 0;
    
            protected FibonacciGenerator(IntPredicate hasNextPredicate)
            {
                super(Long.MAX_VALUE, 0);
                this.hasNextPredicate = hasNextPredicate;
            }
    
            @Override
            public boolean tryAdvance(IntConsumer action)
            {
                if (action == null)
                {
                    throw new NullPointerException();
                }
    
                int next = Math.max(1, beforePrevious + previous);
                beforePrevious = previous;
                previous = next;
    
                if (!hasNextPredicate.test(next))
                {
                    return false;
                }
    
                action.accept(next);
    
                return true;
            }
    
            @Override
            public boolean tryAdvance(Consumer<? super Integer> action)
            {
                if (action == null)
                {
                    throw new NullPointerException();
                }
    
                int next = Math.max(1, beforePrevious + previous);
                beforePrevious = previous;
                previous = next;
    
                if (!hasNextPredicate.test(next))
                {
                    return false;
                }
    
                action.accept(next);
    
                return true;
            }
        }
    
        public static void main(String args[])
        {
            Stream<Integer> infiniteStream = StreamSupport.stream(
                    new FibonacciGenerator(i -> true), false);
    
            Stream<Integer> finiteStream = StreamSupport.stream(
                    new FibonacciGenerator(i -> i < 100), false);
    
            // Print with a side-effect for the demo
            infiniteStream.limit(10).forEach(System.out::println);
            finiteStream.forEach(System.out::println);
        }
    } 
    

    有关更多详细信息,我已经在我的博客 http://thecannycoder.wordpress.com/ 中介绍了 Java 8 中的生成器

    【讨论】:

      【解决方案3】:

      在 Java 8 中,没有实现接口 Stream公共具体类。但是,有一些静态工厂方法。其中最重要的是StreamSupport.stream。特别是在默认方法中使用它Collection.stream——被大多数集合类继承:

      default Stream<E> stream() {
          return StreamSupport.stream(spliterator(), false);
      }
      

      该方法的默认实现通过调用spliterator()创建一个Spliterator,并将创建的对象传递给工厂方法。 Spliterator 是 Java 8 引入的一个新接口,用于支持并行流。它类似于Iterator,但与后者不同的是,一个Spliterator可以被分成多个部分,可以独立处理。详情请见Spliterator.trySplit

      默认方法Iterable.spliterator 也被添加到 Java 8 中,因此每个 Iterable 类都会自动支持 Spliterators。实现如下:

      default Spliterator<T> spliterator() {
          return Spliterators.spliteratorUnknownSize(iterator(), 0);
      }
      

      该方法从任意Iterator 创建Spliterator。如果将这两个步骤结合起来,您可以从任意 Iterator 创建一个 Stream

      <T> Stream<T> stream(Iterator<T> iterator) {
          Spliterator<T> spliterator
              = Spliterators.spliteratorUnknownSize(iterator, 0);
          return StreamSupport.stream(spliterator, false);
      }
      

      为了了解Spliterators,这里有一个非常简单的例子,不使用集合。下面的类实现了 Spliterator 来迭代整数的半开区间:

      public final class IntRange implements Spliterator.OfInt {
          private int first, last;
          public IntRange(int first, int last) {
              this.first = first;
              this.last = last;
          }
          public boolean tryAdvance(IntConsumer action) {
              if (first < last) {
                  action.accept(first++);
                  return true;
              } else {
                  return false;
              }
          }
          public OfInt trySplit() {
              int size = last - first;
              if (size >= 10) {
                  int temp = first;
                  first += size / 2;
                  return new IntRange(temp, first);
              } else {
                  return null;
              }
          }
          public long estimateSize() {
              return Math.max(last - first, 0);
          }
          public int characteristics() {
              return ORDERED | DISTINCT | SIZED | NONNULL
                  | IMMUTABLE | CONCURRENT | SUBSIZED;
          }
      }
      

      【讨论】:

      • 你能解释一下它到底是做什么的吗?我几乎了解 Java 8 的所有概念,但 Spliterators 仍然难以掌握。
      • @skiwi 在后台,可以拆分流以在多核系统上进行并行处理。 Spliterators 是一种迭代器,也可以为此目的进行拆分。它们与经典迭代器不兼容,因此为了向后兼容,他们只是在旧版本旁边添加了新版本,并给了它们一个奇怪的名字。
      【解决方案4】:

      您的错误是认为您需要IteratorCollection 来创建Stream。对于创建无限流,一个方法提供一个接一个的值就足够了。所以对于你的班级FibonacciSupplier,最简单的用法是:

      IntStream s=IntStream.generate(FibonacciSupplier.infinite()::next);
      

      或者,如果您更喜欢装箱的值:

      Stream<Integer> s=Stream.generate(FibonacciSupplier.infinite()::next);
      

      请注意,在这种情况下,方法不必命名为next,也不必满足Iterator 接口。但它是否与您的班级一样并不重要。此外,正如我们刚刚告诉流使用next 方法作为SupplierhasNext 方法将永远不会被调用。它只是无限的。

      使用 Iterator 创建有限流有点复杂:

      Stream<Integer> s=StreamSupport.stream(
        Spliterators.spliteratorUnknownSize(
          FibonacciSupplier.finite(intPredicate), Spliterator.ORDERED),
        false);
      

      在这种情况下,如果您想要一个具有未装箱 int 值的有限 IntStream,您的 FibonacciSupplier 应该实现 PrimitiveIterator.OfInt

      【讨论】:

      • FibonacciSupplier 如何提供任何东西,而不是在任何地方获取索引作为输入参数?
      • @Gangnus:看看这个问题,看看FibonacciSupplier 是如何工作的。它是迭代的,next 方法必须根据需要被调用多次,而流会做到这一点。
      • 但是为了计算下一个,我必须将之前的两个数字相加。我在哪里以及如何在供应商处获得它们?您忘记写答案中更有趣或更难的部分。
      • @Gangnus:再次,问题中已经给出了工作供应商。没有什么可讨论的。问题是,如何从中创建一个流,这个答案就是回答这个问题,仅此而已。如果您有其他问题,可以随时ask a new question
      • 非常抱歉,我只看了答案,还没有看到供应商。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-02-28
      • 1970-01-01
      • 2011-10-13
      • 1970-01-01
      • 2017-10-08
      • 2017-06-02
      相关资源
      最近更新 更多