Thursday, 3 March 2016

Java8: Optional: Avoid NullPointerException

null reference is the source of many problems in real world applications, Myself I had very bad experiences with this null reference while maintaining some body else’s code. By introducing java.util.Optional<T> class, java8 tried to solve some of the problems that come with null reference.


Let’s start examining the problems with null reference using an example. Suppose you want to generate mailIds to all your employees based on employee firstName, lastName, id and pin. Your employee mail id looks like 'firstName_lastName_id_pin@abc.com'.

public class Address {
 private String city;
 private String state;
 private String country;
 private String pin;

 public String getCity() {
  return city;
 }

 public void setCity(String city) {
  this.city = city;
 }

 public String getState() {
  return state;
 }

 public void setState(String state) {
  this.state = state;
 }

 public String getCountry() {
  return country;
 }

 public void setCountry(String country) {
  this.country = country;
 }

 public String getPin() {
  return pin;
 }

 public void setPin(String pin) {
  this.pin = pin;
 }

 @Override
 public String toString() {
  StringBuilder builder = new StringBuilder();
  builder.append("Address [city=").append(city).append(", state=")
    .append(state).append(", country=").append(country)
    .append(", pin=").append(pin).append("]");
  return builder.toString();
 }

 public Address(String city, String state, String country, String pin) {
  super();
  this.city = city;
  this.state = state;
  this.country = country;
  this.pin = pin;
 }

}


public class Employee {
 private String id;
 private String firstName;
 private String lastName;
 private Address addr;

 public String getId() {
  return id;
 }

 public void setId(String id) {
  this.id = id;
 }

 public String getFirstName() {
  return firstName;
 }

 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public void setLastName(String lastName) {
  this.lastName = lastName;
 }

 public Address getAddr() {
  return addr;
 }

 public void setAddr(Address addr) {
  this.addr = addr;
 }

 @Override
 public String toString() {
  StringBuilder builder = new StringBuilder();
  builder.append("Employee [id=").append(id).append(", firstName=")
    .append(firstName).append(", lastName=").append(lastName)
    .append(", addr=").append(addr).append("]");
  return builder.toString();
 }

 public Employee(String id, String firstName, String lastName, Address addr) {
  super();
  this.id = id;
  this.firstName = firstName;
  this.lastName = lastName;
  this.addr = addr;
 }

}


For simplicity purpose, I am storing all employee details in HashMap, where key is employeeId, value is Employee object.

Map<String, Employee> employees = new HashMap<>();


Following method return Employee object for given id.

public Employee getEmployeeById(String empId) {
 return employees.get(empId);
}


Following method generates mailId for given Employee id.

public String getMailId(String empId) {
 Employee emp = getEmployeeById(empId);

 String firstName = emp.getFirstName();
 String lastName = emp.getLastName();
 String pin = emp.getAddr().getPin();

 StringBuilder builder = new StringBuilder();
 builder.append(firstName).append(MAIL_ID_SEPARATOR).append(lastName)
    .append(MAIL_ID_SEPARATOR).append(empId)
    .append(MAIL_ID_SEPARATOR).append(pin).append("@abc.com");
  return builder.toString();
}


What is wrong with above code?
a. We are not checked whether employee is exist or not, before calling getFirstName(), getLastName() methods. If emp don’t exists, then we end up in NullPointerException.

String firstName = emp.getFirstName();
String lastName = emp.getLastName();

b. We are not checked whether Address is exist for given employee or not. If Address is not exist, again we end up in NullPointerException.

String pin = emp.getAddr().getPin();

After adding null checks functionality changed like below.

public String getMailId(String empId) throws EmployeeNotFoundException,
   AddressNotFoundException {
 StringBuilder builder = new StringBuilder();

 Employee emp = getEmployeeById(empId);

 if (emp == null)
  throw new EmployeeNotFoundException("Employee not exist for id "
    + empId);

 if (emp.getAddr() == null) {
  throw new AddressNotFoundException(
    "Address not exist for employee with id " + empId);
 }

 String firstName = emp.getFirstName();
 String lastName = emp.getLastName();
 String pin = emp.getAddr().getPin();

 builder.append(firstName).append(MAIL_ID_SEPARATOR).append(lastName)
   .append(MAIL_ID_SEPARATOR).append(empId)
   .append(MAIL_ID_SEPARATOR).append(pin).append("@abc.com");

 return builder.toString();

}


What if your class has many other references like Address in it. You may end up in writing all these null checks; you may forget to check some null condition. By using Optional class, you can get rid of these checks in a beautiful way. Optional class is a special marker around java object, which has special way to deal with null references.

By using Optional class you can rewrite the logic like below.

public String getMailId(String empId) throws EmployeeNotFoundException,
   AddressNotFoundException {
  StringBuilder builder = new StringBuilder();

  Optional<Employee> optional = getEmployeeById(empId);

  if (!optional.isPresent())
   throw new EmployeeNotFoundException("Employee not exist for id "
     + empId);

  Employee emp = optional.get();
  Optional<Address> optionalAddr = emp.getAddr();

  if (!optionalAddr.isPresent()) {
   throw new AddressNotFoundException(
     "Address not exist for employee with id " + empId);
  }

  String firstName = emp.getFirstName();
  String lastName = emp.getLastName();
  String pin = optionalAddr.get().getPin();

  builder.append(firstName).append(MAIL_ID_SEPARATOR).append(lastName)
    .append(MAIL_ID_SEPARATOR).append(empId)
    .append(MAIL_ID_SEPARATOR).append(pin).append("@abc.com");

  return builder.toString();

}


Following is the complete working application.

import java.util.Optional;

public class Employee {
 private String id;
 private String firstName;
 private String lastName;
 private Optional<Address> addr;

 public String getId() {
  return id;
 }

 public void setId(String id) {
  this.id = id;
 }

 public String getFirstName() {
  return firstName;
 }

 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public void setLastName(String lastName) {
  this.lastName = lastName;
 }

 public Optional<Address> getAddr() {
  return addr;
 }

 public void setAddr(Optional<Address> addr) {
  this.addr = addr;
 }

 @Override
 public String toString() {
  StringBuilder builder = new StringBuilder();
  builder.append("Employee [id=").append(id).append(", firstName=")
    .append(firstName).append(", lastName=").append(lastName)
    .append(", addr=").append(addr).append("]");
  return builder.toString();
 }

 public Employee(String id, String firstName, String lastName,
   Optional<Address> addr) {
  super();
  this.id = id;
  this.firstName = firstName;
  this.lastName = lastName;
  this.addr = addr;
 }

}


public class AddressNotFoundException extends Exception{

 public AddressNotFoundException(String msg){
  super(msg);
 }
}


public class EmployeeNotFoundException extends Exception{

 public EmployeeNotFoundException(String message){
  super(message);
 }
}


import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class EmployeeUtil {
 private Map<String, Employee> employees = new HashMap<>();
 private static final String MAIL_ID_SEPARATOR = "_";

 public void setEmployees(Map<String, Employee> employees) {
  this.employees = employees;
 }

 public String getMailId(String empId) throws EmployeeNotFoundException,
   AddressNotFoundException {
  StringBuilder builder = new StringBuilder();

  Optional<Employee> optional = getEmployeeById(empId);

  if (!optional.isPresent())
   throw new EmployeeNotFoundException("Employee not exist for id "
     + empId);

  Employee emp = optional.get();
  Optional<Address> optionalAddr = emp.getAddr();

  if (!optionalAddr.isPresent()) {
   throw new AddressNotFoundException(
     "Address not exist for employee with id " + empId);
  }

  String firstName = emp.getFirstName();
  String lastName = emp.getLastName();
  String pin = optionalAddr.get().getPin();

  builder.append(firstName).append(MAIL_ID_SEPARATOR).append(lastName)
    .append(MAIL_ID_SEPARATOR).append(empId)
    .append(MAIL_ID_SEPARATOR).append(pin).append("@abc.com");

  return builder.toString();

 }

 public Optional<Employee> getEmployeeById(String empId) {
  if (employees.containsKey(empId))
   return Optional.of(employees.get(empId));

  return Optional.empty();
 }
}


import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class Test {

 public static void main(String args[]) throws EmployeeNotFoundException,
   AddressNotFoundException {
  Address addr1 = new Address("Bangalore", "Karnataka", "India", "560037");
  Optional<Address> addr = Optional.of(addr1);

  Employee emp1 = new Employee("E532123", "HariKrishna", "Gurram", addr);

  Map<String, Employee> map = new HashMap<>();
  map.put("E532123", emp1);

  EmployeeUtil util = new EmployeeUtil();
  util.setEmployees(map);

  System.out.println(util.getMailId("E532123"));
 }
}


Output
HariKrishna_Gurram_E532123_560037@abc.com

Optional class provides other function 'orElse(T other)', it return the value if present, otherwise return other. By using this function, we can rewrite our EmployeeUtil.java like below.

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class EmployeeUtil {
 private Map<String, Employee> employees = new HashMap<>();
 private static final String MAIL_ID_SEPARATOR = "_";
 private Address defaultAddr = new Address("no_city", "no_state",
   "no_country", "no_pin");
 private Employee defaultEmp = new Employee("no_id", "no_first_name",
   "no_last_name", Optional.of(defaultAddr));

 public void setEmployees(Map<String, Employee> employees) {
  this.employees = employees;
 }

 public String getMailId(String empId) {
  StringBuilder builder = new StringBuilder();

  Optional<Employee> optional = getEmployeeById(empId);
  Employee emp = optional.orElse(defaultEmp);
  Optional<Address> optionalAddr = emp.getAddr();
  String firstName = emp.getFirstName();
  String lastName = emp.getLastName();
  String id = emp.getId();
  String pin = optionalAddr.orElse(defaultAddr).getPin();

  builder.append(firstName).append(MAIL_ID_SEPARATOR).append(lastName)
    .append(MAIL_ID_SEPARATOR).append(id).append(MAIL_ID_SEPARATOR)
    .append(pin).append("@abc.com");

  return builder.toString();

 }

 public Optional<Employee> getEmployeeById(String empId) {
  if (employees.containsKey(empId))
   return Optional.of(employees.get(empId));

  return Optional.empty();
 }
}


import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class Test {

 public static void main(String args[]) throws EmployeeNotFoundException,
   AddressNotFoundException {
  Address addr1 = new Address("Bangalore", "Karnataka", "India", "560037");
  Optional<Address> addr = Optional.of(addr1);

  Employee emp1 = new Employee("E532123", "HariKrishna", "Gurram", addr);

  Map<String, Employee> map = new HashMap<>();
  map.put("E532123", emp1);

  EmployeeUtil util = new EmployeeUtil();
  util.setEmployees(map);

  System.out.println("Mail id for id E532123 : "
    + util.getMailId("E532123"));
  System.out.println("Mail id for id 123 : " + util.getMailId("123"));
  System.out.println("Mail id for id 345 : " + util.getMailId("345"));
 }
}


Output
Mail id for id E532123 : HariKrishna_Gurram_E532123_560037@abc.com
Mail id for id 123 : no_first_name_no_last_name_no_id_no_pin@abc.com
Mail id for id 345 : no_first_name_no_last_name_no_id_no_pin@abc.com

Above logic generates no_first_name_no_last_name_no_id_no_pin@abc.com for all non-existence employees.

How to get an Optional instance
Optional class provide following static methods to get an Optional instance.

Method
Description
public static <T> Optional<T> empty()
Returns an empty Optional instance. No value is present for this Optional.
public static <T> Optional<T> of(T value)
Returns an Optional with the specified present non-null value.
public static <T> Optional<T> ofNullable(T value)
Returns an Optional describing the specified value, if non-null, otherwise returns an empty Optional.

How to check whether a value is present in the Optional ?
By using the method ‘isPresent’ you can check whether an object exist or not.

Method
Description
public boolean isPresent()
Return true if there is a value present, otherwise false.

How to get a value from Optional object?
By using following methods you can get the value from Optional object.

Method
Description
public T get()
If a value is present in this Optional, returns the value, otherwise throws NoSuchElementException.
public T orElse(T other)
Return the value if present, otherwise return other.
public T orElseGet(Supplier<? extends T> other)
Return the value if present, otherwise invoke other and return the result of that invocation.
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X extends Throwable
Return the contained value, if present, otherwise throw an exception to be created by the provided supplier.





Previous                                                 Next                                                 Home

No comments:

Post a Comment