Подтвердить что ты не робот

Как работают ленивые потоки в Java 8?

Я читаю Java 8, в частности, "Streams API". Я хотел знать, как потоки могут лениться?

Я считаю, что потоки просто добавлены в библиотеку, и никаких изменений в языке для поддержки лени не происходит. Кроме того, я буду потрясен, если кто-нибудь скажет мне, что это достигнуто через отражение.

4b9b3361

Ответ 1

Зачем вам нужно размышлять, чтобы получить лень? Например, рассмотрим этот класс:

class LazySeq<T> {

    private final List<T> list;
    private Predicate<? super T> predicate;

    public LazySeq(List<T> input) {
        this.list = new ArrayList<>(input);
    }

    //Here you just store the predicate, but you don't perform a filtering
    //You could also return a new LazySeq with a new state
    public LazySeq<T> filter(Predicate<? super T> predicate) {
        this.predicate = predicate;
        return this;
    }

    public void forEach(Consumer<? super T> consumer){
        if(predicate == null) {
            list.forEach(consumer);
        } else {
            for(T elem : list) {
                if(predicate.test(elem)) {
                    consumer.accept(elem);
                }
            }
        }
    }
}

Когда вы вызываете filter в ленивом seq, фильтрация не происходит немедленно, например, например:

LazySeq<Integer> lazySeq = new LazySeq<>(Arrays.asList(1, 2, 3, 4));
lazySeq = lazySeq.filter(i -> i%2 == 0);

Если вы видите содержимое последовательности после вызова фильтра, вы увидите, что оно всегда 1, 2, 3, 4. Однако при вызове операции терминала, например forEach, фильтрация будет выполняться перед использованием потребителя. Так, например:

lazySeq.filter(i -> i%2 == 0).forEach(System.out::println);

будет печатать 2 и 4.

Это тот же самый принцип с Stream s. Из источника вы выполняете операции, которые обладают свойствами. Эти операции либо являются промежуточными, что возвращает ленивый поток (например, filter или map), либо терминал (например, forEach). Некоторые из этих терминальных операций имеют короткое замыкание (например, findFirst), поэтому вы можете не пересекать весь конвейер (вы можете думать о раннем возврате в цикле for, который возвращает индекс значения в массиве, например).

При вызове операции с терминалом эта цепочка операций начинает выполняться так, что в конце вы получаете ожидаемый результат.

Лень может быть достигнута путем сохранения нового состояния в конвейере при применении промежуточного op, а когда вы вызываете терминал op, вы переходите ко всем состояниям один за другим по данным.

Stream API на самом деле не реализован таким образом (это немного сложнее), но на самом деле принцип здесь.

Ответ 2

Нет отражений или прокси. Отражение и прокси-серверы имеют производительность, которую следует избегать, если нет альтернативы, а производительность - номер один в Java.

То, что делает лень, - это функциональный стиль ведения дел. В принципе, stream начинается с источника (ex: List), количества промежуточных операций (например: фильтры, карты..) и операции терминала (например: счет, сумма и т.д.). Промежуточные шаги выполняются лениво, потому что вы передаете функции (lambdas), которые привязаны в конвейере для выполнения на терминальном этапе.

ex: filter(Predicate<? super T>)

filter в этом примере ожидает функция, которая сообщает нам, соответствует ли объект в потоке некоторым критериям или нет.

Множество функций, которые исходят от Java 7, использовались для повышения эффективности. Пример: вызов динамических для выполнения lambdas, а не прокси-серверов или анонимных внутренних классов и пулов ForkJoin для параллельного выполнения.

Если вас интересует внутренняя среда Java 8, вам нужно посмотреть этот разговор, предоставленный экспертом в области Брайан Гетц, на Youtube.

Ответ 3

потоковая передача не является контейнером данных, а контейнером логики. вам просто нужно передать экземпляр интерфейса, чтобы запомнить логику.

рассмотрим следующий код:

class FilterIterable<T> implements Iterable<T>
{
    private Iterable<? extends T> iter;
    private Predicate<? super T> pred;

    public FilterIterable(Iterable<? extends T> iter, Predicate<? super T> pred) {
        this.iter = iter;
        this.pred = pred;
    }

    public Iterator<T> iterator() {
        return FilterIterator<T>();
    }

    class FilterIterator<T> implements Iterator<T>
    {
        private Iterator<? extends T> iterator = iter.iterator();
        private T next = null;

        FilterIterator() {
            getNext();
        }

        private void getNext() {
            next = null;
            while (iterator.hasNext()) {
                T temp = iterator.next();
                if (pred.test(temp)) {
                    next = temp;
                    break;
                }
            }
        }

        public boolean hasNext() { return next != null; }

        public T next() {
            T temp = next;
            getNext();
            return temp;
        }       
    }
}

логика завершается внутри pred, но только вызывается, когда объект получает итерацию. и на самом деле этот класс не хранит никаких данных, он только сохраняет итерабельность, которая может содержать данные или даже просто другой логический держатель.

и элементы возвращаются по требованию. такая парадигма делает так называемый поток api lazy.