Java >> Java tutorial >  >> Tag >> break

Sådan brydes fra Java Stream forEach

1. Oversigt

Som Java-udviklere skriver vi ofte kode, der itererer over et sæt elementer og udfører en operation på hver enkelt. Java 8-streams-biblioteket og dets forEach metode giver os mulighed for at skrive den kode på en ren, deklarativ måde.

Selvom dette ligner loops, mangler vi det, der svarer til pausen erklæring for at afbryde iteration . En strøm kan være meget lang eller potentielt uendelig , og hvis vi ikke har nogen grund til at fortsætte med at behandle det, vil vi gerne bryde fra det i stedet for at vente på dets sidste element.

I denne selvstudie skal vi se på nogle mekanismer, der giver os mulighed for at simulere en pause erklæring på en Stream.forEach operation.

2. Java 9's Stream.takeWhile()

Lad os antage, at vi har en strøm af String elementer, og vi ønsker at behandle dets elementer, så længe deres længder er ulige.

Lad os prøve Java 9 Stream.takeWhile metode:

Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck")
  .takeWhile(n -> n.length() % 2 != 0)
  .forEach(System.out::println);

Hvis vi kører dette, får vi outputtet:

cat
dog

Lad os sammenligne dette med den tilsvarende kode i almindelig Java ved at bruge en for loop og en pause erklæring, for at hjælpe os med at se, hvordan det virker:

List<String> list = asList("cat", "dog", "elephant", "fox", "rabbit", "duck");
for (int i = 0; i < list.size(); i++) {
    String item = list.get(i);
    if (item.length() % 2 == 0) {
        break;
    }
    System.out.println(item);
}

Som vi kan se, er takeWhile metode giver os mulighed for at opnå præcis det, vi har brug for.

Men hvad nu hvis vi ikke har adopteret Java 9 endnu? Hvordan kan vi opnå en lignende ting ved hjælp af Java 8?

3. En tilpasset Spliterator

Lad os oprette en tilpasset Spliterator der vil fungere som dekoratør for en Stream.spliterator . Vi kan lave denne Spliterator udfør pausen for os.

Først får vi Spliterator fra vores stream, så dekorerer vi den med vores CustomSpliterator og angiv prædikatet for at styre pausen operation. Til sidst opretter vi en ny strøm fra CustomSpliterator:

public static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<T> predicate) {
    CustomSpliterator<T> customSpliterator = new CustomSpliterator<>(stream.spliterator(), predicate);
    return StreamSupport.stream(customSpliterator, false);
}

Lad os se på, hvordan du opretter CustomSpliterator :

public class CustomSpliterator<T> extends Spliterators.AbstractSpliterator<T> {

    private Spliterator<T> splitr;
    private Predicate<T> predicate;
    private boolean isMatched = true;

    public CustomSpliterator(Spliterator<T> splitr, Predicate<T> predicate) {
        super(splitr.estimateSize(), 0);
        this.splitr = splitr;
        this.predicate = predicate;
    }

    @Override
    public synchronized boolean tryAdvance(Consumer<? super T> consumer) {
        boolean hadNext = splitr.tryAdvance(elem -> {
            if (predicate.test(elem) && isMatched) {
                consumer.accept(elem);
            } else {
                isMatched = false;
            }
        });
        return hadNext && isMatched;
    }
}

Så lad os tage et kig på tryAdvance metode. Vi kan se her, at den brugerdefinerede Spliterator behandler elementerne i den dekorerede Spliterator . Behandlingen udføres, så længe vores prædikat er matchet, og den indledende strøm stadig har elementer. Når en af ​​betingelserne bliver false , vores Spliterator "pauser" og streaming-operationen slutter.

Lad os teste vores nye hjælpemetode:

@Test
public void whenCustomTakeWhileIsCalled_ThenCorrectItemsAreReturned() {
    Stream<String> initialStream = 
      Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck");

    List<String> result = 
      CustomTakeWhile.takeWhile(initialStream, x -> x.length() % 2 != 0)
        .collect(Collectors.toList());

    assertEquals(asList("cat", "dog"), result);
}

Som vi kan se, stoppede åen, efter at betingelsen var opfyldt. Til testformål har vi samlet resultaterne på en liste, men vi kunne også have brugt en forEach opkald eller en af ​​de andre funktioner i Stream .

4. En brugerdefineret til hver

Mens du leverer en Strøm med pausen indlejret mekanisme kan være nyttigt, det kan være lettere at fokusere på kun forEach operation.

Lad os bruge Stream.spliterator  direkte uden dekoratør:

public class CustomForEach {

    public static class Breaker {
        private boolean shouldBreak = false;

        public void stop() {
            shouldBreak = true;
        }

        boolean get() {
            return shouldBreak;
        }
    }

    public static <T> void forEach(Stream<T> stream, BiConsumer<T, Breaker> consumer) {
        Spliterator<T> spliterator = stream.spliterator();
        boolean hadNext = true;
        Breaker breaker = new Breaker();

        while (hadNext && !breaker.get()) {
            hadNext = spliterator.tryAdvance(elem -> {
                consumer.accept(elem, breaker);
            });
        }
    }
}

Som vi kan se, er den nye brugerdefinerede forEach metode kalder en BiConsumer leverer vores kode med både det næste element og et breaker-objekt, det kan bruge til at stoppe streamen.

Lad os prøve dette i en enhedstest:

@Test
public void whenCustomForEachIsCalled_ThenCorrectItemsAreReturned() {
    Stream<String> initialStream = Stream.of("cat", "dog", "elephant", "fox", "rabbit", "duck");
    List<String> result = new ArrayList<>();

    CustomForEach.forEach(initialStream, (elem, breaker) -> {
        if (elem.length() % 2 == 0) {
            breaker.stop();
        } else {
            result.add(elem);
        }
    });

    assertEquals(asList("cat", "dog"), result);
}

5. Konklusion

I denne artikel har vi set på måder, hvorpå vi kan give det svarende til at kalde pause på et vandløb. Vi så, hvordan Java 9's takWhile løser det meste af problemet for os, og hvordan man leverer en version af det til Java 8.

Til sidst så vi på en hjælpemetode, som kan give os det, der svarer til en pause handling, mens du itererer på en Stream .

Som altid kan eksempelkoden findes på GitHub.


No
Java tag