Ah! Co to był za wrzesień, niedawno premierę miała nowa wersja biblioteki JUnit 5, a teraz mamy oficjalne wydanie Javy 9. Dziewiątka nie jest tak przełomową wersją Javy jak ósemka, jednakże wprowadza kilka ciekawych featurów i usprawnień. W tym wpisie bierzemy na tapetę Stream API.

takeWhile/dropWhile

Operacje na nieskończonych strumieniach są teraz ułatwione, w przykładzie poniżej zostaną wypisane tylko trzy napisy, natomiast program będzie się wykonywał dalej w nieskończoność:

Stream.iterate(“”, s -> s + “s”)
.filter(s->s.length() < 3)
.forEach(this::log);

W nowym API dostajemy dwie metody:

  • takeWhile
  • dropWhile

Służą one do “obcinania” strumieni. W pierwszy przypadku zostaną wypisane tylko trzy napisy i strumień się zamknie. W drugim przypadku będą wyświetlane wszystkie napisy, których długość jest większa od trzy:

Stream.iterate(“”, s -> s + “s”)
.takeWhile(s->s.length() < 3)
.forEach(this::log);

Stream.iterate(“”, s -> s + “s”)
.dropWhile(s->s.length() < 3)
.forEach(this::log);

iterate

Przykład podobny do tego powyżej. Metoda iterate ma teraz swoją trójargumentową wersję. Chcemy wypisać 10 liczb po kolei:

Stream.iterate(0, i -> i < 10, i -> i + 1)
.forEach(this::log);

No dobra, ale w Javie 8 mogliśmy zrobić coś takiego:

Stream.iterate(0, i -> i + 1)
.limit(10)
.forEach(this::log);

Ale teraz wyobraźmy sobie, że mamy bardziej skomplikowany obiekt, na przykład wypisz mi wszystkie daty od początku roku do dziś:

Stream.iterate(startDate, date -> date.plusDays(1))
.filter(date->date.isBefore(LocalDate.now()))
.forEach(this::log); // To wypisze nam wszystkie daty do dziś, ale strumień będzie się dalej “kręcił”

Stream.iterate(startDate, date -> date.plusDays(1))
.peek(this::log)
.allMatch(date->date.isBefore(LocalDate.now())); // Działający przykład, mniej intuicyjny

W Javie 9 możemy użyć trójargumentowego iterate:

Stream.iterate(startDate,date -> date.isBefore(LocalDate.now()), date -> date.plusDays(1))
.forEach(this::log);

ofNullable

Kolejny przykład to metoda ofNullable. Działa ona tak samo jak ofNullable w Optional (nie musimy sprawdzać, czy element jest nullem i wstawiać Stream.empty()):

Map<String, Integer> map = new HashMap<>();
map.put(“String”, 1);
map.put(“StringSecond”, null);

//JAVA 8
List collect = Stream.of(“String”, “StringSecond”)
.flatMap(element -> {
Integer temp = map.get(element);
return temp != null ? Stream.of(temp) : Stream.empty();
})
.collect(toList());

//JAVA 9
List collection = Stream.of(“String”, “StringSecond”)
.flatMap(element -> Stream.ofNullable(map.get(element)))
.collect(toList());

Stream z Optional

Ostatnim usprawnieniem jest dodanie metody, która pozwala z Optional’a stworzyć Stream. Bo tak naprawdę Optional to dwa elementy: null lub wartość. Od teraz nie trzeba wykonywać operacji sprawdzania, czy element istnieje tylko od razu można wykorzystać flatMap:

@Test
void streamFromOptional(){
//JAVA 8
Stream.of(“string”, “second”)
.map(this::getSomething)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(this::log);

//JAVA 9
Stream.of("string", "second")
        .map(this::getSomething)
        .flatMap(Optional::stream)
        .forEach(this::log);

}

Optional getSomething(String text){
return text.equals(“second”) ? Optional.of(text) : Optional.empty();
}

Github

Całość jak zawsze na GitHubie.