The Java Stream API (introduced in Java 8) revolutionized how we work with collections and data processing. It allows you to process data declaratively using functional programming concepts.
๐ What Is a Stream?
A Stream is not a data structure but a sequence of elements supporting sequential and parallel aggregate operations.
โ Features:
- Functional-style operations
- Lazy execution
- Can be parallelized
- Non-destructive (original collection is not modified)
๐ฐ Stream API Workflow
collection.stream() // 1. Create a Stream
.filter(...) // 2. Intermediate Operation
.map(...) // 3. Intermediate Operation
.collect(...)// 4. Terminal Operation
1๏ธโฃ Creating Streams
From Collections:
List<String> names = List.of("John", "Jane", "Jack");
Stream<String> stream = names.stream();
From Arrays:
String[] array = {"A", "B", "C"};
Stream<String> stream = Arrays.stream(array);
Infinite Stream (using Stream.iterate
and generate
):
Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2);
Stream<Double> randoms = Stream.generate(Math::random);
2๏ธโฃ Intermediate Operations
These return a new Stream and are lazy.
๐ filter(Predicate<T>)
List<String> names = List.of("Alice", "Bob", "Anna");
List<String> result = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList()); // [Alice, Anna]
๐ map(Function<T,R>)
List<String> names = List.of("Alice", "Bob");
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList()); // [5, 3]
๐ข flatMap(Function<T, Stream<R>>)
:
List<List<String>> list = List.of(List.of("A", "B"), List.of("C", "D"));
List<String> flat = list.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList()); // [A, B, C, D]
๐งน distinct()
, sorted()
, limit(n)
, skip(n)
List<Integer> nums = List.of(3, 1, 2, 2, 3);
List<Integer> result = nums.stream()
.distinct()
.sorted()
.limit(2)
.collect(Collectors.toList()); // [1, 2]
3๏ธโฃ Terminal Operations
These end the stream pipeline.
๐ฅ collect(Collectors.toList())
, toSet()
, joining()
, groupingBy()
String joined = List.of("A", "B", "C").stream()
.collect(Collectors.joining(", ")); // "A, B, C"
Map<Integer, List<String>> grouped = List.of("apple", "banana", "avocado").stream()
.collect(Collectors.groupingBy(String::length));
โ reduce
โ Reducing to a single value:
int sum = List.of(1, 2, 3).stream()
.reduce(0, Integer::sum); // 6
๐งพ forEach
List.of("A", "B", "C").stream()
.forEach(System.out::println);
4๏ธโฃ Optional + Streams
Optional<String> first = List.of("a", "b", "c").stream()
.filter(s -> s.equals("b"))
.findFirst();
5๏ธโฃ Primitive Streams
Use IntStream
, LongStream
, DoubleStream
for performance:
int sum = IntStream.of(1, 2, 3).sum(); // 6
6๏ธโฃ Parallel Streams
List<String> data = List.of("A", "B", "C");
data.parallelStream()
.forEach(System.out::println); // May not be ordered
๐ฆ Real-World Example: Filtering and Grouping Products
class Product {
String name;
String category;
double price;
// constructor, getters
}
List<Product> products = List.of(
new Product("iPhone", "Electronics", 999.0),
new Product("Laptop", "Electronics", 1299.0),
new Product("Book", "Education", 29.99)
);
// Filter expensive products and group by category
Map<String, List<Product>> result = products.stream()
.filter(p -> p.getPrice() > 100)
.collect(Collectors.groupingBy(Product::getCategory));
๐ง Advanced Use Case: Map of Highest Priced Product per Category
Map<String, Optional<Product>> maxByCategory = products.stream()
.collect(Collectors.groupingBy(
Product::getCategory,
Collectors.maxBy(Comparator.comparing(Product::getPrice))
));
๐งช Testing Streams
@Test
void testStreamFilter() {
List<String> names = List.of("Alice", "Bob", "Anna");
List<String> filtered = names.stream()
.filter(n -> n.startsWith("A"))
.collect(Collectors.toList());
assertEquals(List.of("Alice", "Anna"), filtered);
}
โ ๏ธ Common Pitfalls
- Streams can't be reused:
Stream<String> stream = names.stream();
stream.forEach(...); // OK
stream.forEach(...); // IllegalStateException
Order is lost in
parallelStream
unless you use.forEachOrdered()
.Beware of side effects in lambdas (avoid modifying external state).
๐งฉ Conclusion
Java Stream API empowers developers to write concise, expressive, and readable code for collection processing. From filtering and mapping to grouping and reducing, Streams transform your approach to data manipulation.
๐ References
- Official Java Stream Docs
- [Effective Java by Joshua Bloch]
- Baeldung Stream API Guide
Top comments (0)