Java 8 og Infinite Streams

1. Oversikt

I denne artikkelen vil vi se på a java.util.Stream API og vi får se hvordan vi kan bruke den konstruksjonen til å operere på en uendelig strøm av data / elementer.

Muligheten for å jobbe med den uendelige rekkefølgen av elementer er basert på det faktum at bekker er bygget for å være late.

Denne latskapen oppnås ved å skille mellom to typer operasjoner som kan utføres på bekker: mellomliggende og terminal operasjoner.

2. Mellom- og terminaloperasjoner

Alle Strøm drift er delt inn i mellomliggende og terminal operasjoner og kombineres for å danne strømrørledninger.

En strømrørledning består av en kilde (for eksempel en Samling, en matrise, en generatorfunksjon, en I / O-kanal eller uendelig sekvensgenerator); etterfulgt av null eller flere mellomoperasjoner og en terminaloperasjon.

2.1. Mellomliggende Operasjoner

Mellomliggende operasjoner er ikke utført enhet noen terminal operasjonen påberopes.

De er sammensatt og danner en rørledning av en Strøm henrettelse. De mellomliggende operasjonen kan legges til en Strøm rørledning etter metoder:

  • filter()
  • kart()
  • flatMap ()
  • distinkt()
  • sortert ()
  • kikke ()
  • grense()
  • hopp over ()

Alle Mellomliggende operasjoner er late, så de blir ikke utført før det faktisk er behov for et resultat av en behandling.

I utgangspunktet, mellomliggende operasjoner returnerer en ny strøm. Å utføre en mellomoperasjon utfører faktisk ingen operasjoner, men oppretter i stedet en ny strøm som når den krysses inneholder elementene i den opprinnelige strømmen som samsvarer med det gitte predikatet.

Som sådan, gjennomgang av Strøm begynner ikke før terminal drift av rørledningen utføres.

Det er veldig viktig egenskap, spesielt viktig for uendelige strømmer - fordi det lar oss lage strømmer som faktisk vil bli påkalt når en Terminal drift kalles.

2.2. Terminal Operasjoner

Terminal operasjoner kan krysse strømmen for å gi et resultat eller en bivirkning.

Etter at terminaloperasjonen er utført, betraktes strømrørledningen som forbrukt, og kan ikke lenger brukes. I nesten alle tilfeller er terminaloperasjoner ivrige, og fullfører gjennomkjøringen av datakilden og behandlingen av rørledningen før retur.

Ivrigheten til en terminaloperasjon er viktig med uendelige strømmer fordi i øyeblikket av behandlingen må vi tenke nøye om vår Strøm er skikkelig avgrenset av, for eksempel en grense() transformasjon. Terminal virksomheten er:

  • for hver()
  • forEachOrdered ()
  • toArray ()
  • redusere()
  • samle inn()
  • min ()
  • maks ()
  • telle()
  • anyMatch ()
  • allMatch ()
  • noneMatch ()
  • findFirst ()
  • findAny ()

Hver av disse operasjonene vil utløse kjøring av alle mellomliggende operasjoner.

3. Uendelige bekker

Nå som vi forstår disse to begrepene - Mellomliggende og Terminal operasjoner - vi er i stand til å skrive en uendelig strøm som utnytter dovenes strømmer.

La oss si at vi ønsker å lage en uendelig strøm av elementer fra null som økes med to. Da må vi begrense den sekvensen før vi ringer terminaloperasjonen.

Det er avgjørende å bruke en grense() metode før du utfører en samle inn() metode det er en terminaloperasjon, ellers vil programmet vårt kjøre på ubestemt tid:

// gitt Stream infiniteStream = Stream.iterate (0, i -> i + 2); // når List collect = infiniteStream .limit (10) .collect (Collectors.toList ()); // deretter assertEquals (samle, Arrays.asList (0, 2, 4, 6, 8, 10, 12, 14, 16, 18));

Vi opprettet en uendelig strøm ved hjelp av en repetere() metode. Så ringte vi a grense() transformasjon og en samle inn() terminaldrift. Så i vår resulterende Liste, vi vil ha de første 10 elementene i en uendelig rekkefølge på grunn av latskap av a Strøm.

4. Uendelig strøm av en tilpasset type elementer

La oss si at vi ønsker å skape en uendelig strøm av tilfeldig UUIDer.

Det første trinnet for å oppnå dette ved hjelp av Strøm API er å lage en Leverandør av disse tilfeldige verdiene:

Leverandør randomUUIDSupplier = UUID :: randomUUID;

Når vi definerer en leverandør, kan vi lage en uendelig strøm ved hjelp av en generere() metode:

Stream infiniteStreamOfRandomUUID = Stream.generate (randomUUIDSupplier);

Da kunne vi ta et par elementer fra den strømmen. Vi må huske å bruke en grense() metode hvis vi vil at programmet vårt skal fullføres på en begrenset tid:

Liste randomInts = infiniteStreamOfRandomUUID .skip (10) .limit (10) .collect (Collectors.toList ());

Vi bruker en hopp over () transformasjon for å forkaste de 10 første resultatene og ta de neste 10 elementene. Vi kan lage en uendelig strøm av alle tilpassede typeelementer ved å passere en funksjon av a Leverandør grensesnitt til en generere() metode på en Strøm.

6. Gjør mens - Stream Way

La oss si at vi har en enkel do.. While loop i koden vår:

int i = 0; mens (i <10) {System.out.println (i); i ++; }

Vi skriver ut Jeg teller ti ganger. Vi kan forvente at en slik konstruksjon lett kan skrives med Strøm API og ideelt sett ville vi ha en gjør mens() metode på en strøm.

Dessverre er det ingen slik metode på en strøm og når vi ønsker å oppnå funksjonalitet som ligner på standard gjør mens loop vi trenger å bruke en grense() metode:

Strøm heltall = Strøm .iterate (0, i -> i + 1); heltall .limit (10) .forEach (System.out :: println);

Vi oppnådde samme funksjonalitet som en imperativ mens loop med mindre kode, men ring til grense() funksjonen er ikke så beskrivende som den ville vært hvis vi hadde en gjør mens() metode på en Strøm gjenstand.

5. Konklusjon

Denne artikkelen forklarer hvordan vi kan bruke Stream API for å skape uendelige strømmer. Disse, når de brukes sammen med transformasjoner som grense() - kan gjøre noen scenarier ganske lettere å forstå og implementere.

Koden som støtter alle disse eksemplene, finnes i GitHub-prosjektet - dette er et Maven-prosjekt, så det skal være enkelt å importere og kjøre som det er.