Do jazyka Java verze 8 byly přidány tzv. proudy (streamy), které mají za cíl zjednodušit a hlavně zpřehlednit hromadné operace nad posloupnostmi prvků. Jejich koncept vychází z funkcionálního programování a jsou to obecnější případy kolekcí, u kterých není definována velikost či způsob uložení.

Druhy proudů

Proudy jsou reprezentovány jedním z těchto rozhraní:

  • Stream - generický typ proudu pro referenční a obalové typy
  • DoubleStream - typ proudu pro primitivní číselný typ double
  • IntStream - typ proudu pro primitivní celočíselný typ int
  • LongStream - typ proudu pro primitivní celočíselný typ long

Vytváření proudů

Proud lze získat například z libovolné kolekce:

Collection<String> names = Arrays.asList("John", "David", "Martin");
Stream<String> streamingNames = names.stream();

Lze jej vytvořit i z posloupnosti několika málo pevně daných prvků:

Stream<String> streamingNames = Stream.of("John", "David", "Martin")

Další zajímavou možností definice proudu je "indukce". Proud je zadán prvním prvkem a unární operací, která je aplikována na poslední vytvoření prvek za účelem vytvoření prvku následujícího.

Stream<Boolean> streamingBooleans = Stream.iterate(false, i -> !i);

Tvorba pipeline

Použitím proudu vzniká tzv. pipeline, což je konečná posloupnost operací aplikovaná na nějaký proud.

Operace se dělá do následujících skupn:

  • terminální (terminal) - má postranní efekt nebo produkuje hodnotu; po spuštění této operace je proud uzavřen a nelze jej použít (např. forEach)
  • neterminální (intermediate) - nemá žádné postranní efekty; vytváří vždy nový proud
    • stavové (stateful) - stav operace je ovliněn procházejícími prvky (např. sorted)
    • bezstavové (stateless) - operace nemá žádný vnitřní stav (např. filter)

Mezi nejužitečnější operace lze zařadit tyto:

OperacePopisDruh
filterfiltruje prvky podle zadaného predikátubezstavová-NT
mappřevádí prvky na jiné pomocí zadaného zobrazeníbezstavová-NT
limitomezí maximální délku proudu na zadaný počet prvkůterminální
forEachvšechny prvky proudu postupně odešle konzumentoviterminální
sortedrvky v proudu budou do dalších operací předávány seřazenéstavová-NT
findFirstvezme první prvek z proudubezstavová-NT
countspočítá prvky v proudubezstavová-NT

Příklady

Lidé

Předpokládejme, že existuje třída Person s atributy name (jméno), age (věk), city (město). Dále mějme kolekci nějakých lidí uloženou v kolekci people.

Chceme najít jméno nejstaršího člověka z Washingtonu:

String name = people
        // převést na proud
        .stream()
        // dál propustit pouze lidi z Washingtonu
        .filter(p -> p.getCity().equals("Washington"))
        // seřadit podle věku sestupně
        .sorted(Comparator.comparingInt((Person p) -> p.getAge()).reversed())
        // najít první takovou osobu
        .findFirst()
        .get()
        // získat jméno osoby
        .getName();

Chceme najít průměrný věk:

double avgAge = people
        // převést na proud
        .stream()
        // od osoby získat její věk
        .mapToInt(p -> p.getAge())
        // z čísel vypočítat průměr
        .average()
        .getAsDouble();

Řady

Tento příkaz vypíše číselnou řadu od 0 do 4:

IntStream.range(0, 5).forEach(System.out::println);

Kolektory

Kolektor je třída, která agreguje prvky z proudu. Existují kolektory, které prvky jednoduše uloží do seznamu, provedou seskupení podle nějakého kritéria a tyto skupiny uloží do mapy, a podobně. Lze vytvářet i vlastní kolektory.

// sesbírá jména lidí do seznamu
List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());
// seskupí osoby do mapy podle města, kde žijí
Map<String, List<Person>> personByCity = people
    .stream()
    .collect(Collectors.groupingBy(Person::getCity));
// do mapy uloží počet osob v každém městě
Map<String, Integer> peoplePerCity = people
    .stream()
    .collect(Collectors.groupingBy(Person::getCity, Collectors.counting()));

Reference