Java Stream Operations Simplified - Part 1

Java Stream Operations Simplified - Part 1

Introduction

The Stream API was introduced in Java 8 which is used to process a collection of data. Stream is simply a sequence of objects. There are numerous operations which can be done on java streams. And they are divided into two types

  1. Intermediate
  2. Terminal

Intermediate operations will return a stream whereas a terminal operation will either return nothing or a primitive or object or a collection.

A list of these operations are mentioned below

IntermediateTerminal
filter()forEach()
map()collect()
flatMap()max()
sorted()min()
distinct()count()
peek()reduce()
skip()anyMatch()
limit()allMatch()
noneMatch()
findFirst()
findAny()

In this post, we will be dealing with intermediate operations.

Intermediate Operations

Let's create an enum to represent the different kinds of pets.

public enum Type { 
    DOG,
    CAT,
    BIRD,
    RABBIT
}

Now, let's create a simple Pet class.

@Data
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
class Pet implements Comparable<Pet> {
    private String name;
    private Type type;
    private int age;

    @Override
    public int compareTo(@NotNull Pet o) {
        return this.getName().compareTo(o.getName());
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null)
            return false;

        final Pet other = (Pet) obj;
        if (this == other)
            return true;
        return this.getName().equals(other.getName());
    }

    @Override
    public int hashCode() {
        int hashno = 7;
        hashno = 13 * hashno + (name == null ? 0 : name.hashCode());
        return hashno;
    }
}

You must be wondering why this simple class has a comparable interface, overriding equals and hashcode method. Well, it will be clear to you in some moment when we will learn about sorted() and distint(). So hang in there.

A collection of pets.

List<Pet> myPets =
        List.of(
                new Pet("Molly", Type.CAT, 2),
                new Pet("Jimmy", Type.DOG, 3),
                new Pet("Rocky", Type.DOG, 3)
        );

Now, let's dive into the intermediate operations

  • filter()

Creates a new stream from original stream after certain condition is met. Many a times we will be dealing with lots of elements in a collection and we may need to filter and get only handful of them based on certain conditions. This filtering process is done using filter() method.

 List<Pet> dogs = myPets
        .stream()
        .filter(p -> p.type.equals(Type.DOG))
        .collect(Collectors.toList());

// From the list of pets, we have filtered only dogs. Hence the new collection dogs will have only 2 elements i.e Rocky and Jimmy
// collect() is a terminal operation which will be covered in the next post
  • map()

This operation is used to transform the elements in the stream. map() function takes in an argument which essentially transforms the value of elements in the stream.

 List<String> names= myPets
        .stream()
        .map(Pet::getAge)
        .collect(Collectors.toList()); 
// [3, 2, 3]
// Here the pets collection was transformed into a list of ages of the pets
  • flatMap()

This operation is used upon list of collections. Sometimes, you might have a list which contains a list of objects. So, in order to flatten the inner lists , we use flatMap() function.

List<Pet> myPets =  List.of(
        new Pet("Jimmy", Type.DOG, 3),
        new Pet("Molly", Type.CAT, 2),
        new Pet("Rocky", Type.DOG, 3)
);

List<Pet> myNeighbourPets = List.of(new Pet("Rabbu", Type.RABBIT, 5));

List<List<Pet>> petCollection = List.of(myNeighbourPets, myPets);

 petCollection 
        .stream()
        .flatMap(p -> p.stream())
        .forEach(t -> System.out.println(t.getName())); 
// Rabbu
// Jimmy
// Molly
// Rocky

// foreach() is another terminal operation which will be covered in next post
  • sorted()

This is used to sort the stream according to natural order.

myPets 
     .stream()
     .sorted()
     .forEach(t -> System.out.println(t.getName())); 
// Jimmy
// Molly
// Rocky

// Another example
Stream.of("Mike", "Vishnu", "Laura", "Anchu", "Mahesh", "Arun")
            .sorted()
            .forEach(t -> System.out.print(t+" "));

// Anchu Arun Laura Mahesh Mike Vishnu

If the elements of stream are objects, then make sure they are Comparable. Otherwise it will throw java.lang.ClassCastException. This is the reason class Pet implements Comparable interface.

  • distinct()

Returns a stream with distinct elements.

List<Pet> myPets =  Arrays.asList(
                   new Pet("Jimmy", Type.DOG, 3),
                   new Pet("Jimmy", Type.DOG, 3),
                   new Pet("Molly", Type.CAT, 2),
                   new Pet("Rocky", Type.DOG, 3)
); // A list with duplicate entries

myPets
    .stream()
    .distinct()
    .forEach(t -> System.out.println(t.getName()));
// Jimmy
// Molly
// Rocky

If the stream contains complex object types, then equals() and hashCode() must be overridden. Now please inspect the Pet class carefully.

  • peek()

peek() function is mostly used to log or debug intermediate operations

myPets
    .stream()
    .peek(p -> System.out.println("Pet name is "+p.getName()))
    .map(n -> n.getName().toUpperCase())
    .forEach(t -> System.out.println(t)); 
//Pet name is Jimmy
//JIMMY
//Pet name is Molly
//MOLLY
//Pet name is Rocky
//ROCKY
  • skip()

skip() takes in one argument n and skips 'n' elements from the stream.

myPets
    .stream()
    .skip(2) // skips first two elements
    .forEach(t -> System.out.println(t.getName()));  
// Rocky
  • limit()

limit() takes in one argument n and skips all elements after first 'n' elements from the stream are taken.

myPets
    .stream()
    .limit(2) // skips first two elements
     .forEach(t -> System.out.println(t.getName()));  
// Jimmy
// Molly

Features of Intermediate Operations

  1. These operations only return a stream.
  2. They can be clubbed together one after the other to form a pipeline of operations.
  3. They are lazily loaded. This means they are not operated until a terminal operation is called.

Conclusion

In this post, we looked briefly upon intermediate stream operations. For detailed information, please refer to the official Java Docs

Happy coding !