Эквивалент Scala dropWhile

Я пытаюсь найти способ пропустить некоторые элементы в начале потока в зависимости от предиката.

Что-то вроде этого:

dropWhile( n -> n < 3, Stream.of( 0, 1, 2, 3, 0, 1, 2, 3, 4 ) )
.forEach( System.out::println );
3   
0
1
2
3
4

Это эквивалент Scala dropWhile.

Ответ 1

Этот тип операций не предназначен для использования для Stream, поскольку он включает зависимость между элементами. Поэтому решение может выглядеть не изящно, поскольку вам нужно ввести переменную состояния для вашего предиката:

class MutableBoolean { boolean b; }
MutableBoolean inTail = new MutableBoolean();

IntStream.of(0, 1, 2, 3, 0, 1, 2, 3, 4)
         .filter(i -> inTail.b || i >= 3 && (inTail.b = true))
         .forEach(System.out::println);

Обратите внимание, что условие должно быть отменено по сравнению с вашим примером.

Конечно, вы можете скрыть неприятные данные в методе:

public static void main(String... arg) {
    dropWhile(n -> n < 3, Stream.of(0, 1, 2, 3, 0, 1, 2, 3, 4))
      .forEach(System.out::println);
}
static <T> Stream<T> dropWhile(Predicate<T> p, Stream<T> s) {
    class MutableBoolean { boolean b; }
    MutableBoolean inTail = new MutableBoolean();
    return s.filter(i -> inTail.b || !p.test(i) && (inTail.b = true));
}

Более сложным, но более чистым и потенциально более эффективным способом является спуск на металл, то есть интерфейс Spliterator:

static <T> Stream<T> dropWhile(Predicate<T> p, Stream<T> s) {
    Spliterator<T> sp = s.spliterator();
    return StreamSupport.stream(new Spliterators.AbstractSpliterator<T>(
            sp.estimateSize(), sp.characteristics() & ~Spliterator.SIZED) {
        boolean dropped;
        public boolean tryAdvance(Consumer<? super T> action) {
            if(dropped) return sp.tryAdvance(action);
            do {} while(!dropped && sp.tryAdvance(t -> {
                if(!p.test(t)) {
                    dropped=true;
                    action.accept(t);
                }
            }));
            return dropped;
        }
        public void forEachRemaining(Consumer<? super T> action) {
            while(!dropped) if(!tryAdvance(action)) return;
            sp.forEachRemaining(action);
        }
    }, s.isParallel());
}

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

Ответ 2

К сожалению, единственный способ сделать это с помощью Java 8 - это решение, предоставляемое Holger.

Однако операция dropWhile(predicate) была добавлена ​​в Java 9 поэтому, начиная с JDK 9, вы можете просто:

Stream.of(0, 1, 2, 3, 0, 1, 2, 3, 4).dropWhile(n -> n < 3).forEach(System.out::println);