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.