Strumienie (te związane z danymi a nie do obsługi plików) w Javie 8 to potężne narzędzie do rzucania danymi w lewo i prawo. Do ich przekształcania, filtracji, itd., dlatego warto je wziąć na tapetę i trochę się nimi pobawić.
Najprościej strumień można sobie wyobrazić jako potok danych na których wykonujemy operację w różnych krokach, aż do uzyskania rezultatu.
Dlatego w strumieniach rozróżniamy parę rodzajów metod, które posiadają różne charakterystyki:
- pośrednie (ang. intermediate) – nie kończą strumienia, pozwalają na dalsze operowanie na danych (np. map)
- kończące/terminalne (ang. terminal) – kończą strumień i zwracają wynik (np. forEach)
- bezstanowe – są wykonane niezależnie od innych danych, co ma znaczenie przy użyciu strumieni równoległych (np. filter)
- stanowe – ich stan zależy od pozostałych danych i dlatego nie są przetwarzane równolegle (np. sort)
- redukcyjne – dokonują redukcji danych (np. max)
Warto wiedzieć że:
- strumienie są wywołane w sposób leniwy, tzn. dane są przetwarzane w monecie wywołania metody kończącej
- transformacje strumienia nie modyfikują wejściowych danych z których strumień został utworzony
Tworzenie strumieni:
- (Collection). stream() – tworzenie strumieni z kolekcji List, Set, z Mapy trzeba pobrać seta
- stream(T[] array), Stream.of(T… values)– tworzenie strumieni z tablicy
- (String). chars() – strumień z ciągu znaków
- lines(path) – strumień linii tekstu z pliku tekstowego
- (Pattern). splitAsStream() – strumień danych na podstawie wyrażenia regularnego
- generate(…), Stream.iterate(…) – generowanie strumienia z pomocą metod
- (Random). ints(), (Random). longs() – generowanie strumienia z losowymi danymi
- concat(stream1, stream2) – Łączenie dwóch strumieni
Metody do operacji na strumieniach:
Metoda | Typ | Przeznaczenie | Opis |
map(Function…) | pośrednia | transformacja | Zwraca strumień z przekształconymi danymi przy promocji funkcji |
flatMap(Function f) – | pośrednia | transformacja | Zwraca strumień z przekształconymi elementami strumienia. Przy czym przekształcane dane będą połączone w jeden zestaw danych (spłaszczone). |
filter(Predicate p) | pośrednia | filtrowanie | Zwraca strumień danych dla których warunek będzie spełniony |
distinct() | pośrednia | filtrowanie | Zwraca strumień danych które będą unikalne |
sorted(…) | pośrednia, stanowa | transformacja | Zwraca strumień z posortowanymi |
unordered() | pośrednia | transformacja | Przekształca strumień do postaci nieuporządkowanej |
limit(int n) | pośrednia | transformacja | Ogranicza strumień do podanego rozmiaru |
generate(Supplier s) | pośrednia | generowanie | Generuje strumień składający się z wygenerowanych elementów |
iterate(T init, UnaryOperator op) | pośrednia | generowanie | Generuje strumień tworzony poprzez iteracyjne wywołanie operatora |
substream(…) | pośrednia | filtrowanie | Zwraca część strumienia, n-elementów od… |
parallel () | pośrednia | generowanie | Generuje strumień równoległy |
sequential() | pośrednia | generowanie | Generuje strumień sekwencyjny |
allMatch(Predicate p) | kończąca | redukcja | Testuje czy w strumieniu wszystkie obiekty spełniają podany warunek |
anyMatch(Predicate p) | kończąca | redukcja | Testuje czy w strumieniu jeden chociaż jeden obiekt spełnia podany warunek |
noneMatch(Predicate p) | kończąca | redukcja | Testuje czy w strumieniu nie ma obiektów spełniających podany warunek |
findAny() | kończąca | filtrowanie | Zwraca dowolny element strumienia |
findFirst() | kończąca | filtrowanie | Zwraca pierwszy element strumienia |
count() | kończąca | redukcja | |
forEach(Consumer c) | Kończąca, bezstanowa | Wykonuje akcję z każdym elementem strumienia, zamykając go jednocześnie | |
forEachOrdered(Consumer) | kończąca, stanowa | Wykonuje akcję z każdym elementem strumienia, zamykając go jednocześnie | |
reduce(…) | kończąca | redukcja | Wykonuje operację redukcji danych za pomocą funkcji |
collect(…) | kończąca | Grupuje wszystkie elementy pozostające w strumieniu i zwraca je w postaci definiowanej przez podany Collector | |
max(…) | kończąca | redukcja | Zwraca największą wartość wynikająca z zastosowanego komparatora |
min(…) | kończąca | redukcja | Zwraca najmniejszą wartość wynikająca z zastosowanego komparatora |
toArray(…) | kończąca | Przekształca strumień do tablicy danych | |
peek(Consumer super T> action) | pośrednia | Wykonuje operację na elemencie bez przekształcania go |
Sposób pracy ze strumieniem, generalnie robimy w ten sposób:
- Tworzenie strumienia
- Pracujemy z metodami pośrednimi gdzie dokonujemy filtrowania, przetwarzania danych, itd.
- Zwracamy rezultat lub dokonujemy innych operacji przy pomocy metod kończących
Na pewno warto zapoznać się z: https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html
Porównanie kodu z Javy 7 do Javy 8, można powiedzieć ze jest czytelniej:
// // Dodanie mapy z danymi // Map<String, Integer> map = new HashMap<>(); map.put("jeden", 1); map.put("dwa", 2); map.put("trzy", 3); map.put("cztery", 4); map.put("pięć", 5); map.put("szcześć", 6); // // Kod w Javie 7 // List<String> listJava7 = new ArrayList<>(); for (Map.Entry<String, Integer> entry : map.entrySet()){ if (entry.getValue() > 3){ listJava7.add(entry.getKey()); } } System.out.println("Java7: " + listJava7); // // Kod w Javie 8 // List<String> listJava8 = map.entrySet().stream() .filter(entry -> entry.getValue() > 3) .map(Map.Entry::getKey) .collect(Collectors.toList()); System.out.println("Java8: " + listJava8); |
Przykłady strumieni:
Tworzenie strumienii
// // Z tablicy // System.out.println("Dane tablicy:"); double[] as = {42.20, 12.12, 10.0, 20.0, 30.0, 40.0, 43.0, 23.23, 10.22}; Arrays.stream(as) .forEach(d -> System.out.print(d + " ")); // // Losowe // System.out.println("Losowe dane:"); Random rnd = new Random(); rnd.ints(10, 0, 10) .forEach(d -> System.out.print(d + " ")); // // Z kolekcji // System.out.println("Dane z kolekcji:"); List<String> list = new ArrayList<>(); list.add("Pierwszy"); list.add("Drugi"); list.add("Trzeci"); list.add("Czwarty"); list.add("Piąty"); list.add("Szósty"); list.parallelStream() .forEach(s -> System.out.print(s + " ")); // // Z kolekcji intów // System.out.println("Dane intów z kolekcji:"); final List<Integer> integerList = Arrays.asList(10, 20, 30, 40, 50, 60, 70, 80, 90, 100); integerList.stream() .forEach(i -> System.out.print(i + " ")); |
Operacje na strumieniach
// // Fitrowanie przy pomnocy strumieni // System.out.println("Filtrowanie danych:"); list.stream() .filter(s -> s.startsWith("P") || s.startsWith("C")) .forEach(s -> System.out.print(s + " ")); // // Sortowanie // System.out.println("Sortowanie danych:"); list.stream() .sorted(Comparator.reverseOrder()) .forEach(s -> System.out.print(s + " ")); // // Mapowanie, za obiekt podstawiany jest wynikający z mapowania // System.out.println("Mapowanie danych:"); list.stream() .map(s -> s + " czy jest równy 'Trzeci' ? " + (s.equals("Trzeci") ? "TAK" : "NIE")) .forEach(s -> System.out.println(s)); // // Sortowanie, filtrowanie, mapowanie // System.out.println("Obróbka danych:"); list.stream() .map(s -> s.toUpperCase()) .filter(s -> s.endsWith("Y")) .sorted() .forEach(System.out::println); |
Operacja na własnych klasach
Mamy klasę przechowywująca informację o krajach:
class Country{ private String name; private String system; private int area; private int population; public Country(String name, String system, int area, int population){ this.name = name; this.system = system; this.area = area; this.population = population; } public String getName(){ return name; } public String getSystem(){ return system; } public int getArea(){ return area; } public int getPopulation(){ return population; } @Override public String toString(){ return "Państwo: " + name + " powierzchnia: " + area + " km2 populacja: " + population; } } |
Możemy przy pomocy strumieni gdzie używamy małej ilości kodu trochę popracować nad danymi:
// // Dodanie przykładowych danych // List<Country> countryList = Arrays.asList( new Country("Poland", "Republika", 312679, 38454576), new Country("Germany", "system kanclerski", 357375, 81083600), new Country("Česká republika", "Republika", 78866, 10541466), new Country("Slovenská republika", "Republika", 49035, 5421349), new Country("République française", "Republika semiprezydencka", 643801, 66318000) ); // // Fitrowanie i sortowanie po nazwie // final List<Country> countriesMoreThan10Million = countryList.stream() .filter(country -> country.getPopulation() > 10000000) .sorted((o1, o2) -> o1.getName().compareToIgnoreCase(o2.getName())) .collect(Collectors.toList()); System.out.println("Państwa z populacją ponad 10 mil:"); countriesMoreThan10Million.forEach(System.out::println); // // Grupowanie według systemu politycznego // Map<String, List<Country>> countriesGroupBySystem = countryList.stream() .sorted((o1, o2) -> o1.getName().compareToIgnoreCase(o2.getName())) .collect(Collectors.groupingBy(b -> b.getSystem())); countriesGroupBySystem.forEach((s, countries) -> System.out.println("Ustrój: " + s + " " + countries)); // // Wyliczenie średniej poulacji dla podanej listy krajów // Double avgPopulation = countryList.stream() .collect(Collectors.averagingInt(Country::getPopulation)); System.out.println("Średnia populacji dla podanej listy krajów to " + Math.round(avgPopulation) + " osób"); |
Możliwość komentowania jest wyłączona.