Java8 stream – przykłady użycia
Mimo, że od debiutu javy 8 minęło już parę lat i używanie jej funkcjonalności powinno być standardem to jednak nie do końca tak jest. Podczas programowanie w parach kolega mnie zapytał czy znam jakieś strony z przykładami użycia. O ile dla Optionala powstał niedawno ciekawy artykuł na dzone to dla streamów nie kojarzę niczego w formie kod w javie poniżej 8 i jego odpowiednik w tej wersji.
Poniżej kilka przykładów użycia streamów z javy 8 kontra kod napisany we wcześniejszej wersji javy. Dzięki nim powinno być łatwiej zacząć używać streamów w codziennej pracy.
Java8 stream – przykłady
W większości przypadków używam listy Stringów zdefiniowanej w następujący sposób:
Arrays.asList("first", "second", "third");
Jeśli dane są inne jest to zaznaczone w ciele metody. Do swoich działań wykorzystuję metody toUpperCase oraz length().
1. Iterowanie z wywołaniem akcji która zwraca void
Często zdarza się iterować po liście aby wykonać akcję która nic nie zwraca. Możesz wtedy użyć metody forEach.
//pre java8
void iterateList() {
for (String string : strings) {
someService.doSomething(string);
}
}
//java8
void iterateList() {
strings.forEach(someService::doSomething);
}
2. Zmapowanie obiektu
Jednak w większości przypadków będziesz chciał zmapować swoją kolekcję na coś innego. Do tego służy metoda map.
// pre java 8
List<String> map() {
List<String> upperCases = new ArrayList<>();
for (String string : strings) {
upperCases.add(string.toUpperCase());
}
return upperCases;
}
// java 8
List<String> map() {
return strings.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
}
3. Zwrócenie ArrayList
Domyślnie powyższa metoda zwraca ArrayList. Dzieje się to w metodzie collect. Metoda ta jest również używana w innych przypadkach zwracanych obiektów. Jest ona jedną z ważniejszych dla javy8 i streamów.
4. Zwrócenie HashSet
Stworzenie HashSetu jest podobne do tego jak stworzenie ArrayList.
//pre java8
Set<String> mapToSet() {
Set<String> upperCases = new HashSet<>();
for (String string : strings) {
upperCases.add(string.toUpperCase());
}
return upperCases;
}
//java
Set<String> mapToSet() {
return strings.stream()
.map(String::toUpperCase)
.collect(Collectors.toSet());
}
5. Zwrócenie LinkedList
Aby stworzyć nową LinkedList ze streamu należy postąpić prawie podobnie jak w powyższych przykładach.
//pre java 8
List<String> mapToLinkedList() {
List<String> upperCases = new LinkedList<>();
for (String string : strings) {
upperCases.add(string.toUpperCase());
}
return upperCases;
}
//java 8
List<String> mapToLinkedList() {
return strings.stream()
.map(String::toUpperCase)
.collect(Collectors.toCollection(LinkedList::new));
}
6. Zwrócenie znalezionych elementów w kolekcji
Często chcesz zwrócić tyko część kolekcji przefiltrowanej po jakimś warunku.
//pre java 8
List<String> filter(String filter) {
List<String> result = new ArrayList<>();
for (String string : strings) {
if (string.equals(filter)) {
result.add(string);
}
}
return result;
}
//java 8
List<String> filter(String filter) {
return strings.stream()
.filter(s -> s.equals(filter))
.collect(Collectors.toList());
}
7. Zwrócenie pierwszego znalezionego elementu lub wartość domyślną
Jednak czasami interesuje nas tylko jeden element, który spełnia określony warunek lub wartość domyślna.
//pre java 8
String mapFirstFilteredFound(String filter) {
for (String string : strings) {
if (string.equals(filter)) {
return string.toUpperCase();
}
}
return "DEFAULT";
}
//java 8
String mapFirstFilteredFound(String filter) {
return strings.stream()
.filter(s -> s.equals(filter))
.map(String::toUpperCase)
.findFirst()
.orElse("DEFAULT");
}
W czasach przed java8 najłatwiej było zwrócić pierwszy znaleziony element ale jeśli kolejność nie jest dla Ciebie ważna to lepiej użyć metody findAny zamiast findFirst.
//java 8
String mapAnyFilteredFound(String filter) {
return strings.stream()
.filter(s -> s.equals(filter))
.map(String::toUpperCase)
.findAny()
.orElse("DEFAULT");
}
8. Użycie licznika z zakresem otwartym
Aby iterować po kolekcji i mieć wartość licznika możesz skorzystać z IntStream.range dla przedziału otwartego.
//pre java 8
List<String> mapWithCounterOpenRange() {
List<String> result = new ArrayList<>();
for (int i = 0; i < strings.size(); i++) {
result.add(String.valueOf(i));
}
return result;
}
//java 8
List<String> mapWithCounterOpenRange() {
return IntStream.range(0, strings.size())
.mapToObj(String::valueOf)
.collect(Collectors.toList());
}
9. Użycie licznika z zakresem zamkniętym
Lub z IntStream.rangeClosed dla przedziału zamkniętego.
//pre java 8
List<String> mapWithCounterCloseRange() {
List<String> result = new ArrayList<>();
for (int i = 0; i <= strings.size(); i++) {
result.add(String.valueOf(i));
}
return result;
}
// java 8
List<String> mapWithCounterCloseRange() {
return IntStream.rangeClosed(0, strings.size())
.mapToObj(String::valueOf)
.collect(Collectors.toList());
}

10. Zwrócenie unikalnych wartości
Aby zwrócić unikalne wartości można użyć metody distinct.
//pre java 8
List<String> mapOnlyUnique() {
List<String> stringsWithDuplicates = Arrays.asList("first", "second", "third", "second");
List<String> upperCasesWithoutDuplicates = new ArrayList<>();
for (String string : stringsWithDuplicates) {
if (!upperCasesWithoutDuplicates.contains(string.toUpperCase())) {
upperCasesWithoutDuplicates.add(string.toUpperCase());
}
}
return upperCasesWithoutDuplicates;
}
//java 8
List<String> mapOnlyUnique() {
List<String> stringsWithDuplicates = Arrays.asList("first", "second", "third", "second");
return stringsWithDuplicates.stream()
.distinct()
.map(String::toUpperCase)
.collect(Collectors.toList());
}
11. Stworzenie mapy
W niektórych przypadkach zamiast listy ważne dla Ciebie będzie zwrócenie mapy.
//pre java 8
Map<String, String> createMap() {
Map<String, String> result = new HashMap<>();
for (String string : strings) {
result.put(string, string.toUpperCase());
}
return result;
}
//java 8
Map<String, String> createMap() {
return strings.stream()
.collect(Collectors.toMap(s -> s, String::toUpperCase));
}
12. Utworzenie HashMap
Domyślnie zwracana jest HashMap tak jak w przykładzie powyżej.
13. Utworzenie LinkedHashMap
Stworzenie LinkedHashMap jest trochę bardziej skomplikowane niż LinkedList
//pre java 8
Map<String, String> createLinkedHashMap() {
Map<String, String> result = new LinkedHashMap<>();
for (String string : strings) {
result.put(string, string.toUpperCase());
}
return result;
}
//java 8
Map<String, String> createLinkedHashMap() {
return strings.stream()
.collect(Collectors.toMap(s -> s, String::toUpperCase, (key1, key2) -> key1, LinkedHashMap::new));
}
lub alternatywnie.
Map<String, String> createLinkedHashMapOtherWay() {
return strings.stream()
.collect(
LinkedHashMap::new,
(map, s) -> map.put(s, s.toUpperCase()),
Map::putAll);
}
14. Grupowanie
Natomiast grupowanie jest bardzo łatwym przykładem gdzie java8 stream ma przewagę nad wersją bez streamów.
//pre java8
Map<Integer, List<String>> group() {
Map<Integer, List<String>> result = new HashMap<>();
for (String string : strings) {
if (result.containsKey(string.length())) {
result.get(string.length()).add(string);
} else {
List<String> elements = new ArrayList<>();
elements.add(string);
result.put(string.length(), elements);
}
}
return result;
}
//java8
Map<Integer, List<String>> group() {
return strings.stream()
.collect(Collectors.groupingBy(String::length));
}
15. Sortowanie
Streamy pozwalają Ci sortować daną kolekcję.
//pre java 8
List<String> createSortedDescList() {
List<String> copy = new ArrayList(strings);
copy.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.length() - o1.length();
}
});
return copy;
}
//java 8
List<String> createSortedDescList() {
return strings.stream()
.sorted(Comparator.comparing(String::length).reversed())
.collect(Collectors.toList());
}
16. Zwrócenie najmniejszej wartości
Oraz zwracać jej najmniejsze i największe elementy.
//pre java 8
String min() {
List<String> copy = new ArrayList(strings);
copy.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.length() - o1.length();
}
});
return copy.get(0);
}
//java 8
String min() {
return strings.stream()
.min(Comparator.comparing(String::length).reversed())
.get();
}
Podobnie można zwrócić wartość największą poprzez metodę max()
17. Połączenie elementów listy w jeden element
Często listę stringów chcesz połączyć w jeden string.
//pre java8
String join() {
StringBuilder result = new StringBuilder();
for (String string : strings) {
result.append(string + ",");
}
return result.substring(0, result.length() - 1);
}
//java8
String joinStream() {
return strings.stream().collect(Collectors.joining(","));
}
W końcu w javie 8 do biblioteki standardowej doszła metoda join dla Stringów. Więc w powyższym przykładzie lepiej byłoby użyć tego kodu poniżej.
String join() {
return String.join(",", strings);
}
18. Spłaszczenie listy list do listy
Również łatwo można spłaszczyć listę list danego typu do prostej listy.
//pre java 8
List<String> flat() {
List<List<String>> listOfList = Arrays.asList(Arrays.asList("first", "second"), Arrays.asList("third", "fourth"));
List<String> result = new ArrayList<>();
for (List<String> list : listOfList) {
for (String string : list) {
result.add(string);
}
}
return result;
}
//java 8
List<String> flat() {
List<List<String>> listOfList = Arrays.asList(Arrays.asList("first", "second"), Arrays.asList("third", "fourth"));
return listOfList.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
}
19. Sumowanie
Sumowanie także jest proste do napisania przy użyciu streamów.
//pre java 8
Integer sum() {
int result = 0;
for (String string : strings) {
result += string.length();
}
return result;
}
//java 8
Integer sum() {
return strings.stream().map(String::length).collect(Collectors.summingInt(Integer::intValue));
}
20. Redukcja
Jednak w niektórych przypadkach lepiej będzie użyć redukcji. Metody reduce mogę tutaj użyć dla sumowanie ale także dla join.
//java 8
Integer sumByReduce() {
return strings.stream().map(String::length).reduce(0, (a, b) -> a + b);
}
Java8 stream – to dopiero początek
To są przykłady, które dosyć często widziałem w kodzie. Ich zapis przy użyciu streamów często staje się bardziej czytelny, zwięzły i mniej błędogenny. Najtrudniejszym elementem nauki używania streamów może być przyzwyczajenie się do nowej składni. Dzięki tym przykładom możesz spróbować zacząć z nich korzystać. Krok po kroku dodawać kolejne elementy i budować bardziej zaawansowane operacje. Tylko pamiętaj nie przesadź, bo nawet z nimi możesz stworzyć legacy code. Może nawet zauważysz, że nie wszystko jest idealnie z użyciem streamów w javie 8 i poznasz inne biblioteki, które mają to inaczej rozwiązane. Możliwe, że wtedy trafisz na vavr, eclispeCollection.
Kod dostępny na githubie.