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 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 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
listJava7 = new ArrayList();
for (Map.Entry entry : map.entrySet()){
if (entry.getValue() > 3){
listJava7.add(entry.getKey());
}
}
System.out.println("Java7: " + listJava7);
//
// Kod w Javie 8
//
List
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
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
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
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
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> 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");