-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathjava-stream-api.md
239 lines (195 loc) · 8.55 KB
/
java-stream-api.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
---
title: Die Java Stream API
description: ''
sidebar_position: 300
tags: [java-stream-api]
---
Die Java Stream API stellt Klassen zum Erzeugen von und Arbeiten mit Strömen
(Streams) bereit. Ein Strom stellt eine Folge von Elementen dar, die das
Ausführen verketteter, intermediärer und terminaler Operationen auf diesen
Elementen nacheinander oder parallel ermöglicht. Die Daten, die durch die
Elemente des Stromes repräsentiert werden, werden dabei durch den Strom selbst
nicht verändert. Die Verarbeitung der Elemente erfolgt nach dem Prinzip der
Bedarfsauswertung (Lazy Evaluation). Neben endlichen Strömen stellt die Java
Stream API auch Methoden zum Erzeugen unendlicher Ströme bereit.
```mermaid
flowchart TD
Strom1 -->|Filtern| Strom2
Strom2 -->|Abbilden| Strom3
Strom3 -->|Sortieren| Strom4
subgraph Strom1
hans[Hans, 18, m]
peter[Peter, 27, m]
lisa[Lisa, 43, w]
heidi[Heidi, 19, w]
maria[Maria, 17, w]
end
subgraph Strom2
peter2[Peter, 27, m]
heidi2[Heidi, 19, w]
maria2[Maria, 17, w]
end
subgraph Strom3
peter3[PETER]
heidi3[HEIDI]
maria3[MARIA]
end
subgraph Strom4
heidi4[HEIDI]
maria4[MARIA]
peter4[PETER]
end
```
:::note Hinweis
Ströme (Paket `java.util.stream`) haben nichts mit
[Datenströmen (IO-Streams)](io-streams) (Paket `java.io`) zu tun.
:::
## Erzeugen von Strömen
Ströme können unter anderem aus Feldern, Datensammlungen wie z.B. Listen und
Mengen sowie Einzelobjekten erzeugt werden.
```java title="MainClass.java" showLineNumbers
public class MainClass {
public static void main(String[] args) {
int[] array = {4, 8, 15, 16, 23, 42};
IntStream integerStream = Arrays.stream(array);
List<Integer> list = List.of(4, 8, 15, 16, 23, 42);
Stream<Integer> integerStream2 = list.stream();
Stream<Integer> integerStream3 = Stream.of(4, 8, 15, 16, 23, 42);
}
}
```
:::note Hinweis
Die Zahlenfolge 4-8-15-16-23-42 spielt eine große Rolle in der Fernsehserie
_Lost_.
:::
Im Gegensatz zu "normalen" Strömen besitzen Objekte der Klassen `IntStreams`,
`DoubleStreams` und `LongStreams` Methoden zur Weiterverarbeitung ihrer
primitiver Werte.
```java title="MainClass.java" showLineNumbers
public class MainClass {
public static void main(String[] args) {
int[] array = {4, 8, 15, 16, 23, 42};
IntStream integerStream = Arrays.stream(array);
int sum = integerStream.sum();
}
}
```
## Intermediäre Operationen
Intermediäre Operationen ermöglichen unter anderem das Filtern, Abbilden sowie
das Sortieren von Strömen und liefern als Ergebnis wiederum einen Strom.
| Operation | Methode | Schnittstellen-Methode |
| ------------- | ---------------------------------------------------------- | -------------------------------- |
| Filtern | `Stream<T> filter(predicate: Predicate<T>)` | `boolean test(t: T)` |
| Abbilden | `Stream<T> map(mapper: Function<T, R>)` | `R apply(t: T)` |
| Abbilden | `DoubleStream mapToDouble(mapper: ToDoubleFunction<T, R>)` | `double applyAsDouble(value: T)` |
| Abbilden | `IntStream mapToInt(mapper: ToIntFunction<T, R>)` | `int applyAsInt(vaue: T)` |
| Abbilden | `LongStream mapToLong(mapper: ToLongFunction<T, R>)` | `long applyAsLong(value: T)` |
| Spähen | `Stream<T> peek(consumer: Consumer<T>)` | `void accept(t: T)` |
| Sortieren | `Stream<T> sorted(comparator: Comparator<T>)` | `int compare(o1: T, o2: T)` |
| Unterscheiden | `Stream<T> distinct()` | - |
| Begrenzen | `Stream<T> limit(maxSize: long)` | - |
| Überspringen | `Stream<T> skip(n: long)` | - |
## Terminale Operationen
Terminale Operationen werden z.B. zum Prüfen, zum Aggregieren oder zum Sammeln
verwendet. Da terminale Operationen den Strom schließen, können auf ihnen keine
weiteren Operationen mehr ausgeführt werden.
| Operation | Methode | Schnittstellen-Methode |
| ----------- | -------------------------------------------- | --------------------------- |
| Finden | `Optional<T> findAny()` | - |
| Finden | `Optional<T> findFirst()` | - |
| Prüfen | `boolean allMatch(predicate: Predicate<T>)` | `boolean test(t: T)` |
| Prüfen | `boolean anyMatch(predicate: Predicate<T>)` | `boolean test(t: T)` |
| Prüfen | `boolean noneMatch(predicate: Predicate<T>)` | `boolean test(t: T)` |
| Aggregieren | `Optional<T> min(comparator: Comparator<T>)` | `int compare(o1: T, o2: T)` |
| Aggregieren | `Optional<T> max(comparator: Comparator<T>)` | `int compare(o1: T, o2: T)` |
| Aggregieren | `long count()` | - |
| Sammeln | `R collect(collector: Collector<T, A, R>)` | - |
| Ausführen | `void forEach(action: Consumer<T>)` | `void accept(t: T)` |
Zahlenströme (`IntStream`, `DoubleStream`, `LongStream`) besitzen die
zusätzlichen terminale Operationen `int|double|long sum()` und
`OptionalDouble average()`.
## Bedarfsauswertung (Lazy Evaluation)
Die Elemente in Strömen werden nur bei Bedarf ausgewertet. Intermediäre
Operationen werden also nur dann ausgeführt, wenn eine terminale Operation
vorhanden ist und bei verketteten Operationen werden für jedes Element die
einzelnen Operationen nacheinander ausgeführt.
In der main-Methode der Startklasse wird auf den Zahlenstrom 4-8-15-16-23-42
zunächst der Filter _Zahl > 15_ angewendet, anschließend der Filter _Zahl =
ganze Zahl_ und abschließend werden die verbliebenen Zahlen auf der Konsole
ausgegeben. Zum Nachvollziehen werden die Zahlen auch bei den beiden Filtern auf
der Konsole ausgegeben.
```java title="MainClass.java" showLineNumbers
public class MainClass {
public static void main(String[] args) {
Stream.of(4, 8, 15, 16, 23, 42).filter(i -> {
System.out.println(i + ": filter 1");
return i % 2 == 0;
}).filter(i -> {
System.out.println(i + ": filter 2");
return i > 15;
}).forEach(i -> System.out.println(i + ": forEach"));
}
}
```
Ohne Bedarfsauswertung würden die verschiedenen Operationen für die jeweils
verbliebenen Elemente ausgeführt nacheinander werden. Die Ausgabe sähe wie folgt
aus:
```
4: filter 1
8: filter 1
15: filter 1
16: filter 1
23: filter 1
42: filter 1
4: filter 2
8: filter 2
16: filter 2
42: filter 2
16: forEach
42: forEach
```
Aufgrund der Bedarfsauswertung werden die verschiedenen Operationen aber für
jedes Element einzeln nacheinander ausgeführt. Dadurch ergibt sich folgende
Ausgabe:
```
4: filter 1
4: filter 2
8: filter 1
8: filter 2
15: filter 1
16: filter 1
16: filter 2
16: forEach
23: filter 1
42: filter 1
42: filter 2
42: forEach
```
## Unendliche Ströme
Die Java Stream API stellt drei Methoden zur Verfügung, mit deren Hilfe
(un)endliche Ströme erzeugt werden können:
- Die Methode `Stream<T> iterate(seed: T, f: UnaryOperator<T>)` generiert einen
unendlichen Strom aus einem Startwert und einer Funktion, welche das nächste
Element erstellt
- Die Methode
`Stream<T> iterate(seed: T, hasNext: Predicat <T>, next: UnaryOperator<T>)`
erweitert die "normale" iterate-Methode um eine Prädikatsfunktion zum Beenden
des Stroms
- Die Methode `Stream<T> generate(s: Supplier<T>)` kann zum Beispiel zum
Erzeugen unendlich vieler zufälliger Elemente genutzt werden
In der main-Methode der Startklasse werden drei (un)endliche Zahlenströme
erzeugt.
```java title="MainClass.java" showLineNumbers
public class MainClass {
public static void main(String[] args) {
Stream.iterate(0, i -> ++i).limit(100).forEach(System.out::println);
Stream.iterate(0, i -> i < 100, i -> ++i).forEach(System.out::println);
Stream.generate(() -> new Random().nextInt(100)).limit(100).forEach(System.out::println);
}
}
```
Die ersten beiden Zahlenströme geben die Zahlen von 0 bis 99 aus, der dritte
Zahlenstrom 100 Pseudozufallszahlen von 0 bis 99. Der erste und dritte
Zahlenstrom würden eigentlich unendliche viele (Pseudozufalls-)Zahlen erzeugen,
werden aber durch die Methode `Stream<T> limit(maxSize: long)` auf 100
(Pseudozufalls-)Zahlen begrenzt.