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.
|
No comments:
Post a Comment