All about Java Streams — a quick hands-on introduction

therealdumbprogrammer

TheCodeAlchemist

Posted on March 14, 2023

All about Java Streams — a quick hands-on introduction

Java 8 added great features to the Java language. Out of them, two stand out — Lambdas and Streams.
You can find all the video links here. This is a very detailed and hands-on playlist.

What is a Stream?

See this where I explain this in details.

In simple words — a stream is simply a sequence of optionally ordered values.

That’s it! What else?

Oh yes, before I forget — Stream doesn’t store the data, their purpose is to process the data with the help of pipelines that a programmer would write/set.

And, Streams are lazy that means pipeline is not executed until we call a terminal operation.

What do you mean by pipeline?

You can see a small demo here

Streams follow a declarative syntax, similar to a SQL query which is close to the problem statement. You write what to do rather than how to do it.

Additionally, we can chain multiple operations where each method represents a step, and this whole code is called a stream pipeline. See the example below and I’m sure you will appreciate the simplicity and clarity if you compare this with traditional Java code.

int sum = widgets.stream()
             .filter(w -> w.getColor() ==RED)
             .mapToInt(w -> w.getWeight())
             .sum();
Enter fullscreen mode Exit fullscreen mode

Stream Operations

Stream supports many methods. On a very high level, they can be categorized as below.

Stream Methods

Creating a Stream

See the demo here — part1 and part2

There are many ways to generate a Stream. We’ll be focusing on important ones.

Via Static factory methods, Stream.of(..) variants

// 1. static factory methods
System.out.println("Stream factory methods---------");

//Creating a stream of single element i.e. Hello
Stream.of("Hello").forEach(System.out::println);

System.out.println("------------------------");

//Creating a stream of multiple elemtns
Stream.of("Apple", "Banana", "Grapes").forEach(System.out::println);

System.out.println("------------------------");

//When source is null
Stream.ofNullable(null).forEach(System.out::println);

System.out.println("------------------------");
Enter fullscreen mode Exit fullscreen mode

From Arrays using Arrays.stream(..) method

// 2. From Arrays
System.out.println("From arrays--------------------");
String[] names = {"Mike", "John", "Tom"};
Arrays.stream(names).forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

From collections using stream() method

Most of the collections have a stream() method that can be used to create a new stream.

// 3. From Collection
System.out.println("From collections---------------");

List<String> languages = List.of("Java", "Python");
languages.stream().forEach(System.out::println); //by calling stream()
Enter fullscreen mode Exit fullscreen mode

From Files

IO API has been enhanced to return streams. Here’s an example, where we’re reading a file using Stream.

// 4. Files
System.out.println("From Files---------------------");
Files.readAllLines(Paths.get("src\\dummy.txt"))
     .stream()
     .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Using map() and flatMap()

See this demo to understand how to use these methods.

map() is used to transform an object or value. For instance, transform a stream of Movie objects to a stream of movie titles.

flatMap() is used when the value/object we’re trying to transform is itself a collection/array so we need to flatten the object first in order to get the individual items from that collection/array.

public class StreamDemo {
    static List<Movie> MOVIES = List.of(
            new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
            new Movie(3, "Matrix", "Wachowski", "Neo", 10, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
            new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
    );
    public static void main(String[] args) {
        MOVIES.stream()
                .map(Movie::rating)
                .forEach(System.out::println);

        MOVIES.stream()
                .flatMap(movie -> Arrays.stream(movie.sideCharacters()))
                .forEach(System.out::println);

    }

    record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters) {    }
}
Enter fullscreen mode Exit fullscreen mode

Using filter()

See the filter demo

Filters are used to apply a predicate to filter the records.

MOVIES.stream()
   .filter(movie -> movie.releaseYear() > 2000)
   .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Using peek() to debug

See the peek demo

Just like print statements but for Streams.

MOVIES.stream()
      .peek(movie -> {
          System.out.println("Before Filter:: "+movie);
      })
      .filter(movie -> movie.releaseYear() > 2000)
      .peek(movie -> {
          System.out.println("After Filter:: "+movie);
      })
      .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Using limit() and skip()

See the demo here

limit() is used to truncate the Stream while skip() can be used to skip/jump stream by n elements.

public class StreamDemo {
    static List<Movie> MOVIES = List.of(
            new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
            new Movie(3, "Matrix", "Wachowski", "Neo", 10, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
            new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
    );
    public static void main(String[] args) {
        MOVIES.stream()
                .limit(2) //will print first two but last movie in the list will be truncated
                .forEach(System.out::println);

        MOVIES.stream()
                .skip(2) //will print last movie with id 1
                .forEach(System.out::println);


    }

    record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters) {    }
}
Enter fullscreen mode Exit fullscreen mode

Using sorted()

See the demo here

sorted() is used to sort the stream elements.

public class StreamDemo {
    static List<Movie> MOVIES = List.of(
            new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
            new Movie(3, "Matrix", "Wachowski", "Neo", 10, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
            new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
    );
    public static void main(String[] args) {
        MOVIES.stream()
                .sorted(Comparator.comparingInt(Movie::id)) //to sort on the basis of an int property i.e. movieID
                .forEach(System.out::println);

        MOVIES.stream()
                .sorted(Comparator.comparing(Movie::title)) //to sort on the basis of title, will apply natural ordering of String
                .forEach(System.out::println);


    }

    record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters) {    }
}
Enter fullscreen mode Exit fullscreen mode

Using distinct()

See the demo here

distinct() is used to process distinct elements by avoiding duplicates.

public class StreamDemo {
    static List<Movie> MOVIES = List.of(
            new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
            new Movie(3, "Matrix", "Wachowski", "Neo", 10, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
            new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"}),
            //added a duplicate movie
            new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
    );
    public static void main(String[] args) {
        MOVIES.stream()
                .distinct() //will use equals to compare objects via id
                .forEach(System.out::println);
    }

    //added equals and hashcode to do a comparison on id
    record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters) {
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Movie movie = (Movie) o;
            return id == movie.id;
        }

        @Override
        public int hashCode() {
            return Objects.hash(id);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Using forEach() and forEachOrdered()

See the demo here

public class StreamDemo {
    static List<Movie> MOVIES = List.of(
            new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
            new Movie(3, "Matrix", "Wachowski", "Neo", 10, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
            new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
    );
    public static void main(String[] args) {
        MOVIES.parallelStream()
                //order is not guaranteed in case of parallel stream
                .forEach(System.out::println); 
    }

    record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters) {    }
}

Output:
Movie[id=3, title=Matrix, director=Wachowski, character=Neo, rating=10, releaseYear=2000, sideCharacters=[Ljava.lang.String;@2ef1e4fa]
Movie[id=2, title=LOTR, director=Peter Jackson, character=Aragorn, rating=10, releaseYear=2000, sideCharacters=[Ljava.lang.String;@3b0971b2]
Movie[id=1, title=The Dark Knight, director=Nolan, character=Batman, rating=10, releaseYear=2008, sideCharacters=[Ljava.lang.String;@6b408627]
Enter fullscreen mode Exit fullscreen mode

forEachOrdered() makes sure the order remains the same in case of parallel streams.

MOVIES.parallelStream()
                .forEachOrdered(System.out::println);
Enter fullscreen mode Exit fullscreen mode

Using findFirst() and findAny()

See the demo here

Depending on the filter applied, find*() methods try to find the matching element in the Stream.

public class StreamDemo {
    static List<Movie> MOVIES = List.of(
            new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
            new Movie(3, "Matrix", "Wachowski", "Neo", 9, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
            new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
    );
    public static void main(String[] args) {
        Optional<Movie> m1 = MOVIES.stream()
                .filter(movie -> movie.releaseYear() == 2000)
                .findAny();

        m1.ifPresent(System.out::println);
        System.out.println("---------------------");

        Optional<Movie> m2 = MOVIES.stream()
                .filter(movie -> movie.releaseYear() == 2000)
                .findFirst();
        m2.ifPresent(System.out::println);

        System.out.println("========================================");

        Optional<Movie> pm1 = MOVIES.parallelStream()
                .filter(movie -> movie.releaseYear() == 2000)
                .findAny();

        pm1.ifPresent(System.out::println);
        System.out.println("---------------------");

        Optional<Movie> pm2 = MOVIES.parallelStream()
                .filter(movie -> movie.releaseYear() == 2000)
                .findFirst();
        pm2.ifPresent(System.out::println);
    }

    record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters){    }
}
Enter fullscreen mode Exit fullscreen mode

Using allMatch(), anyMatch(), and noneMatch()

See the demo here

public class StreamDemo {
    static List<Movie> MOVIES = List.of(
            new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
            new Movie(3, "Matrix", "Wachowski", "Neo", 9, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
            new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
    );
    public static void main(String[] args) {
        //allMatch, noneMatch, anyMatch

        boolean result1 = MOVIES.stream()
                .allMatch(movie -> movie.rating() == 10);

        boolean result2 = MOVIES.stream()
                .anyMatch(movie -> movie.rating() == 10);

        boolean result3 = MOVIES.stream()
                .noneMatch(movie -> movie.rating() == 8);

    }

    record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters){    }
}
Enter fullscreen mode Exit fullscreen mode

Using takeWhile() and dropWhile()

See the demo here

They make a Stream more like a “stream” where take/drop the elements while a condition is true.

public class StreamDemo {
    static List<Movie> MOVIES = List.of(
            new Movie(2, "LOTR", "Peter Jackson", "Aragorn", 10, 2000, new String[]{"Sam", "Frodo", "Gandalf"}),
            new Movie(3, "Matrix", "Wachowski", "Neo", 9, 2000, new String[]{"Trinity", "Agent", "Oracle"}),
            new Movie(1, "The Dark Knight", "Nolan", "Batman", 10, 2008, new String[]{"Joker", "Mr. Fox"})
    );
    public static void main(String[] args) {
        MOVIES.stream()
                .takeWhile(movie -> movie.rating() == 10)
                .forEach(System.out::println);
        System.out.println("-----------------------");
        MOVIES.stream()
                .dropWhile(movie -> movie.rating() == 10)
                .forEach(System.out::println);

    }

    record Movie(int id, String title, String director, String character, int rating, int releaseYear, String[] sideCharacters){    }
}
Enter fullscreen mode Exit fullscreen mode

Using joining()

See the demo here

public static void main(String[] args) {

     String titles = MOVIES.stream()
              .filter(movie -> movie.rating() == 10)
              .map(Movie::title)
              .collect(Collectors.joining(", "));

      System.out.println(titles);

    }

Output:
LOTR, The Dark Knight
Enter fullscreen mode Exit fullscreen mode

Using Collectors.filtering() and Collectors.mapping()

See the demo here

String titles = MOVIES.stream()
            //.filter(movie -> movie.rating() == 10)
            //.map(Movie::title)
            .collect(
                Collectors.filtering(movie -> movie.rating() == 10, 
                                 Collectors.mapping(Movie::title, 
                                    Collectors.joining(", "))
                            ));

         System.out.println(titles);
Enter fullscreen mode Exit fullscreen mode

Using toList(), toSet(), toCollection()

See the demo here

List<String> titles = MOVIES.stream()
            .filter(movie -> movie.rating() == 10)
            .map(Movie::title)
            .collect(Collectors.toList());

Set<String> titlesSet = MOVIES.stream()
      .filter(movie -> movie.rating() == 10)
      .map(Movie::title)
      .collect(Collectors.toSet());

ArrayList<String> titlesArrayList = MOVIES.stream()
      .filter(movie -> movie.rating() == 10)
      .map(Movie::title)
      .collect(Collectors.toCollection(ArrayList::new)); 
        //specifically asks to create an ArrayList
Enter fullscreen mode Exit fullscreen mode

Using toMap()

See the demo here

//If keys are unique
Map<Integer, String> map = MOVIES.stream()
      .collect(Collectors.toMap(Movie::rating, Movie::title));

//If not, provide a merge function to merge two values with the same key
Map<Integer, String> map1 = MOVIES.stream()
      .collect(Collectors.toMap(Movie::rating, 
                                Movie::title, 
                                (s, s2) -> s.concat(", ").concat(s2)));
Enter fullscreen mode Exit fullscreen mode

Using flatMapping()

See the demo here

List<String> titles = MOVIES.stream()
                          //.flatMap(movie -> Arrays.stream(movie.sideCharacters()))
                          .collect(
                              Collectors.flatMapping(movie -> Arrays.stream(movie.sideCharacters()), 
                              Collectors.toList())
                           );

System.out.println(titles);
Enter fullscreen mode Exit fullscreen mode

Using Collectors.groupingBy()

See the demo here

Map<Integer, List<Movie>> byRating = MOVIES.stream()
        .collect(Collectors.groupingBy(Movie::rating));

System.out.println(byRating.getClass().getName());

Map<Integer, Set<Movie>> setByRating = MOVIES.stream()
        .collect(Collectors.groupingBy(Movie::rating, Collectors.toSet()));

System.out.println(setByRating.getClass().getName());

Map<Integer, Set<String>> titlesByRating = MOVIES.stream()
        .collect(Collectors.groupingBy(Movie::rating, Collectors.mapping(Movie::title, Collectors.toSet())));
System.out.println(titlesByRating.getClass().getName());

Map<Integer, Set<Movie>> linkedHashmapByRating = MOVIES.stream()
        .collect(Collectors.groupingBy(Movie::rating, LinkedHashMap::new, Collectors.toSet()));
System.out.println(linkedHashmapByRating.getClass().getName());
Enter fullscreen mode Exit fullscreen mode

Using Collectors.partitioningBy()

See the demo here

Map<Boolean, List<Movie>> moviesByRating = MOVIES.stream()
        .collect(Collectors.partitioningBy(movie -> movie.rating() == 10));

System.out.println(moviesByRating);

Map<Boolean, Set<Movie>> setmoviesByRating = MOVIES.stream()
        .collect(Collectors.partitioningBy(movie -> movie.rating() == 10, Collectors.toSet()));

System.out.println(setmoviesByRating);
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
therealdumbprogrammer
TheCodeAlchemist

Posted on March 14, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related