1) What is a Stream?
- A Stream is a sequence of elements supporting functional-style operations (map, filter, reduce
- It is not a data structure; it doesn’t store elements.
- It’s lazy: intermediate operations don’t run until a terminal operation triggers evaluation.
- It’s once-use: a stream can be consumed only once.
Example:
List<Integer> nums = List.of(1,2,3,4,5);
int sum2 = nums.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.reduce(0, Integer::sum);
2) Creating Streams
Stream<String> s1 = List.of("a","b","c").stream();
IntStream s2 = Arrays.stream(new int[]{1,2,3});
Stream<String> s3 = Stream.of("x","y","z");
Stream<Double> randoms = Stream.generate(Math::random).limit(5);
IntStream range = IntStream.range(0, 10);
3) Intermediate Operations (lazy)
filter, map, flatMap, distinct, sorted, peek, limit, skip
Example:
List<String> result = names.stream()
.filter(n -> n.length() <= 3)
.map(String::toUpperCase)
.distinct()
.sorted()
.toList();
4) Terminal Operations
forEach, collect, reduce, min, max, count, anyMatch, allMatch, findFirst
Example:
Optional<String> longest =
names.stream().max(Comparator.comparingInt(String::length));
5) Collectors
Collectors.toList(), toSet(), joining(","), summarizingInt, groupingBy, partitioningBy
Example:
Map<Integer, List<String>> byLen =
names.stream().collect(Collectors.groupingBy(String::length));
6) reduce()
int prod = IntStream.of(1,2,3,4).reduce(1, (a,b) -> a*b);
7) Optional + Streams
Optional<String> maybeName = names.stream().findFirst();
String first = maybeName.orElse("N/A");
8) Primitive Streams
double avgLen = names.stream()
.mapToInt(String::length)
.average()
.orElse(0.0);
9) Sorting & Comparators
List<String> sorted = names.stream()
.sorted(Comparator.comparingInt(String::length)
.thenComparing(Comparator.naturalOrder()))
.toList();
10) Files & IO with Streams
try (Stream<String> lines = Files.lines(Path.of("log.txt"))) {
Map<String, Long> freq = lines
.flatMap(line -> Arrays.stream(line.split("\W+")))
.filter(w -> !w.isBlank())
.map(String::toLowerCase)
.collect(Collectors.groupingBy(w -> w, Collectors.counting()));
}
11) Handling Checked Exceptions
@FunctionalInterface
interface ThrowingFunction<T,R> {
R apply(T t) throws Exception;
}
static <T,R> Function<T,R> sneaky(ThrowingFunction<T,R> f) {
return t -> {
try { return f.apply(t); }
catch (Exception e) { throw new RuntimeException(e); }
};
}
List<String> contents = files.stream()
.map(sneaky(path -> Files.readString(Path.of(path))))
.toList();
12) Parallel Streams
long count =
LongStream.rangeClosed(1, 10_000_000)
.parallel()
.filter(n -> n % 2 == 0)
.count();
13) Stateful vs Stateless Operations
Stateless: map, filter, flatMap
Stateful: sorted, distinct, limit
14) Common Patterns
Top-K by score:
record Player(String name, int score) {}
List<Player> top3 = players.stream()
.sorted(Comparator.comparingInt(Player::score).reversed())
.limit(3)
.toList();
Multi-key grouping:
record Txn(String user, String city, double amount) {}
Map<String, Map<String, Double>> totals =
txns.stream().collect(Collectors.groupingBy(
Txn::user,
Collectors.groupingBy(
Txn::city,
Collectors.summingDouble(Txn::amount)
)
));
15) Performance Tips & Pitfalls
- Prefer primitive streams to avoid boxing
- Combine operations to keep pipelines short
- Never mutate external state inside map/filter
- Use toList() (JDK 16+)
16) Testing Streams
static List<String> normalize(List<String> in) {
return in.stream().filter(s -> !s.isBlank()).map(String::trim).toList();
}
17) Mini Projects (practice)
1) Word frequency from file
2) CSV to object grouping
3) Log analyzer (unique visitors per hour)
4) Options P&L simulator
18) Cheat Sheet
// Create
list.stream(); Stream.of(x,y); IntStream.range(a,b);
// Ops
filter(p), map(f), flatMap(f), distinct(), sorted(c), peek(a), limit(n), skip(n)
// Terminal
forEach(a), collect(c), reduce(id,acc,comb), min/max, count
// Collectors
toList(), toSet(), toMap(k,v,merge), joining(delim),
groupingBy(key, downstream), partitioningBy(pred),
// Optional
findFirst()/findAny() -> Optional<T>
// Primitive
mapToInt/Long/Double, average(), sum()
// Files
Files.lines(path)