DEV Community

DevCorner2
DevCorner2

Posted on

๐Ÿ”ฅ Mastering Java Stream API: The Ultimate Guide with Real-World Code Examples

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
Enter fullscreen mode Exit fullscreen mode

1๏ธโƒฃ Creating Streams

From Collections:

List<String> names = List.of("John", "Jane", "Jack");
Stream<String> stream = names.stream();
Enter fullscreen mode Exit fullscreen mode

From Arrays:

String[] array = {"A", "B", "C"};
Stream<String> stream = Arrays.stream(array);
Enter fullscreen mode Exit fullscreen mode

Infinite Stream (using Stream.iterate and generate):

Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2);
Stream<Double> randoms = Stream.generate(Math::random);
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

๐Ÿ”„ map(Function<T,R>)

List<String> names = List.of("Alice", "Bob");
List<Integer> nameLengths = names.stream()
    .map(String::length)
    .collect(Collectors.toList()); // [5, 3]
Enter fullscreen mode Exit fullscreen mode

๐ŸŽข 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]
Enter fullscreen mode Exit fullscreen mode

๐Ÿงน 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]
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode
Map<Integer, List<String>> grouped = List.of("apple", "banana", "avocado").stream()
    .collect(Collectors.groupingBy(String::length));
Enter fullscreen mode Exit fullscreen mode

โž• reduce โ€“ Reducing to a single value:

int sum = List.of(1, 2, 3).stream()
    .reduce(0, Integer::sum); // 6
Enter fullscreen mode Exit fullscreen mode

๐Ÿงพ forEach

List.of("A", "B", "C").stream()
    .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

4๏ธโƒฃ Optional + Streams

Optional<String> first = List.of("a", "b", "c").stream()
    .filter(s -> s.equals("b"))
    .findFirst();
Enter fullscreen mode Exit fullscreen mode

5๏ธโƒฃ Primitive Streams

Use IntStream, LongStream, DoubleStream for performance:

int sum = IntStream.of(1, 2, 3).sum(); // 6
Enter fullscreen mode Exit fullscreen mode

6๏ธโƒฃ Parallel Streams

List<String> data = List.of("A", "B", "C");
data.parallelStream()
    .forEach(System.out::println); // May not be ordered
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ฆ Real-World Example: Filtering and Grouping Products

class Product {
    String name;
    String category;
    double price;
    // constructor, getters
}
Enter fullscreen mode Exit fullscreen mode
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));
Enter fullscreen mode Exit fullscreen mode

๐Ÿง  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))
    ));
Enter fullscreen mode Exit fullscreen mode

๐Ÿงช 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);
}
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ Common Pitfalls

  1. Streams can't be reused:
   Stream<String> stream = names.stream();
   stream.forEach(...); // OK
   stream.forEach(...); // IllegalStateException
Enter fullscreen mode Exit fullscreen mode
  1. Order is lost in parallelStream unless you use .forEachOrdered().

  2. 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

Top comments (0)