Как создать бесконечный поток <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 );