Skip to main content

Command Palette

Search for a command to run...

Understanding Streams in Java

Published
•5 min read•View as Markdown
Understanding Streams in Java
A

Fullstack Software Engineer.

Introduction to Java Streams

Java Streams, introduced in Java 8, are a powerful and expressive tool for processing sequences of elements, unlike collections, which store and group data, streams are designed to process data declaratively, providing a high-level abstraction for operations such as filtering, mapping, and reducing.

What are Streams?

Streams are sequences of elements that support various operations to process data. A stream does not store data; instead, it conveys elements from a source (such as a collection) through a pipeline of operations. Streams allow for complex data processing while maintaining a clean and readable code structure.

Streams vs Collections

  • Collections:

    • Data storage mechanism.

    • Can be iterated directly.

    • Eager in nature (process elements as they are added).

  • Streams:

    • Data processing mechanism.

    • Cannot be iterated directly.

    • Lazy in nature (process elements on-demand).

Stream Operations

Stream operations are classified into intermediate and terminal operations.

Intermediate Operations:

  • Return a new stream.

  • Lazy and allow for pipeline construction.

  • Examples: filter(), map(), sorted().

Terminal Operations:

  • Produce a result or side effect.

  • Trigger the processing of the stream pipeline.

  • Examples: collect(), forEach(), reduce().

Benefits and Pitfalls of Streams

Benefits:

  • Declarative and readable code.

  • Easy parallelization.

  • Improved performance for large datasets.

Pitfalls:

  • Learning curve for developers new to functional programming.

  • Overhead of creating streams.

  • Misuse can lead to performance degradation.

Types of Streams

  1. Stream<T>: Generic stream.

  2. IntStream, LongStream, DoubleStream: Streams for primitive types.

  3. ParallelStream: Stream that supports parallel processing.

Examples

Stream<T> Examples

  1. Basic Filtering:

     List<String> names = Arrays.asList("John", "Jane", "Jack", "Doe");
     List<String> filteredNames = names.stream()
                                       .filter(name -> name.startsWith("J"))
                                       .collect(Collectors.toList());
     System.out.println(filteredNames); // [John, Jane, Jack]
    
    • names.stream() creates a stream from the list of names.

    • filter(name -> name.startsWith("J")) filters the stream to include only names that start with "J".

    • collect(Collectors.toList()) collects the filtered names into a list.

  2. Mapping:

     List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
     List<Integer> squares = numbers.stream()
                                    .map(n -> n * n)
                                    .collect(Collectors.toList());
     System.out.println(squares); // [1, 4, 9, 16, 25]
    
    • numbers.stream() creates a stream from the list of numbers.

    • map(n -> n * n) maps each number to its square.

    • collect(Collectors.toList()) collects the squared numbers into a list.

Primitive Stream Examples

  1. IntStream Example:

     IntStream.range(1, 10)
              .filter(n -> n % 2 == 0)
              .forEach(System.out::println); // 2 4 6 8
    
    • IntStream.range(1, 10) creates an IntStream from 1 to 9.

    • filter(n -> n % 2 == 0) filters the stream to include only even numbers.

    • forEach(System.out::println) prints each number.

  2. DoubleStream Example:

     DoubleStream.of(1.0, 2.5, 3.6, 4.8)
                 .average()
                 .ifPresent(System.out::println); // 3.0
    
    • DoubleStream.of(1.0, 2.5, 3.6, 4.8) creates a DoubleStream with given values.

    • average() calculates the average of the stream.

    • ifPresent(System.out::println) prints the average if it is present.

Parallel Stream Examples

  1. Basic Parallel Stream:

     List<String> words = Arrays.asList("one", "two", "three", "four");
     words.parallelStream()
          .forEach(System.out::println);
    
    • words.parallelStream() creates a parallel stream from the list of words.

    • forEach(System.out::println) prints each word in parallel.

  2. Parallel Stream with Reduction:

     List<Integer> values = Arrays.asList(1, 2, 3, 4, 5);
     int sum = values.parallelStream()
                     .reduce(0, Integer::sum);
     System.out.println(sum); // 15
    
    • values.parallelStream() creates a parallel stream from the list of values.

    • reduce(0, Integer::sum) reduces the stream to a sum, starting with 0.

    • System.out.println(sum) prints the sum.

Complex Stream Examples

  1. Grouping and Counting:

     List<String> items = Arrays.asList("apple", "banana", "apple", "orange", "banana", "apple");
     Map<String, Long> itemCount = items.stream()
                                        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
     System.out.println(itemCount); // {banana=2, orange=1, apple=3}
    
    • items.stream() creates a stream from the list of items.

    • collect(Collectors.groupingBy(Function.identity(), Collectors.counting())) groups the items by their identity and counts them.

    • System.out.println(itemCount) prints the item count map.

  2. Nested Streams:

     List<List<String>> nestedList = Arrays.asList(
         Arrays.asList("a", "b", "c"),
         Arrays.asList("d", "e", "f"),
         Arrays.asList("g", "h", "i")
     );
     List<String> flatList = nestedList.stream()
                                       .flatMap(List::stream)
                                       .collect(Collectors.toList());
     System.out.println(flatList); // [a, b, c, d, e, f, g, h, i]
    
    • nestedList.stream() creates a stream from the list of lists.

    • flatMap(List::stream) flattens the nested lists into a single stream.

    • collect(Collectors.toList()) collects the flattened elements into a list.

    • System.out.println(flatList) prints the flattened list.

  3. Stream of Objects:

     class Person {
         String name;
         int age;
    
         Person(String name, int age) {
             this.name = name;
             this.age = age;
         }
     }
    
     List<Person> people = Arrays.asList(
         new Person("John", 25),
         new Person("Jane", 30),
         new Person("Jack", 20)
     );
    
     List<String> names = people.stream()
                                .map(person -> person.name)
                                .collect(Collectors.toList());
     System.out.println(names); // [John, Jane, Jack]
    
    • people.stream() creates a stream from the list of people.

    • map(person -> person.name) maps each person to their name.

    • collect(Collectors.toList()) collects the names into a list.

    • System.out.println(names) prints the list of names.

Examples Without Streams

  1. Filtering Without Streams:

     List<String> names = Arrays.asList("John", "Jane", "Jack", "Doe");
     List<String> filteredNames = new ArrayList<>();
     for (String name : names) {
         if (name.startsWith("J")) {
             filteredNames.add(name);
         }
     }
     System.out.println(filteredNames); // [John, Jane, Jack]
    
    • Iterates through the list of names.

    • Checks if each name starts with "J".

    • Adds the matching names to a new list.

    • Prints the filtered list.Mapping Without Streams:

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    List<Integer> squares = new ArrayList<>();
    for (Integer number : numbers) {
        squares.add(number * number);
    }
    System.out.println(squares); // [1, 4, 9, 16, 25]
  • Iterates through the list of numbers.

  • Squares each number.

  • Adds the squared numbers to a new list.

  • Prints the list of squares.

Examples Using Streams

  1. Filtering With Streams:

     List<String> names = Arrays.asList("John", "Jane", "Jack", "Doe");
     List<String> filteredNames = names.stream()
                                       .filter(name -> name.startsWith("J"))
                                       .collect(Collectors.toList());
     System.out.println(filteredNames); // [John, Jane, Jack]
    
    • Creates a stream from the list of names.

    • Filters the stream to include only names that start with "J".

    • Collects the filtered names into a list.

    • Prints the filtered list### Understanding Java Streams

  2. Mapping With Streams:

     List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
     List<Integer> squares = numbers.stream()
                                    .map(n -> n * n)
                                    .collect(Collectors.toList());
     System.out.println(squares); // [1, 4, 9, 16, 25]
    
    • Creates a stream from the list of numbers.

    • Maps each number to its square.

    • Collects and prints the squares.

By understanding and utilizing Java Streams, developers can write more concise, readable, and efficient code, enhancing their ability to process data in a functional and declarative manner.

More from this blog

Andre Bento's Blog

26 posts