Java Streams
Java streams let you process collections in a declarative style: you describe what you want (filter these, transform those, add them up) rather than writing the loop by hand. The Stream API was added in Java 8 and works hand in hand with lambdas.
How a Stream Pipeline Works
- Source: a collection, array or generator, for example list.stream().
- Intermediate operations: filter, map, sorted, distinct, limit. They are lazy and return a new stream.
- Terminal operation: toList, forEach, count, sum, collect, reduce. It runs the pipeline and produces a result.
- Streams do not modify the original collection and can be used only once.
filter, map and toList
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names = List.of("sana", "ahmed", "li", "fatima", "bo");
List<String> result = names.stream()
.filter(n -> n.length() > 2)
.map(n -> n.substring(0, 1).toUpperCase() + n.substring(1))
.sorted()
.toList();
System.out.println(result);
System.out.println(names.stream().filter(n -> n.length() <= 2).count());
}
}[Ahmed, Fatima, Sana] 2
toList() (Java 16+) returns an unmodifiable list. In older code you will see .collect(Collectors.toList()).
Numbers: sum, average and reduce
import java.util.List;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
List<Integer> prices = List.of(120, 80, 45, 300);
int total = prices.stream().mapToInt(Integer::intValue).sum();
double avg = prices.stream().mapToInt(Integer::intValue).average().orElse(0);
int product = IntStream.rangeClosed(1, 5).reduce(1, (a, b) -> a * b);
System.out.println(total);
System.out.println(avg);
System.out.println(product);
}
}545 136.25 120
Grouping with Collectors
Collectors turn a stream into maps, joined strings and summaries. groupingBy is especially powerful for reports.
import java.util.*;
import java.util.stream.Collectors;
record Order(String customer, String city, double amount) {}
public class Main {
public static void main(String[] args) {
List<Order> orders = List.of(
new Order("Ali", "Lahore", 250),
new Order("Mia", "London", 400),
new Order("Omar", "Lahore", 150),
new Order("Eva", "London", 100)
);
Map<String, Double> byCity = orders.stream()
.collect(Collectors.groupingBy(Order::city, TreeMap::new,
Collectors.summingDouble(Order::amount)));
System.out.println(byCity);
String big = orders.stream()
.filter(o -> o.amount() >= 200)
.map(Order::customer)
.collect(Collectors.joining(", ", "Big spenders: ", ""));
System.out.println(big);
Optional<Order> top = orders.stream().max(Comparator.comparingDouble(Order::amount));
System.out.println(top.map(Order::customer).orElse("none"));
}
}{Lahore=400.0, London=500.0}
Big spenders: Ali, Mia
MiaStreams vs Loops
Streams shine for transforming and summarising data and read almost like a sentence. Plain loops are still better when you need early exits with complex logic, index manipulation, or checked exceptions. Both are valid Java; choose whichever is clearer.
Frequently asked questions
Are Java streams faster than for loops?
Not usually. For simple work a loop is often slightly faster. Streams are chosen for readability, and parallel streams can help only with large, CPU-heavy workloads.
What is the difference between map and flatMap?
map turns each element into exactly one new element. flatMap turns each element into a stream and flattens all of them into a single stream, for example from a list of lists to one list.
Can I reuse a Java stream?
No. After a terminal operation the stream is consumed, and using it again throws IllegalStateException. Create a new stream from the source instead.