November 18, 2018

Using java streams to create immutable class

In my post – How to create immutable classes – to make lists immutable, I created a new List-Object everytime.

This is the class which, we are going to modify in this tutorial.

package immutable;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

final public class Person {
  private int age;
  private String name;
  private Date dateOfBirth;
  private List<String> nameOfFriends;
  
  public Person(int age, String name, Date dateOfBirth, List<String> nameOfFriends) {
    this.age = age;
    this.name = name;
    if(dateOfBirth != null) {
      this.dateOfBirth = (Date) dateOfBirth.clone();
    }
    if(nameOfFriends != null && !nameOfFriends.isEmpty()) {
      List<String> friendList = new ArrayList<>(nameOfFriends.size());
      for(String friendName : nameOfFriends) {
        friendList.add(friendName);
      }
      this.nameOfFriends = friendList;
    }
  }

  public int getAge() {
    return age;
  }

  public String getName() {
    return name;
  }

  public Date getDateOfBirth() {
    Date dob = null;
    if(dateOfBirth != null) {
      dob = (Date) dateOfBirth.clone();
    }
    return dob;
  }    

  public List<String> getNameOfFriends() {
    List<String> friendList  = null;
    if(nameOfFriends != null && !nameOfFriends.isEmpty()) {
      friendList = new ArrayList<>(nameOfFriends.size());
      for(String friendName : nameOfFriends) {
        friendList.add(friendName);
      }
    }
    return friendList;
  }
}

 

I am using a Person class in this article. As you can see, it has a member variable nameOfFriends, which is a List of Strings. To make it immutable we are creating new List object and copying the values from the the source list into the new List.

If, we are inside a constructor then, we copy data from the list instance into the new list. And if we are in getter funcition, then we create a new list and copy from the member list, everytime. Whenever a user wants to read from the list, we create a new list. That’s a HUGE waste of time, money, memory, key-strokes… blah blah blah….

So, to fix this issue, we are going to use Java-Stream. Why?
Because, Java-Streams are immutable by design. We can not modify the List by using it’s stream.
Java stream provides data in form of a pipe. The data is coming through a pipe, that’s why we can not modify the source. Because, we do not have any reference to the source of pipe.
We are standing at the other end of pipe and performing operations on the items coming through the pipe.

Modifying the instance variable. 

//Create a empty list
private List<String> nameOfFriends = Collections.emptyList();

In the above code, we are initializing the nameOfFriends with an empty list.This will help us to get rid of unnecessary NullPointerException and also, we won’t have to check for null in if-statements.

This is what constructor will look like with Java-Streams. 

public Person(int age, String name, Date dateOfBirth, Stream<String> nameOfFriends) {
  //Rest of the code is the same - that's why not repeated here
  if(nameOfFriends != null) {
    this.nameOfFriends = nameOfFriends.collect(Collectors.toList());
  }
}

I have modified the constructor parameters to use Stream rather than a List object. And now we are using the methods of java-stream to get a list of friends. Behind the screen of java-stream, a new List object is created and all the values coming from the stream is added into it. So, we did get a concise code and removed lots of lines. But, we are still creating a new object. The real benefit of java-stream is not in the constructor but in getter method.

New Getter

public Stream<String> getNameOfFriends() {
  return nameOfFriends.stream();
}

Old Getter

public List<String> getNameOfFriends() {
  List<String> friendList  = null;
  if(nameOfFriends != null && !nameOfFriends.isEmpty()) {
    friendList = new ArrayList<>(nameOfFriends.size());
    for(String friendName : nameOfFriends) {
      friendList.add(friendName);
    }
  }
  return friendList;
}

What changed ?

We changed the return-type to java-stream, and we return a stream. As streams are immutable by design, because of that, we do not need to create a new object and copy all the items and return it.
It is just a one-line code. We removed 8-lines from the getter and we are not creating any new objects anymore. Less code and less memory usage …. 🙂

Complete code using java streams is here.

import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

final public class PersonUsingJavaStream {
  private int age;
  private String name;
  private Date dateOfBirth;
  private List<String> nameOfFriends = Collections.emptyList();

  public PersonUsingJavaStream(int age, String name, Date dateOfBirth, Stream<String> nameOfFriends) {
    this.age = age;
    this.name = name;
    if(dateOfBirth != null) {
      this.dateOfBirth = (Date) dateOfBirth.clone();
    }
    if(nameOfFriends != null) {
      this.nameOfFriends = nameOfFriends.collect(Collectors.toList());
    }
  }

  public int getAge() {
    return age;
  }

  public String getName() {
    return name;
  }

  public Date getDateOfBirth() {
    Date dob = null;
    if(dateOfBirth != null) {
      dob = (Date) dateOfBirth.clone();
    }
    return dob;
  }

  public Stream<String> getNameOfFriends() {
    return nameOfFriends.stream();
  }
}

Okay.. This tutorial is over.

I Hope you guys had fun learning this 🙂

Leave a Reply

Your email address will not be published.