0
# Enhanced Stream Operations
1
2
Map-specialized stream operations extending Java 8 streams with fluent APIs for key-value pair processing and transformations. This package provides powerful stream operations specifically designed for working with key-value data structures.
3
4
## Capabilities
5
6
### MapStream Interface
7
8
Enhanced stream operations specialized for key-value pairs with full integration with Java 8 Stream API.
9
10
```java { .api }
11
/**
12
* Stream operations specialized for key-value pairs
13
* @param <K> Key type
14
* @param <V> Value type
15
*/
16
public interface MapStream<K, V> extends BaseStream<Map.Entry<K, V>, MapStream<K, V>> {
17
/**
18
* Create a MapStream from a Map
19
* @param map Source map (may be null)
20
* @return MapStream wrapping the map entries
21
*/
22
static <K, V> MapStream<K, V> of(Map<? extends K, ? extends V> map);
23
24
/**
25
* Create a MapStream from a Map, handling null safely
26
* @param map Source map (null becomes empty stream)
27
* @return MapStream wrapping the map entries or empty stream
28
*/
29
static <K, V> MapStream<K, V> ofNullable(Map<? extends K, ? extends V> map);
30
31
/**
32
* Create a MapStream from a collection of entries
33
* @param entries Collection of map entries
34
* @return MapStream wrapping the entries
35
*/
36
static <K, V> MapStream<K, V> of(Collection<? extends Map.Entry<? extends K, ? extends V>> entries);
37
38
/**
39
* Concatenate two MapStreams
40
* @param a First MapStream
41
* @param b Second MapStream
42
* @return Concatenated MapStream
43
*/
44
static <K, V> MapStream<K, V> concat(MapStream<? extends K, ? extends V> a,
45
MapStream<? extends K, ? extends V> b);
46
47
/**
48
* Create an empty MapStream
49
* @return Empty MapStream
50
*/
51
static <K, V> MapStream<K, V> empty();
52
53
/**
54
* Create a MapStream with a single key-value pair
55
* @param key The key
56
* @param value The value
57
* @return MapStream containing one entry
58
*/
59
static <K, V> MapStream<K, V> of(K key, V value);
60
61
/**
62
* Create a MapStream from multiple entries
63
* @param entries Variable number of map entries
64
* @return MapStream containing the entries
65
*/
66
static <K, V> MapStream<K, V> ofEntries(Map.Entry<? extends K, ? extends V>... entries);
67
68
/**
69
* Create a map entry (convenience method)
70
* @param key The key
71
* @param value The value
72
* @return Map entry
73
*/
74
static <K, V> Map.Entry<K, V> entry(K key, V value);
75
}
76
```
77
78
**Usage Example:**
79
80
```java
81
import aQute.bnd.stream.MapStream;
82
import java.util.Map;
83
import java.util.HashMap;
84
import java.util.function.BiPredicate;
85
import java.util.function.BiFunction;
86
import java.util.function.BiConsumer;
87
88
// Create MapStream from existing map
89
Map<String, Integer> scores = new HashMap<>();
90
scores.put("Alice", 95);
91
scores.put("Bob", 87);
92
scores.put("Charlie", 92);
93
94
MapStream<String, Integer> stream = MapStream.of(scores);
95
96
// Create MapStream from entries
97
MapStream<String, String> config = MapStream.ofEntries(
98
MapStream.entry("host", "localhost"),
99
MapStream.entry("port", "8080"),
100
MapStream.entry("timeout", "30000")
101
);
102
103
// Handle null maps safely
104
Map<String, String> nullableMap = getNullableMap();
105
MapStream<String, String> safeStream = MapStream.ofNullable(nullableMap);
106
```
107
108
### Stream Decomposition
109
110
Extract keys, values, or entries as regular Java streams.
111
112
```java { .api }
113
/**
114
* Get the entries as a regular Stream
115
* @return Stream of Map.Entry objects
116
*/
117
Stream<Map.Entry<K, V>> entries();
118
119
/**
120
* Extract keys as a Stream
121
* @return Stream of keys
122
*/
123
Stream<K> keys();
124
125
/**
126
* Extract values as a Stream
127
* @return Stream of values
128
*/
129
Stream<V> values();
130
```
131
132
**Usage Example:**
133
134
```java
135
MapStream<String, Integer> scores = MapStream.of(scoresMap);
136
137
// Process keys
138
List<String> names = scores.keys()
139
.map(String::toUpperCase)
140
.sorted()
141
.collect(Collectors.toList());
142
143
// Process values
144
OptionalInt maxScore = scores.values()
145
.mapToInt(Integer::intValue)
146
.max();
147
148
// Process entries
149
List<String> results = scores.entries()
150
.map(entry -> entry.getKey() + ": " + entry.getValue())
151
.collect(Collectors.toList());
152
```
153
154
### Transformation Operations
155
156
Transform keys, values, or both while maintaining the MapStream structure.
157
158
```java { .api }
159
/**
160
* Transform values while keeping keys unchanged
161
* @param mapper Function to transform values
162
* @return MapStream with transformed values
163
*/
164
<R> MapStream<K, R> mapValue(Function<? super V, ? extends R> mapper);
165
166
/**
167
* Transform keys while keeping values unchanged
168
* @param mapper Function to transform keys
169
* @return MapStream with transformed keys
170
*/
171
<R> MapStream<R, V> mapKey(Function<? super K, ? extends R> mapper);
172
173
/**
174
* Transform both keys and values using a BiFunction
175
* @param mapper Function to transform key-value pairs to new entries
176
* @return MapStream with transformed entries
177
*/
178
<R, S> MapStream<R, S> map(BiFunction<? super K, ? super V, ? extends Map.Entry<? extends R, ? extends S>> mapper);
179
180
/**
181
* FlatMap operation for MapStreams using BiFunction
182
* @param mapper Function to transform key-value pairs to streams of entries
183
* @return Flattened MapStream
184
*/
185
<R, S> MapStream<R, S> flatMap(BiFunction<? super K, ? super V, ? extends MapStream<? extends R, ? extends S>> mapper);
186
```
187
188
**Usage Example:**
189
190
```java
191
MapStream<String, Integer> scores = MapStream.of(scoresMap);
192
193
// Transform values: convert scores to letter grades
194
MapStream<String, String> grades = scores.mapValue(score -> {
195
if (score >= 90) return "A";
196
if (score >= 80) return "B";
197
if (score >= 70) return "C";
198
if (score >= 60) return "D";
199
return "F";
200
});
201
202
// Transform keys: normalize names to lowercase
203
MapStream<String, Integer> normalized = scores.mapKey(String::toLowerCase);
204
205
// Transform both: create descriptive entries
206
MapStream<String, String> descriptions = scores.map((key, value) ->
207
MapStream.entry(key.toUpperCase(), "Score: " + value));
208
209
// FlatMap: expand each student to multiple subjects
210
MapStream<String, Integer> expanded = scores.flatMap((key, value) ->
211
MapStream.ofEntries(
212
MapStream.entry(key + "-Math", value),
213
MapStream.entry(key + "-Science", value + 5)
214
));
215
```
216
217
### Filtering Operations
218
219
Filter MapStream entries based on keys, values, or both.
220
221
```java { .api }
222
/**
223
* Filter entries based on key-value pairs
224
* @param predicate BiPredicate test for key-value pairs
225
* @return Filtered MapStream
226
*/
227
MapStream<K, V> filter(BiPredicate<? super K, ? super V> predicate);
228
229
/**
230
* Filter entries based on keys only
231
* @param predicate Test for keys
232
* @return MapStream with entries having matching keys
233
*/
234
MapStream<K, V> filterKey(Predicate<? super K> predicate);
235
236
/**
237
* Filter entries based on values only
238
* @param predicate Test for values
239
* @return MapStream with entries having matching values
240
*/
241
MapStream<K, V> filterValue(Predicate<? super V> predicate);
242
```
243
244
**Usage Example:**
245
246
```java
247
MapStream<String, Integer> scores = MapStream.of(scoresMap);
248
249
// Filter by key: students whose names start with 'A'
250
MapStream<String, Integer> aStudents = scores.filterKey(name -> name.startsWith("A"));
251
252
// Filter by value: high scores only
253
MapStream<String, Integer> highScores = scores.filterValue(score -> score >= 90);
254
255
// Filter by both: specific criteria for key-value pairs
256
MapStream<String, Integer> filtered = scores.filter((key, value) ->
257
key.length() > 3 && value > 85);
258
259
// Chain filtering operations
260
MapStream<String, Integer> qualified = scores
261
.filterKey(name -> !name.isEmpty())
262
.filterValue(score -> score >= 70)
263
.filter(entry -> !entry.getKey().equals("Test"));
264
```
265
266
### Sorting and Ordering
267
268
Sort MapStream entries by keys, values, or custom comparators.
269
270
```java { .api }
271
/**
272
* Sort entries using natural ordering of entries
273
* @return Sorted MapStream
274
*/
275
MapStream<K, V> sorted();
276
277
/**
278
* Sort entries using a custom comparator
279
* @param comparator Comparator for entries
280
* @return Sorted MapStream
281
*/
282
MapStream<K, V> sorted(Comparator<? super Map.Entry<K, V>> comparator);
283
```
284
285
**Usage Example:**
286
287
```java
288
MapStream<String, Integer> scores = MapStream.of(scoresMap);
289
290
// Sort by key (alphabetically)
291
MapStream<String, Integer> byName = scores.sorted(Map.Entry.comparingByKey());
292
293
// Sort by value (highest scores first)
294
MapStream<String, Integer> byScore = scores.sorted(
295
Map.Entry.<String, Integer>comparingByValue().reversed());
296
297
// Sort by custom criteria
298
MapStream<String, Integer> custom = scores.sorted((e1, e2) -> {
299
// First by score descending, then by name ascending
300
int scoreCompare = Integer.compare(e2.getValue(), e1.getValue());
301
return scoreCompare != 0 ? scoreCompare : e1.getKey().compareTo(e2.getKey());
302
});
303
```
304
305
### Limiting and Skipping
306
307
Limit or skip entries in the MapStream.
308
309
```java { .api }
310
/**
311
* Limit the MapStream to the first n entries
312
* @param maxSize Maximum number of entries
313
* @return Limited MapStream
314
*/
315
MapStream<K, V> limit(long maxSize);
316
317
/**
318
* Skip the first n entries
319
* @param n Number of entries to skip
320
* @return MapStream with skipped entries
321
*/
322
MapStream<K, V> skip(long n);
323
324
/**
325
* Take key-value pairs while predicate is true
326
* @param predicate Test for key-value pairs
327
* @return MapStream of consecutive pairs matching predicate
328
*/
329
MapStream<K, V> takeWhile(BiPredicate<? super K, ? super V> predicate);
330
331
/**
332
* Drop key-value pairs while predicate is true
333
* @param predicate Test for key-value pairs
334
* @return MapStream after dropping consecutive matching pairs
335
*/
336
MapStream<K, V> dropWhile(BiPredicate<? super K, ? super V> predicate);
337
```
338
339
**Usage Example:**
340
341
```java
342
MapStream<String, Integer> scores = MapStream.of(scoresMap);
343
344
// Get top 3 scores
345
MapStream<String, Integer> top3 = scores
346
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
347
.limit(3);
348
349
// Skip the highest score and get the next 5
350
MapStream<String, Integer> next5 = scores
351
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
352
.skip(1)
353
.limit(5);
354
355
// Take high scores until we find one below 80
356
MapStream<String, Integer> highScoresSequence = scores
357
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
358
.takeWhile(entry -> entry.getValue() >= 80);
359
```
360
361
### Reduction and Collection
362
363
Reduce MapStream to single values or collect into various data structures.
364
365
```java { .api }
366
/**
367
* Count the number of entries
368
* @return Number of entries in the stream
369
*/
370
long count();
371
372
/**
373
* Execute an action for each key-value pair
374
* @param consumer Action to perform on each key-value pair
375
*/
376
void forEach(BiConsumer<? super K, ? super V> consumer);
377
378
/**
379
* Test if any key-value pair matches the predicate
380
* @param predicate Test for key-value pairs
381
* @return true if any pair matches
382
*/
383
boolean anyMatch(BiPredicate<? super K, ? super V> predicate);
384
385
/**
386
* Test if all key-value pairs match the predicate
387
* @param predicate Test for key-value pairs
388
* @return true if all pairs match
389
*/
390
boolean allMatch(BiPredicate<? super K, ? super V> predicate);
391
392
/**
393
* Test if no key-value pairs match the predicate
394
* @param predicate Test for key-value pairs
395
* @return true if no pairs match
396
*/
397
boolean noneMatch(BiPredicate<? super K, ? super V> predicate);
398
399
/**
400
* Find the first entry
401
* @return Optional containing first entry, or empty if stream is empty
402
*/
403
Optional<Map.Entry<K, V>> findFirst();
404
405
/**
406
* Find any entry
407
* @return Optional containing any entry, or empty if stream is empty
408
*/
409
Optional<Map.Entry<K, V>> findAny();
410
411
/**
412
* Collect entries using a collector
413
* @param collector Collector to use
414
* @return Collected result
415
*/
416
<R, A> R collect(Collector<? super Map.Entry<K, V>, A, R> collector);
417
```
418
419
### Map Collection
420
421
Collect MapStream back into various Map implementations.
422
423
```java { .api }
424
/**
425
* Collect to a Map
426
* @return Map containing all entries
427
*/
428
static <K, V> Collector<Map.Entry<K, V>, ?, Map<K, V>> toMap();
429
430
/**
431
* Collect to a Map with merge function for duplicate keys
432
* @param mergeFunction Function to resolve duplicate keys
433
* @return Map containing all entries with duplicates resolved
434
*/
435
static <K, V> Collector<Map.Entry<K, V>, ?, Map<K, V>> toMap(BinaryOperator<V> mergeFunction);
436
437
/**
438
* Collect to a specific Map implementation
439
* @param mergeFunction Function to resolve duplicate keys
440
* @param mapSupplier Supplier for the Map implementation
441
* @return Map of specified type containing all entries
442
*/
443
static <K, V, M extends Map<K, V>> Collector<Map.Entry<K, V>, ?, M> toMap(
444
BinaryOperator<V> mergeFunction, Supplier<M> mapSupplier);
445
```
446
447
**Usage Example:**
448
449
```java
450
MapStream<String, Integer> scores = MapStream.of(scoresMap);
451
452
// Count entries
453
long totalStudents = scores.count();
454
455
// Check conditions
456
boolean hasHighScores = scores.anyMatch(entry -> entry.getValue() > 95);
457
boolean allPassed = scores.allMatch(entry -> entry.getValue() >= 60);
458
459
// Find specific entries
460
Optional<Map.Entry<String, Integer>> topScore = scores
461
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
462
.findFirst();
463
464
// Collect back to Map
465
Map<String, Integer> filteredScores = scores
466
.filterValue(score -> score >= 80)
467
.collect(MapStream.toMap());
468
469
// Collect to specific Map type
470
LinkedHashMap<String, Integer> orderedScores = scores
471
.sorted(Map.Entry.comparingByKey())
472
.collect(MapStream.toMap(
473
(v1, v2) -> v1, // merge function (shouldn't have duplicates)
474
LinkedHashMap::new));
475
476
// Collect with custom merge function
477
Map<String, Integer> merged = scores
478
.collect(MapStream.toMap(Integer::max)); // Keep maximum value for duplicate keys
479
```
480
481
### Side Effects
482
483
Execute side effects while processing the stream.
484
485
```java { .api }
486
/**
487
* Peek at key-value pairs without consuming them
488
* @param action BiConsumer to perform on each key-value pair
489
* @return MapStream with unchanged entries
490
*/
491
MapStream<K, V> peek(BiConsumer<? super K, ? super V> action);
492
```
493
494
**Usage Example:**
495
496
```java
497
MapStream<String, Integer> scores = MapStream.of(scoresMap);
498
499
// Debug processing pipeline
500
Map<String, String> grades = scores
501
.peek((key, value) -> System.out.println("Processing: " + key + "=" + value))
502
.filterValue(score -> score >= 70)
503
.peek((key, value) -> System.out.println("Passed filter: " + key + "=" + value))
504
.mapValue(score -> score >= 90 ? "A" : score >= 80 ? "B" : "C")
505
.peek((key, grade) -> System.out.println("Final grade: " + key + "=" + grade))
506
.collect(MapStream.toMap());
507
```
508
509
### Distinctness
510
511
Remove duplicate entries based on keys, values, or both.
512
513
```java { .api }
514
/**
515
* Remove duplicate entries (based on key-value pairs)
516
* @return MapStream with distinct entries
517
*/
518
MapStream<K, V> distinct();
519
```
520
521
**Usage Example:**
522
523
```java
524
// Remove exact duplicate entries
525
MapStream<String, Integer> uniqueEntries = scores.distinct();
526
527
// Custom distinctness can be achieved with collect and merge
528
Map<String, Integer> distinctByKey = scores
529
.collect(MapStream.toMap((v1, v2) -> v1)); // Keep first value for each key
530
```
531
532
## Complete Processing Example
533
534
```java
535
import aQute.bnd.stream.MapStream;
536
import java.util.*;
537
import java.util.stream.Collectors;
538
539
public class StudentGradeProcessor {
540
public Map<String, String> processGrades(Map<String, Integer> rawScores) {
541
return MapStream.of(rawScores)
542
// Filter out invalid entries
543
.filterKey(name -> name != null && !name.trim().isEmpty())
544
.filterValue(score -> score >= 0 && score <= 100)
545
546
// Normalize names
547
.mapKey(name -> name.trim().toLowerCase())
548
549
// Remove duplicates (keep highest score)
550
.collect(MapStream.toMap(Integer::max))
551
.let(map -> MapStream.of(map))
552
553
// Convert scores to letter grades
554
.mapValue(score -> {
555
if (score >= 90) return "A";
556
if (score >= 80) return "B";
557
if (score >= 70) return "C";
558
if (score >= 60) return "D";
559
return "F";
560
})
561
562
// Sort by grade (A first), then by name
563
.sorted((e1, e2) -> {
564
int gradeCompare = e1.getValue().compareTo(e2.getValue());
565
return gradeCompare != 0 ? gradeCompare : e1.getKey().compareTo(e2.getKey());
566
})
567
568
// Convert back to regular map
569
.collect(MapStream.toMap());
570
}
571
572
// Helper method for chaining
573
private static <T> T let(T value, Function<T, T> function) {
574
return function.apply(value);
575
}
576
}
577
```
578
579
## Integration with Java Streams
580
581
MapStream seamlessly integrates with regular Java 8 streams:
582
583
```java
584
// Convert to regular stream when needed
585
Stream<Map.Entry<String, Integer>> entryStream = mapStream.entries();
586
Stream<String> keyStream = mapStream.keys();
587
Stream<Integer> valueStream = mapStream.values();
588
589
// Create MapStream from regular streams
590
MapStream<String, Integer> fromStream = MapStream.of(
591
regularStream.collect(Collectors.toMap(keyMapper, valueMapper))
592
);
593
```
594
595
This provides the flexibility to use MapStream where it adds value and fall back to regular streams for operations that don't benefit from the key-value specialization.