Как создать бесконечный поток <E> из Iterator <E>?
Глядя на следующий класс, я сделал:
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<E>
?
Если бы я использовал Stream<Integer> infiniteStream = toList(FibonacciSupplier.infinite()).stream()
, я бы, возможно, удивительно, никогда не получал бесконечный поток.
Вместо этого код будет цикл навсегда при создании list
в базовом методе.
Это до сих пор чисто теоретическое, но я могу определенно понять его необходимость, если я хочу сначала пропустить первые числа из бесконечного потока, а затем ограничить его последними числами 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();
Код никогда не вернет результат, как это сделать?
Ответы
Ответ 1
Ваша ошибка состоит в том, что вам нужно создать Iterator
или Collection
для создания Stream
. Для создания бесконечного потока достаточно одного метода, обеспечивающего одно значение за другим. Итак, для вашего класса FibonacciSupplier
простейшее использование:
IntStream s=IntStream.generate(FibonacciSupplier.infinite()::next);
или, если вы предпочитаете значения в коробке:
Stream<Integer> s=Stream.generate(FibonacciSupplier.infinite()::next);
Обратите внимание, что в этом случае метод не должен называться next
и не выполнять интерфейс Iterator
. Но это не имеет значения, если это так же, как с вашим классом. Кроме того, поскольку мы просто сказали потоку использовать метод next
как Supplier
, метод hasNext
никогда не будет вызываться. Его просто бесконечно.
Создание конечного потока с использованием Iterator
немного сложнее:
Stream<Integer> s=StreamSupport.stream(
Spliterators.spliteratorUnknownSize(
FibonacciSupplier.finite(intPredicate), Spliterator.ORDERED),
false);
В этом случае, если вам нужен конечный IntStream
с unboxed int
значениями, ваш FibonacciSupplier
должен реализовать PrimitiveIterator.OfInt
.
Ответ 2
В Java 8 нет открытых, конкретных классов, реализующих интерфейс Stream. Однако существуют некоторые статические методы factory. Одним из наиболее важных является StreamSupport.stream. В частности, он используется в методе по умолчанию Collection.stream - унаследовано большинством классов коллекций:
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
По умолчанию этот метод создает Spliterator путем вызова spliterator()
и передает созданный объект методу factory, Spliterator - это новый интерфейс, реализованный с Java 8 для поддержки параллельных потоков. Он похож на Iterator, но в отличие от последнего, Spliterator можно разделить на части, которые могут обрабатываться независимо. Подробнее см. Spliterator.trySplit.
Метод по умолчанию Iterable.spliterator также был добавлен в Java 8, так что каждый класс Iterable автоматически поддерживает Spliterators. Реализация выглядит следующим образом:
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
Метод создает Spliterator из произвольного итератора. Если вы объедините эти два шага, вы можете создать поток из любого итератора:
<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;
}
}
Ответ 3
Чтобы добавить еще один ответ, возможно, AbstractSpliterator - лучший выбор, особенно с учетом кода примера. Генерация негибкая, поскольку нет [хорошего] способа дать условие останова, кроме как с использованием предела. Ограничение допускает только количество элементов, а не предикат, поэтому мы должны знать, сколько элементов мы хотим сгенерировать, что может быть невозможно, и что, если генератор является черным ящиком, переданным нам?
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);
}
}
Для более подробной информации я рассмотрел генераторы в Java 8 в своем блоге http://thecannycoder.wordpress.com/
Ответ 4
Вы можете использовать примитивы поддержки низкого уровня потока и библиотеку 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 );