A stream is a lazy pipeline of operations over a sequence of elements — declare what to compute, and the library does the looping.
Source (list.stream()) → Intermediate ops (filter, map…) — lazy → Terminal op (collect, sum…) — triggers work → Result
A first pipeline
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> words = List.of("stream", "java", "lambda", "api", "collector", "map");
List<String> result = words.stream()
.filter(w -> w.length() > 3) // keep long words
.map(String::toUpperCase) // transform
.sorted() // natural order
.toList(); // terminal (Java 16+)
System.out.println(result);
// the imperative equivalent
List<String> loop = new ArrayList<>();
for (String w : words) if (w.length() > 3) loop.add(w.toUpperCase());
Collections.sort(loop);
System.out.println(loop.equals(result));
}
}[COLLECTOR, JAVA, LAMBDA, STREAM]
trueCreating streams
| Source | Example |
|---|---|
| Collection | list.stream(), set.parallelStream() |
| Array | Arrays.stream(arr), Stream.of(1, 2, 3) |
| Numeric range | IntStream.range(0, 5) (end exclusive), rangeClosed(1, 5) |
| Generated | Stream.iterate(1, x -> x * 2), Stream.generate(supplier) — infinite, use limit |
| Bounded iterate (9+) | Stream.iterate(1, x -> x < 100, x -> x * 2) |
| Others | str.chars(), Files.lines(path), map.entrySet().stream(), Stream.empty() |
Intermediate operations (lazy, return a Stream)
| Operation | What it does | Stateful? |
|---|---|---|
filter(pred) | keep elements matching | No |
map(fn) / mapToInt(fn) | transform each element (1 → 1) | No |
flatMap(fn) | each element → a stream, flattened (1 → many) | No |
peek(action) | look at elements as they pass (debugging) | No |
distinct() | drop duplicates (uses equals/hashCode) | Yes |
sorted() / sorted(cmp) | sort — must see all elements first | Yes |
limit(n) / skip(n) | first n / drop first n (short-circuit limit) | Yes (count) |
takeWhile / dropWhile (9+) | prefix while predicate holds | No |
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3, 4), List.of(4, 5));
System.out.println(nested.stream().flatMap(List::stream).toList());
System.out.println(nested.stream().flatMap(List::stream).distinct().skip(1).limit(3).toList());
System.out.println(Stream.of("b", "a", "c").sorted(Comparator.reverseOrder()).toList());
System.out.println(Stream.iterate(1, x -> x * 2).limit(6).toList());
System.out.println(IntStream.rangeClosed(1, 10).takeWhile(i -> i < 5).boxed().toList());
System.out.println("hello".chars().filter(c -> c == 'l').count());
}
}[1, 2, 3, 4, 4, 5]
[2, 3, 4]
[c, b, a]
[1, 2, 4, 8, 16, 32]
[1, 2, 3, 4]
2Laziness: nothing runs until the terminal op
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
Stream<Integer> pipeline = Stream.of(1, 2, 3, 4, 5, 6)
.peek(x -> System.out.println("saw " + x))
.filter(x -> x % 2 == 0)
.map(x -> x * 10);
System.out.println("pipeline built, nothing printed yet");
Integer first = pipeline.findFirst().orElseThrow(); // terminal op
System.out.println("first = " + first);
}
}pipeline built, nothing printed yet
saw 1
saw 2
first = 20Terminal operations
| Operation | Returns | Notes |
|---|---|---|
forEach(action) | void | order not guaranteed on parallel streams (forEachOrdered) |
collect(collector) / toList() | a collection / value | see Collectors topic |
reduce(identity, op) | T | fold into one value |
count(), min(cmp), max(cmp) | long, Optional<T> | min/max are Optional — stream may be empty |
sum(), average(), summaryStatistics() | number / OptionalDouble | only on IntStream/LongStream/DoubleStream |
anyMatch, allMatch, noneMatch | boolean | short-circuit; allMatch on empty = true |
findFirst(), findAny() | Optional<T> | findAny is faster on parallel streams |
toArray() | array | toArray(String[]::new) for a typed array |
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<Integer> nums = List.of(4, 8, 15, 16, 23, 42);
System.out.println(nums.stream().reduce(0, Integer::sum));
System.out.println(nums.stream().reduce(Integer::max).get());
System.out.println(nums.stream().mapToInt(Integer::intValue).sum());
System.out.println(nums.stream().mapToInt(i -> i).average().getAsDouble());
System.out.println(nums.stream().max(Comparator.naturalOrder()).get());
System.out.println(nums.stream().filter(n -> n > 100).count());
System.out.println(nums.stream().anyMatch(n -> n % 2 == 1) + " " + nums.stream().allMatch(n -> n > 0));
IntSummaryStatistics st = nums.stream().mapToInt(i -> i).summaryStatistics();
System.out.println(st.getMin() + ".." + st.getMax() + " avg " + st.getAverage());
String[] arr = Stream.of("x", "y").toArray(String[]::new);
System.out.println(Arrays.toString(arr));
}
}108
42
108
18.0
42
0
true true
4..42 avg 18.0
[x, y]Primitive streams and boxing
IntStream, LongStream and DoubleStream avoid boxing and add sum, average, range. Convert with mapToInt / mapToObj / boxed().
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
System.out.println(IntStream.range(0, 5).sum());
System.out.println(IntStream.range(0, 5).boxed().toList());
System.out.println(IntStream.rangeClosed(1, 5).mapToObj(i -> "#" + i).collect(Collectors.joining(",")));
System.out.println(IntStream.of(3, 1, 2).map(i -> i * i).sorted().boxed().toList());
}
}10
[0, 1, 2, 3, 4]
#1,#2,#3,#4,#5
[1, 4, 9]Streams are single-use
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
Stream<String> s = Stream.of("a", "b");
System.out.println(s.count());
try {
s.count(); // reuse → exception
} catch (IllegalStateException e) {
System.out.println(e.getMessage());
}
}
}2
stream has already been operated upon or closedParallel streams — use with care
list.parallelStream()or.parallel()splits the work across the commonForkJoinPool(cores − 1 threads).Worth it only for large, CPU-bound, easily splittable data (
ArrayList, arrays, ranges) — notLinkedListor I/O.Lambdas must be stateless and non-interfering: never mutate a shared
ArrayListfromforEach— usecollect.reduceneeds an associative operation and a true identity (0for sum), or results change with the split.Order-dependent ops (
findFirst,limit,forEachOrdered) cost extra in parallel;findAny/unordered()are cheaper.
Intermediate vs terminal operations?
map vs flatMap?
Stateless vs stateful intermediate operations?
Can a stream be reused? Does it modify its source?