Java8 stream – przykłady użycia

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());
}
Java8 stream przypadki użycia.

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.