Pages

Saturday, April 2, 2022

How to use Java Optional

The most common and frequent exception which many Java developers encounter is NullPointerException.  Though this is very common exception and the fix is also straightforward for most of the developers. They simply put null check or nested null checks in order to avoid happening this exception. This actually increases the number of indentation level in your code and also reduces the readability too. Even if you avoid happening this exception in this way, the complete flow might not be consistence with all client codes. If you fix this exception with simple null check,  you need to make sure that the complete flow executes correctly as per business requirement without producing strange results from the application. 

And also, there are many situations where, you add this null check only after happening the NullPointerException at least a once only. Yes, some developers proactively check the presence and absence of a value of variable before it is accessed and write the complete flow which handles the absence of the value. 

Java 8 introduces java.util.Optional<T> to represent the absence of a value in a particular variable or a field of any type in more precise and safer manner. This enforces developers to specifically focus on the absence of the value of their reference, a variable or return type of a method.  So defining a method to accept an optional or a method to return an optional indicates that the value of that variable may not be presence. There is a possibility of absence of the value. Your peer developers or in future, if someone is going to modify the code or use those methods by different client code, they will know that the value of the reference can be absence and should write the client code according to that. 

Assume the following are two methods of a data access class for Employee entity.
    public Employee findEmployeeByName(String name) {
        Employee emp = null; // code get employee from DB or somewhere.
        return emp;
    }

    public Optional<Employee> findEmployeeOptionalByName(String name) {
        Employee emp = null; // code to get employee from DB or somewhere
        return Optional.ofNullable(emp);
    }

Both method is to find an employee for a given employee name. Let's say both scenario, system could not find an employee for the given name and both method returns value absence reference (null). The first method returns null. But the second method, in stead of returning null, it returns an empty optional which indicates all clients codes that, the return value in the returned variable may or may not presence and write your client code according to that. Let's look two client code for both above method.
 
    //Client code1
    Employee emp = employeeRepository.findEmployeeByName("David");
    System.out.print(emp.getName());

    //Client code2
    Optional<Employee> employeeOptional = employeeRepository.findEmployeeOptionalByName("David");
    System.out.println(employeeOptional.get().getName());	

In the above code, first client code end with NullPointerException and the second client code ends with NoSuchElementException. Off-course, in the first client code, developers may directly access 'emp' object without checking the absence of the value which results NullPointerException and then later on, they will add null check before accessing 'emp' object. This is the old practice that most developers were doing.

But, how about the second client code2 which is also not the best way to unwrap an optional. The 'findEmployeeOptionalByName()' method returns an Optional of Employee which indicates that the value may not be present. The developers should write the client code to handle the absent of the value in this case. It is a developer's responsibility and good practice too. 

How, optional is unwrapped in the above client code2 is not a best way of doing. If particular method returns an optional means, developers need to specifically focus on it and write the code appropriately. The above code does not leverage the purpose of Optional. 

There are several ways to unwrap an Optional. Using get() method of Optional is not a good idea every time, unless you know the the presence and absence of the value of the optional. For empty optional, this will return NoSuchElementException exception. The optional's ifPresent() method and also for default values, orElse() method are good choices in order to unwrap an optional and get the value. Look at the following code.

    //unwrap optional with isPresent() method
    if(employeeOptional.isPresent()) {
       System.out.println(employeeOptional.get().getName());
    }

    //unwrap optional with orElse() method
    String empName = employeeOptional.map(Employee::getName).orElse("Unknown");
    System.out.print(empName);
As in the above second approach, you can apply map() method to an optional in order to transform optional into a different type. In this case, Employee optional to a string. If optional is empty, it will execute default orElse() method. Further you have orElseGet() method which accepts a supplier than using orElse(). The orElseGet() is more efficient than orElse(), because the code within the supplier argument will be executed only if optional value is not present. Let's say, you have to do some expensive thing, if  optional value is not presence, orElseGet() method is best suited.
String empName = employeeOptional.map(Employee::getName).orElseGet(() -> "Unknnow"); System.out.println(empName);
If you want to throw an exception, in case of optional value is absence, you can use orElseThrow() method as follows. The advantage of using this method is, you can throw any exception type that you preferred.

      String empName = employeeOptional.map(Employee::getName).orElseThrow(() -> new RuntimeException("Unable to find employee"));
      System.out.print(empName);
You can apply map(), flatMap() and filter() methods to an optional similar as stream API.

Thursday, February 3, 2022

Java Function example use case

Java Function<T, R> is functional interface which accepts one type of argument and return a result. We can leverage java Function to write more maintainable codes. For example, let's say, we are going to develop an API or service method to place an order for the customer. After successful placement of order, the service method should send a notification to the customer. At this moment, we have two ways of sending notification, ie: SMS and  email.  But, we don't know, in future, we might need to add more notification methods. 
If we don't write this method in clear and maintainable manner, we have to modify this service method every time in order to add a new notification method. 

First, we will see, the solution for this using object oriented approach and then we will improve the code using Java Function in a functional way. 

As usual, let's define an interface as follows.

public interface Notifier {
    void notify(Order order);
}

Then we need two concrete classes for each type of notification method. As follows, we need to create a new concrete class for each notification method.

public class SmsNotifier implements Notifier {
    @Override
    public void notify(Order order) {
        //code to send sms notification
    }
}


public class EmailNotifier implements Notifier {
    @Override
    public void notify(Order order) {
        // Code to send email notification
    }
}

Let's write some sample client code in order to place an order
     
//OOP approch
//Assume we have an instanc eof order service
OrderService orderService = ...
//Assume we have the order object
Order order = ...
//Assume we have the customer object
Customer customer = ....

Notifier notifier = null;
if (customer.getNotificationPreference().equals("SMS")) {
    notifier = new SmsNotifier();
} else {
    notifier = new EmailNotifier();
}

orderService.placeOrder(order, notifier);
     
  
This disadvantage of above approch is, we have to create a concrete class for each new notification method. Let's see, how we can improve the code using java Function. Let's make 'Notifier' a functional interface. We simply add @FunctionalInterface annotation.

@FunctionalInterface
public interface Notifier {
    void notify(Order order);
}

The client code using java lambda expression is as follows.

// functional approach.
if (customer.getNotificationPreference().equals("SMS")) {
    orderService.placeOrder(order, (Order odr) -> {
      //code to send SMS
    });
} else {
    orderService.placeOrder(order, (Order odr) -> {
     //code toe send email
    });
}
With the functional programming approch, we don't want to create a new class for each new notification method. You need to decide which approch to use based on your use case or scope of different strategy. If it is small piece of code that you want to customize, you can go with functional approch using java lambda.

Sunday, January 30, 2022

How to group list of objects using java lambda

Let's consider the following POJO class.
public class Dish {

	private String name;
	private Boolean vegitarian;
	private Integer calories;
	private DishType type;

}

Java 8 stream API provides plenty of features to group objects into different buckets. Let's say, we want to group list of Dish objects based on the amount of calories. We will define an enum constant to declare different calory categories. 


public enum CaloryLevel {
    DIET,
    NORMAL,
    FAT;
}

The Calory level is not an attribute of Dish class. The calory level is defined by using the amount of calories from Dish class which is an attribute of Dish class, ie: 'calories'. If the amount of calory is less than 400, the calory level is 'DIET'. If the amount of calory is between 400 and 700, the calory level is 'NORMAL'. If the calory amount is above 700, the calary level is defined as 'FAT'. The legacy approch of grouping list of objects is as follows. It generally uses a map to put objects into different buckets.


public static void groupDishByCaloriAmountLegacyApproch(List<Dish> menu) {

	Map<CaloryLevel, List<Dish>> dishOverCaloryLevel = new HashMap<CaloryLevel, List<Dish>>();

	for(CaloryLevel caloryLevel : CaloryLevel.values()) {
		dishOverCaloryLevel.put(caloryLevel, new ArrayList<Dish>());
	}

	for(Dish dish : menu) {
		if (dish.getCalaries() <= 400) {
			dishOverCaloryLevel.get(CaloryLevel.DIET).add(dish);
		} else if (dish.getCalaries() <= 700) {
			dishOverCaloryLevel.get(CaloryLevel.NORMAL).add(dish);
		} else {
			dishOverCaloryLevel.get(CaloryLevel.FAT).add(dish);
		}
	}
}

Now, let's see, how we can improve the above code using Java 8 features. We can get the same output by using Java stream API provided methods. See the following code.

Map<CaloryLevel, List<Dish>> dishOverCaloryLevel = menu.stream().collect(Collectors.groupingBy((Dish d) -> {
	if (d.getCalaries() <= 400) {
		return CaloryLevel.DIET;
	} else if (d.getCalaries() <= 700) {
		return CaloryLevel.NORMAL;
	} else
		return CaloryLevel.FAT;
	}
));
Again, the calory level is not an attribute of Dish class. That attribute is external one which is defined by using the amount of calories. If we want to get the calory level by using the amount calories which is defined within the Dish class, we have to write boilaplate codes as in the above method. As a best coding practice, defining the calory level can be added into the Dish class itself as method. The new Dish class is as follows.

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Dish {

	private String name;
	private Boolean vegitarian;
	private Integer calaries;
	private DishType type;

	private CaloryLevel getCaloryLevel() {
		if (this.getCalaries() <= 400) {
			return CaloryLevel.DIET;
		} else if (this.getCalaries() <= 700) {
			return CaloryLevel.NORMAL;
		} else {
			return CaloryLevel.FAT;
		}
	}
}

The complete lambda expresion which was passed into the 'Collectors.groupingBy' method, now it is defined as a method inside the Dish class itself. We can now use Java 8's method referene to simply the code more as follows. Now the code to group list of Dish objects has narrowed down into a single like.

Map<CaloryLevel, List<Dish>> dishOverCaloryLevel1 = menu.stream().collect(Collectors.groupingBy(Dish::getCaloryLevel));

How to replace anonymous class with Java 8 lambda

Java lambda expression can be used to replace legacy anonymous class. For example, let's consider the following POJO class.

public class Dish {

	private String name;
	private Boolean vegitarian;
	private Integer calories;
	private DishType type;

}
Let's say, we have a list of objects from the above class and we want to sort them by name. The normal approch to sort this kind of custom object list is, using a comparator. So, we can write a comparator as follows.

Comparator<Dish> comparator = new Comparator<Dish>() {
         @Override
         public int compare(Dish o1, Dish o2) {
             return o1.getName().compareTo(o2.getName()); // ASC order
             //return o2.getName().compareTo(o1.getName()); // DESC order
         }
};

With Java's lambda, we can get rid of anonymous class comparator and reduce the code to a single like as follows.

Comparator<Dish> comparator = Comparator.comparing(Dish::getName);

And we can sort the list of Dish's as following.

Collections.sort(menu, comparator);

Wednesday, September 8, 2021

Java 8 Stream.collect() examples

Java 8 Stream interface provides a method called collect() which helps to perform various kind of manipulations of the elements in a Stream. These include

  • Combining all the elements of a Stream into a different data structures like List and Set.
  • Reducing and summarizing operations like count, max, min.
  • Grouping and partitioning.

The arguments passed into the collect() method are called collectors which are implementations of Collector interface.

In this tutorial, I am going to show you that, how to use these collectors under each category with examples. Very famous example, Employee and Department and the sample data set is as follows.

public class Employee {
 
    private String name;
 
    private Integer age;
 
    private String city;
 
    private Integer salary;
 
    private String sex;
 
    private Department department;
 
    public Employee(String name, Integer age, String city, Integer salary, String sex, Department department) {
        super();
        this.name = name;
        this.age = age;
        this.city = city;
        this.salary = salary;
        this.sex = sex;
        this.department = department;
    }

   // getters and setters
 
}


public class Department {

    private String departmentName;
 
    private Integer noOfEmployees;

    public Department(String departmentName, Integer noOfEmployees) {
         super();
         this.departmentName = departmentName;
         this.noOfEmployees = noOfEmployees;
    }
    // getters and setters
}


Sample data set :
Department account = new Department("Account", 75); 
Department hr = new Department("HR", 50);
Department ops = new Department("OP", 25);
Department tech = new Department("Tech", 150);
  
List<Employee> employeeList = Arrays.asList(new  Employee("David", 32, "Matara", 2000, "Male", account), 
    new  Employee("Brayan", 25, "Galle",  3000, "Male",hr),
    new  Employee("JoAnne", 45, "Negombo",  800, "Female", ops),
    new  Employee("Jake", 65, "Galle",  2500, "Male", hr),
    new  Employee("Brent", 55, "Matara",  8000, "Male", hr),
    new  Employee("Allice", 23, "Matara",  600, "Female", ops),
    new  Employee("Austin", 30, "Negombo",  9000, "Male", tech),
    new  Employee("Gerry", 29, "Matara",  2500, "Male", tech),
    new  Employee("Scote", 20, "Negombo",  2500, "Male", ops),
    new  Employee("Branden", 32, "Matara",  2500, "Male", account),
    new  Employee("Iflias", 31, "Galle",  2900, "Female", hr)); 



Combining Elements

The most straightforward and frequently used collector is toList() static method. By using this method, elements of a Stream can be combined to a List as follows.

List<Employee> employeeList = employeeList.stream().collect(Collectors.toList()); 

Similarly, if you want to convert Stream into a set, the toSet() static method can be used as follows.

Set<Employee> employeeSet = employeeList.stream().collect(Collectors.toSet());


Reducing and Summarizing


The collectors which are parameters to the Stream's collect() method can also be used to combine all the items in the stream into a single result like total, min, max and count.

Using Collectors.counting() :

If we want to find the number of employees in the list, Collectors.counting() method can be passed as an argument to collect() method as follows.
Long totalNumOfEmployees = employeeList.stream().collect(Collectors.counting());

Using Collectors.maxBy() and Collectors.minBy() :

If we want to find the maximum and the minimum value in a stream, we can use maxBy() and minBy() methods. These two collectors take a Comparator as an argument to compare the elements in the stream.

Let's try to find the employee who is getting the maximum and the minimum salary.

//Find max salary employee 
employeeList.stream() 
            .collect(Collectors.maxBy(Comparator.comparingInt(Employee::getSalary))) 
            .ifPresent(e -> System.out.println("Max salary employee :" + e.getName())); 

//Find min Salary 
employeeList.stream() 
            .collect(Collectors.minBy(Comparator.comparingInt(Employee::getSalary))) 
            .ifPresent(e -> System.out.println("Min salary employee :" + e.getName()));


Using Collectors.summingXXX() methods:


Collectors.summingInt(), Collectors.summingLong() and Collectors.summingDouble() are some collectors which can be used to get the summation of a particular properly of elements in a stream. These methods accept a property of corresponding type in order to perform the summing operation.

Let's say, we want to find the total salary of all the employees.

Integer totalSalary = employeeList.stream().collect(Collectors.summingInt(Employee::getSalary)); 



Using Collectors.averagingXXX() methods:

Collectors.averagingInt(), Collectors.averagingDouble() and Collectors.averagingLong() are the collectors that can be used to calculate the average value of some numeric fields. Let's say, we want to find the average salary of all the employees.

Double averageSalary = employeeList.stream().collect(Collectors.averagingInt(Employee::getSalary));

Using Collectors.summarizingXXX() methods:

Quite often, we might need to get the count, total, maximum, minimum and average by using a single operation. Collectors.summarizingInt(), Collectors.summarizingDouble() and Collectors.summarizingLong() collectors can be used to get above all statistics from a single operation. 

Let's try to get all above salary statistics from a single operation.

IntSummaryStatistics salalryStat = employeeList.stream().collect(Collectors.summarizingInt(Employee::getSalary));

IntSummaryStatistics class has many defined methods which returns the above statistic values separately. The string representation of above IntSummaryStatistics result will be as follows.

IntSummaryStatistics{count=11, sum=36300, min=600, average=3300.000000, max=9000}

Using Collectors.joining() method:

The joining() method can be used to concatenate String type property values into a single string from the elements of a stream. Note that the joining() method internally makes use of a StringBuilder to append the generated string into one.


Let's say, we want to get all the employee's names as comma separated single string.

String employeeNameStrings = employeeList.stream().map(Employee::getName).collect(Collectors.joining(", ")); 
System.out.println(employeeNameStrings);

Using Collectors.reducing() method:

The reducing() method is a generalization of all the collector methods. Using the reducing() method, we can perform many operations that were performed using specific collector methods. These operations include max, min, sum etc. 

The reducing() method takes three arguments.


  • The first argument is the starting value for reduction operation. This value will return in case empty stream. 
  • The second argument is usual transformation function. 
  • The third argument is a BinaryOperator that aggregates two arguments into a single one. 


Let's try to get the maximum and minimum salary of employees by using reducing() method too.

//Max salary
Integer maxSalary = employeeList.stream().collect(Collectors.reducing(0, Employee::getSalary, Integer::max)); 

Optional minSalary = employeeList.stream().collect(Collectors.reducing((e1, e2) -> e1.getSalary() < e2.getSalary() ? e1 :  e2));
System.out.println("Min salary " + minSalary.get().getSalary()); 

The reducing() method has a form of accepting single argument as in the above min operation.


Grouping

Groping of elements based on a one or more property is a very common database operation. The Collectors.groupingBy() factory method in Java 8 can be used to perform same kind of operations with collections of elements. The Collectors.groupingBy() method accepts a method reference as an argument which is used to classify the elements of the stream into different groups. 

Let's try to group list of employees by their department.

Map<Department, List<Employee>> employeesOverDepartment = employeeList.stream().collect(Collectors.groupingBy(Employee::getDepartment));

We call the above Function a classification function, because it is used to classify the elements of the stream into different groups. But, it is not always possible to use a classification function, because we may wish to classify the elements using more complex criteria than a simple property reference. 

The Collectors.groupingBy() method accepts a lambda expression which allows us to group elements based on more complex criteria. 

Let's say, we want to group our employee list into two groups as "High Salary" and "Low Salary". This might not be a good example, but just to show you, how we can use more complex criteria to group elements.

Map<String, List<Employee>> map = employeeList.stream().collect(
                                        Collectors.groupingBy((Employee e) -> {
                                                if (e.getSalary() > 2000) {
                                                   return "High Salary";
                                                } else {
                                                    return "Low Salary";
                                                } 
                                        }));

The similar approach can be used to group employees by their department name.

Map<String, List<Employee>> employeesByDepartmentName = employeeList.stream()
                       .collect( Collectors.groupingBy((Employee e) -> { 
                                    return e.getDepartment().getDepartmentName();
                               }));


The Collectors.groupingBy() method has a version of accepting two argument which allows us to perform multilevel grouping. In the above example, we grouped employees by the department name. 

Let's say, we want to further group employees within a particular department by their sex.

Map<String, Map<String, List<Employee>>> employeesByDepartmentName = employeeList.stream()
                .collect( Collectors.groupingBy((Employee e) -> { return e.getDepartment().getDepartmentName(); },
                         Collectors.groupingBy((Employee e) -> { return e.getSex(); }) ) );


In the above example, two-argument version of Collectors.groupingBy() method, the second argument was a another groupingBy() method. But, it is not necessarily to be a groupingBy() method. But, more generally, the second collector passed to the first groupingBy() method can be any type of collector.

Let's say, we want to get the number of employees in each department.

Map<String, Long> noOfEmployeesInDepartment = employeeList.stream()
                       .collect(Collectors.groupingBy(
                                 (Employee e) -> { return e.getDepartment().getDepartmentName(); }, 
                                  Collectors.counting()));
Using a similar kind of approach, we can find the employee who gets the maximum salary for each department. See the following example.
Map<String, Employee> maxSalaryEmployeeOfDept = employeeList.stream()
                 .collect(Collectors.groupingBy((Employee e) -> {return e.getDepartment().getDepartmentName();}, 
                          Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Employee::getSalary)), 
                                                                        Optional::get) ) );




More generally, the collector passed as the second argument to the groupingBy() factory method will be used to perform a further reduction operation on all the elements in the stream classified into the same group. Let's say, we want to get the total salary for each department.
Map<String, Integer> totalSalaryOverDepartment = employeeList.stream() 
            .collect(Collectors.groupingBy(
                         (Employee e) -> {return e.getDepartment().getDepartmentName();}, 
                          Collectors.summingInt(Employee::getSalary)));

Partitioning
Share

Widgets