Lambdas and Streams
7 pages · ~5 min✓ Reviewed
Builds on File I/O and NIO. Next up: JVM and Memory.
Functional Interfaces, Lambdas and Method References
1. Functional interfaces
- One abstract method only; default, static, Object methods don't count
@FunctionalInterface: compiler enforces it;IntFunction,ToIntFunctionavoid boxing
| Type | Method | Shape |
|---|---|---|
Function<T,R> | apply | T→R |
Predicate<T> | test | T→boolean |
Consumer<T> | accept | T→void |
Supplier<T> | get | ()→T |
UnaryOperator<T> | apply | T→T |
BinaryOperator<T> | apply | (T,T)→T |
2. Lambda syntax
- Target type comes from context:
x -> x + 1fits either type below - Captures effectively final locals only
java
UnaryOperator<Integer> a = x -> x + 1; Function<Integer,Integer> b = x -> x + 1; BinaryOperator<Integer> c = (p, q) -> { return p + q; }; int base = 1; // base++ breaks capture
3. Method references
| Kind | Example | Receiver |
|---|---|---|
| Static | Integer::parseInt | none |
| Bound | System.out::println | System.out, fixed |
| Unbound | String::length | first arg s |
| Constructor | ArrayList::new | new object |
4. Interview point
- Why effectively final? Values are copied; a changing local would diverge
- Lambda vs anonymous class?
thisis the enclosing instance; lambda usesinvokedynamic, no extra class - Bound or unbound?
System.outfixed before the call;String::lengthtakes the receiver as first arg
The Stream Pipeline: Source, Intermediate, Terminal
1. Stream and Pipeline
- A stream is a one-shot view of a source
- Stores no data, never modifies the source
- Sourcecollection, array
- Intermediate0 or more, lazy
- Terminalexactly one
map, filter, reduce and Laziness
1. map, filter, flatMap, reduce
| Op | Takes | Result |
|---|---|---|
map | Function | 1:1 transform |
filter | Predicate | keeps true ones |
flatMap | Function to Stream | 1:N, List<List<T>> to Stream<T> |
reduce(identity, op)returns Treduce(op)returns Optional<T>- Op must be associative, identity neutral (parallel-safe)
Collectors: groupingBy, toMap and More
1. Collect to a Container
collect(Collectors.toList()/toSet())= mutable reduction- Stream
toList()(16+) is unmodifiable joining(", ", "[", "]")builds a String:[a, b, c]
2. groupingBy vs partitioningBy
groupingBy(classifier)→Map<K, List<T>>- A downstream collector changes the value
- Default map is an unordered
HashMap: don't rely on order
| Collector |
|---|
Optional and Common Stream Mistakes
1. Optional: Create and Consume
- Optional<T> = empty or one value; a return type for 'maybe no result'
of(x)NPE on null;ofNullable(x);empty()
| Method | Behaviour |
|---|---|
orElse(d) | eager: d built always |
orElseGet(s) | lazy: only if empty |
orElseThrow() | throws if empty |
ifPresent(c) | run if present |
Cheat Sheet: Lambdas, Streams, Collectors, Optional
1. Interfaces and Pipeline
| Type | Shape |
|---|---|
| Function | T→R |
| Predicate | T→boolean |
| Consumer | T→void |
| Supplier | ()→T |
| UnaryOperator | T→T |
| BinaryOperator | (T,T)→T |
- Sourcecollection, array
- Intermediatelazy: filter, map, flatMap, sorted, distinct, limit
- Terminalcollect, reduce, count, forEach, findFirst, anyMatch
Check yourself
What does this print?
- Nothing.
- No terminal op, so the lazy pipeline never runs.
Stream.of("a", "b") .peek(System.out::println) .map(String::toUpperCase);
What happens here, and how do you fix it?
- IllegalStateException: Duplicate key a (attempted merging values apple and avocado).
- Fix: pass a merge function,
(x, y) -> x.
Stream.of("apple", "avocado") .collect(Collectors.toMap(s -> s.charAt(0), s -> s));
Which call fails, and why?
- The forEach: IllegalStateException, stream already operated upon or closed.
count()was terminal, so build a new stream per use.
var s = Stream.of(1, 2).filter(n -> n > 0); s.count(); s.forEach(System.out::println);