Streams process collections declaratively — you describe what you want, not the loop that gets it.
Java
import java.util.List;
import java.util.stream.Collectors;
List<String> books = List.of("Muna Madan", "Palpasa Cafe", "Seto Dharti");
List<String> result = books.stream()
.filter(b -> b.length() > 10)
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
System.out.println(result);The shape of a pipeline
- Source —
list.stream(),Arrays.stream(arr),Stream.of(...) - Intermediate operations — lazy, return a new stream
- Terminal operation — triggers execution, produces a result
Nothing runs until the terminal operation. A pipeline without one does nothing at all.
Intermediate operations
Java
.filter(x -> x > 10) // keep matching
.map(x -> x * 2) // transform each
.flatMap(List::stream) // flatten nested collections
.distinct() // remove duplicates
.sorted() // natural order
.sorted(Comparator.comparing(Book::getPrice))
.limit(10) // first 10
.skip(5) // drop first 5
.peek(System.out::println) // debugging onlyTerminal operations
Java
.collect(Collectors.toList())
.toList() // Java 16+, immutable
.forEach(System.out::println)
.count()
.anyMatch(x -> x > 100)
.allMatch(x -> x > 0)
.noneMatch(x -> x < 0)
.findFirst() // Optional
.reduce(0, Integer::sum)Numeric streams
Java
List<Integer> nums = List.of(5, 2, 9, 1);
int sum = nums.stream().mapToInt(Integer::intValue).sum();
OptionalDouble avg = nums.stream().mapToInt(Integer::intValue).average();
IntSummaryStatistics st = nums.stream().mapToInt(Integer::intValue).summaryStatistics();
System.out.println(st.getMax() + " " + st.getMin() + " " + st.getAverage());Grouping and joining
Java
Map<String, List<Book>> byAuthor = books.stream()
.collect(Collectors.groupingBy(Book::getAuthor));
Map<String, Long> countByAuthor = books.stream()
.collect(Collectors.groupingBy(Book::getAuthor, Collectors.counting()));
String titles = books.stream()
.map(Book::getTitle)
.collect(Collectors.joining(", "));When not to use streams
A stream is not automatically faster than a loop, and a long pipeline can
be harder to debug. For a simple iteration with side effects, a plain
for loop is clearer. Use streams where they genuinely shorten
filter/map/reduce logic.
Also: a stream can be consumed once. Reusing one throws
IllegalStateException.