Commonly Used Intermediate Stream Operations In Java

  • Post last modified:December 15, 2022
  • Reading time:6 mins read

Introduction

  • In Java Streams we can perform two type of operation , once is terminal operations such max, count, reduce and other is intermediate Operations such map, filter etc.
  • Basically Intermediate operation perform some logic and transform stream elements. For exam filter element will filter from source based on defined filter logic, while map operation map element from one type to another.
  • Whereas , Terminal operation perform termination of stream with eventual operation. This operation can be find max of stream elements, or converting the stream of element to list or map.
  • Major objective of this article is to go through mostly used Stream Intermediate operation in Java along with examples.

Intermediate Operations

Map

  • Map operation takes streams of elements and then apply function that is passed to it on each element and forward the result to next operation
  • In below example, we are squaring each number , map function takes each element from stream and squares it using lambda logic and then collector accumulate each element into list result.
List<Integer> sqNums = List.of(1, 2, 3, 4, 5).stream()
                .map(a -> a * a)
                .collect(Collectors.toList());
  • Let see one more example, here we are filtering premium customers and mapping customer object to customer name and collecting it into list of customer names.
List<String> targetUserNames = customers.stream()
                .filter(a->a.isPremium())
                .map(c -> c.getName())
                .collect(Collectors.toList());

FlatMap

  • FlatMap is used for flattening the input data . Flattening is basically merging collection such as list1, list2, list2 or map1, map2, map3 etc.
  • As we can see in below example we have a List of List of integer. now all we want to get as single list of integer , in that case we can merge each individual list using flatMap.
  • Inside flatMap function we are streaming each result and adding it to List collector as stream of element.Its kind of unnesting the elements.
List<List<Integer>> data = List.of(List.of(1), List.of(2), List.of(3));
System.out.println(data);
List<Integer> data1 = data.stream().flatMap(a -> a.stream()).collect(Collectors.toList());
System.out.println(data1);
  • Let’s consider one more example. Let’s say we have 3 small files that we want to merge into one single file , then we will read them one by one.
  • Then we can stream over each file content and flatten them to single List of String., which we can easily to write final output file using nio or bufferedstream.
Stream<String> lines = Files.lines(Path.of("")); // add filePath
Stream<String> lines1 = Files.lines(Path.of("")); // add filePath
Stream<String> lines2 = Files.lines(Path.of("")); // add filePath
List<Stream<String>> lines3 = List.of(lines, lines1, lines2);
List<String> mergedFiles = lines3.stream().flatMap(a -> a).collect(Collectors.toList());
@imsurajmishra
 

Filter : 

  • Streams provides filter() method that takes input as predicate and filter stream of elements based on the result of passed predicate.
  • Below example , we are filtering all the positive integers and collecting it to another list as output
List<Integer> integers = List.of(1, 2, -3, 4, -6, 7);
List<Integer> positive = integers.stream()
                .filter(e -> e > 0)
                .collect(Collectors.toList());
  • Similarly we can also filter based on any custom logic, in below example we are filtering each customers based in whether they are premium customers or not.

customer POJO

  • Lets create customer pojo which consist of property called isPremium.
static class Customer{
        private int age;
        private String name;
        private boolean isPremium;

        public Customer(int age, String name, boolean isPremium) {
            this.age = age;
            this.name = name;
            this.isPremium = isPremium;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public boolean isPremium() {
            return isPremium;
        }

        public void setPremium(boolean premium) {
            isPremium = premium;
        }
    }
  • We filter out customer list based on if they are premium customer or not.
 public static void main(String[] args) {

        List<Customer> customers = List.of(new Customer(23, "sam", true),
                new Customer(32, "lisa", true),
                new Customer(20, "jason", false),
                new Customer(39, "jemmy", true));

        List<Customer> premiumCustomers = customers.stream()
                .filter(Ops::premiumCustomer)
                .collect(Collectors.toList());
    }

    private static boolean premiumCustomer(Customer customer) {
        return customer.isPremium();
    }

limit

  • Limit basically only allows defined number of elements to pass from input stream to the next step.
  • It is intermediate operation that means it converts one streams into another.
  • Below example we generate random UUID and limit only 5 UUID to print.
Stream.generate(()-> UUID.randomUUID()).limit(5).forEach(System.out::println);

Skip

  • Skip allow us to skip the defined number of elements from streams of input.
  • Let’s say we have file that contains of header as first line so we can skip it using this operation.
Files.lines(Path.of(""))
    .skip(1)
    .forEach(System.out::println);

More info here

Peek

  • Peek method takes stream of elements and pass it to next operation , while doing it so it takes consumer function that it applies on it but it doesn’t modify the input element.
  • Purpose of this method is to just support debugging so that we can understand exact flow of elements in stream operation.
customers.stream().peek(c->c.getName()).forEach(System.out::println);
  • As we can see in the output , peek consumer operation doesn’t effect input and forEach print each customer that it originally takes from stream operation.
customers.stream()
          .peek(a-> System.out.println(a.getName()))
           .collect(Collectors.toList());

Distinct

  • Distinct element is useful operation when we want to filter only unique elements from input to output.
List.of(1,2,3,4,5,3).stream()
        .distinct()
        .collect(Collectors.toList())
        .forEach(System.out::println);

Sorted

  • We can also sort the elements using this operation using natural order.
List.of(5,2,3,4,5,0).stream()
           .sorted()
           .forEach(System.out::println);
  • We can also use comparator to define the sorting order.
 List.of(5,2,3,4,5,0).stream()
           .sorted((a,b)->b-a)
           .forEach(System.out::println);

If you are interested in Commonly Used Stream Terminal Operation then please read this article.

Conclusion

  • In this article we , we discussed commonly used Streams Terminal Operations in Java.
  • This article just touches the surface and doesn’t cover all the aspects of the mentioned operation so please check the documentation for Streams interface.

Bonus Tip

Leave a Reply