Stream API używa lambd, więc zacznijmy od wyjaśnienia sobie, czym są i po co służą owe „lambdy”. W uproszczeniu mówiąc, są to anonimowe metody, czyli metody nie należące do żadnej klasy, ale których definicję piszemy od razu w miejscu ich wywołania. Zobrazujemy sobie to za chwilę na przykładzie, jednak zanim to zrobimy, utwórzmy prostą klasę **Person**, która będzie nam służyła także w dalszej części artykułu.
```
public class Person {
private String name;
private int age;
// constructors, getters and setters
```
Mamy prostą klasę **Person**, w której mamy pola **name** oraz **age**. Dla przejrzystości, przyjmijmy, iż są zdefiniowane konstruktory, gettery oraz settery. Załóżmy, iż mamy listę takich osób, a następnie, chcemy tę listę posortować według imienia:
```
List persons = new ArrayList();
persons.add(new Person("Andrzej", 30));
persons.add(new Person("Stefan", 26));
persons.add(new Person("Katarzyna", 29));
Collections.sort(persons, new Comparator() {
@Override
public int compare(Person p1, Person p2) {
return p1.getName().compareTo(p2.getName());
}
});
```
Metoda **sort** jako drugi argument przyjmuje obiekt typu **Comparator**, więc w tym wypadku tworzymy klasę anonimową. Przed javą 8, trzeba było pisać tak jak na przykładzie powyżej – sporo „boilerplate kodu”, czyli kodu wymaganego przez kompilator, ale nie wnoszącego nic do logiki biznesowej. Tak naprawdę tylko:
```
p1.getName().compareTo(p2.getName());
```
Jest tu logiką biznesową. Z pomocą przychodzą lambdy:
```
Collections.sort(persons,(p1, p2) -> p1.getName().compareTo(p2.getName()));
```
Omówmy poszczególne elementy: (**p1**, **p2**) to zestaw argumentów. Jak widać, nie potrzeba definiowania typów – kompilator wie o nich. **->** to symbol składniowy, oddzielający argumenty od ciała naszej lambdy. Następnie jest już ciało lambdy – jeżeli można zapisać je w jednej linijce, niepotrzebne są zarówno nawiasy klamrowe jak i słówko **return**.
Kiedy już znamy lambdy, przejdźmy do „mięsa”, czyli Stream API. Najczęściej jest ono używane, kiedy chcemy daną kolekcję przefiltrować lub przekształcić, przy używaniu łatwiejszego i bardziej czytelnego kodu niż standardowe zagnieżdżone pętle. Rozszerzmy naszą początkową klasę **Person**:
```
public class Person {
private String name;
private int age;
private List pets;
// constructors, getters and setters
}
```
Dołożyliśmy listę nazw zwierząt danej osoby.
Załóżmy, iż mając listę takich osób, chcemy otrzymać listę takich osób, które mają mniej niż 30 lat:
```
List personsUnder30Age = persons.stream()
.filter(person -> person.getAge() person.getAge() names = persons.stream()
.map(person -> person.getName())
.collect(Collectors.toList());
```
Widzimy tutaj podobieństwa do przykładu z filtrowaniem, jednak różnicą jest użycie metody **.map(...)** zamiast **.filter(...)**. Dokonuje ona przemapowania – w tym przykładzie, przekształca ona stream elementów typu **Person** w stream elementów typu **String** (według użycia metody **getName()** obiektu **Person**).
Kolejnym przykładem, będzie sytuacja przekształcenia listy osób na listę nazw zwierząt – np. chcemy znać wszystkie dostępne zwierzęta w danej grupie osób (czyli np „pies”, „kot” itp). Na pierwszy rzut oka, moglibyśmy zacząć z następującą lambdą:
```
.map(person -> person.getPets())
```
Jednak jak się przekonamy, zamiast streamu elementów typu **String**, dostaniemy stream elementów typu **List**! Nie o to nam chodziło, więc musimy jakoś „spłaszczyć” tę strukturę. Z pomocą przychodzi metoda **.flatMap(...)**:
```
Set petNames = persons.stream()
.map(person -> person.getPets())
.flatMap(pets -> pets.stream())
.collect(Collectors.toSet());
```
A więc używamy mapowania normalnie tak jak chcemy, jednak później, jest potrzeba spłaszczenia streama – dzięki temu zbiegowi, później operujemy już na streamie Stringów. Ale uwaga – różni ludzie mogli mieć te same zwierzęta, więc aby uniknąć duplikatów, powinniśmy użyć tym razem setu zamiast listy.
Streamy w javie pozwalają na szybkie pisanie czytelnego kodu, jednak należy mieć na uwadze, iż wprowadzają pewien narzut, także w przypadku bardzo rygorystycznych wymagań czasowych może się okazać, iż pozostanie przy standardowych pętlach będzie bardziej efektywne. W powyższym artykule przedstawiłem 3 najczęściej używane metody operacji na streamach, tj. **.map(...)**, **.filter(...)** oraz **.flatMap(...)**. Oczywiście Stream API jest dużo bogatsze, zatem zachęcam do jego explorowania i próbowania różnych kombinacji.