Streams (course memo 1 & 2)
Java. Changing elements grouped in a collection tends to follow the same repeated shape of code. Streams are a template tool for that: you pick the steps to apply like building blocks, and describe how each element changes with a lambda expression.
Starting a Stream
List<String> things = List.of("table", "chair");
things.stream()
// ...
Stream.of("table", "chair")
// ...Start a stream via .stream() on a collection (e.g. List), or via the Stream class directly (Stream.of(...)).
Ending a Stream (terminal operation)
List<Integer> colours = List.of(1, 2, 3, 4, 5);
Optional<Integer> oNumber = colours.stream()
.filter(number -> number > 3)
.findFirst();
List<String> vowels = Stream.of("a", "e", "i")
.map(vowel -> vowel.toUpperCase())
.collect(Collectors.toList());The end of a stream is its terminal operation — every stream has exactly one. Starting with findFirst and collect.
Continuing a Stream (intermediate operations)
List<Integer> numbers = Stream.of(1, 3, 5)
.map(number -> number * 3)
.collect(Collectors.toList());
List<String> days = List.of("Monday", "Tuesday", "Wednesday", "Thursday", "Friday");
List<String> tDays = days.stream()
.filter(day -> day.startsWith("T"))
.collect(Collectors.toList());Each step of a stream is an intermediate operation — you can chain as many as you want, and each one returns another stream. Starting with map and filter.
Combining several steps
List<String> names = List.of("Rodrigo", "Laura", "Rosa", "Seth", "Lisa");
List<String> result = names.stream()
.filter(name -> name.endsWith("sa"))
.map(name -> name.replaceAll("a", "e"))
.map(name -> name.toUpperCase())
.filter(name -> name.startsWith("R"))
.collect(Collectors.toList());A stream: starts with .stream()/Stream.of(...), has zero or more intermediate operations, ends with exactly one terminal operation.
Lambda expressions
Stream operations that need extra behavior receive it as a lambda — a quick way to write a method.
int applyFormula(int number) {
return (number * number) - 10;
}
number -> (number * number) - 10 // same logic, as a lambdaA regular method has a name, return type, parameters, body. A lambda skips the name and return type (both inferred) and keeps only parameters + body.
Structure:
(param1, param2) -> {
System.out.println(param1);
System.out.println(param2);
}
(e1, e2) -> System.out.println(e1 + e2)
e1 -> System.out.println(e1)
() -> System.out.println("Hello!")- Parameters go in parentheses, comma-separated; you choose the names.
->separates parameters from body.- Multi-line body: curly braces, each statement ends with
;. - Single-line body: braces and
;can be dropped. - Single parameter: parentheses can be dropped. Zero parameters: parentheses are required (
()).
Method references
.map(vowel -> vowel.toUpperCase())
.map(String::toUpperCase) // equivalent, simplifiedUse a method reference (ClassName::methodName, no parentheses/parameters) when a lambda's whole body is just calling one existing method with no extra logic.
More intermediate operations
skip(n)— ignore the firstnelements.limit(n)— stop after exactlynelements have passed through.sorted()— sort by natural/default order.sorted(Comparator)— sort by custom rules.
// Skip the first two
List<Integer> numbers = Stream.of(1, 2, 3, 4, 5).skip(2).collect(Collectors.toList());
// Keep only the first three
List<Integer> numbers = Stream.of(1, 2, 3, 4, 5).limit(3).collect(Collectors.toList());
// Sort, default order
List<Integer> numbers = Stream.of(4, 2, 1, 5, 3).sorted().collect(Collectors.toList());
// Sort, custom order
List<Integer> numbers = Stream.of(4, 2, 1, 5, 3)
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());Comparators
.sorted(Comparator.naturalOrder())
.sorted(Comparator.reverseOrder())
.sorted(Comparator.comparing(Pet::getName))
.sorted(Comparator.comparing(Pet::getName).reversed())
.sorted(Comparator.comparing(Pet::getName).thenComparing(Pet::getOwner))naturalOrder()/reverseOrder()— delegate to each element's owncompareTo.comparing(getter)— sort by one specific attribute (via a method reference)..reversed()— flips a comparator..thenComparing(getter)— tiebreaker, applied when the first comparison is equal.
List<Pet> pets = Stream.of(
new Pet("Mittens", "Tom"),
new Pet("Cooper", "Lucy"))
.sorted(Comparator.comparing(Pet::getName)
.thenComparing(Pet::getOwner))
.collect(Collectors.toList());Collectors
List<Integer> numbers = Stream.of(1, 2, 3).collect(Collectors.toList());
String text = Stream.of("1", "2", "3").collect(Collectors.joining()); // "123"
String delimited = Stream.of("1", "2", "3").collect(Collectors.joining(", ")); // "1, 2, 3"toList()— collect elements into aList.joining()— concatenate strings with no delimiter.joining(delimiter)— concatenate with a delimiter between elements (not at the start/end). Many otherCollectorsmethods exist for different groupings.
More terminal operations
count()— number of elements reaching this point.forEach(lambda)— perform a side effect per element.reduce(lambda)— combine all elements into one via the given operation (e.g. sum); returnsOptionalsince there might be no elements.reduce(start, lambda)— same, but returnsstartif the stream is empty, and usesstartas the seed value.
long count = Stream.of(5, 2, 3, 1, 4).count();
Stream.of(5, 2, 3, 1, 4).forEach(e -> System.out.println(e));
Optional<Integer> oSum = Stream.of(1, 2, 3).reduce(Integer::sum);
Integer sum = Stream.of(1, 2, 3).reduce(0, Integer::sum);