Pages

Showing posts with label Java 8. Show all posts
Showing posts with label Java 8. Show all posts

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);

Thursday, July 5, 2018

Java 8 Stream examples

This post will help you to understand some of the important and frequently used Stream operations in Java 8 which makes your programming with Java easy.

Let's take our traditional example, Employee and Department.  

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

 // getters and setters.

}


public class Department {

 private String departmentName;
 
 private Integer noOfEmployees;

 public Department(String departmentName, Integer noOfEmployees) {
      this.departmentName = departmentName;
      this.noOfEmployees = noOfEmployees;
 }

        // getters and setters
}

I am going to have some sample data set as follows in order to show you some important functionalities of Java 8 Stream interface. We have four departments and set of employees from those departments.

      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", account), 
                           new  Employee("Brayan", 25, "Galle", hr),
                           new  Employee("JoAnne", 45, "Negombo", ops),
                           new  Employee("Jake", 65, "Galle", hr),
                           new  Employee("Brent", 55, "Matara", hr),
                           new  Employee("Allice", 23, "Matara", ops),
                           new  Employee("Austin", 30, "Negombo", tech),
                           new  Employee("Gerry", 29, "Matara", tech),
                           new  Employee("Scote", 20, "Negombo", ops),
                           new  Employee("Branden", 32, "Matara", account),
                           new  Employee("Iflias", 31, "Galle", hr)); 

Find all employees who lives in 'Matara' city, sort them by their name and print the names of employees.

employeeList.stream()
     .filter(e -> e.getCity().equalsIgnoreCase("Matara"))
     .sorted(Comparator.comparing(Employee::getName))
     .forEach(e -> System.out.println(e.getName()));

Find distinct department names that employees work for.

employeeList.stream()
            .map(e -> e.getDepartment().getDepartmentName())
            .distinct()
            .forEach(System.out::println); 


Find the department names that these employees work for, where the number of employees in the department is over 50.

employeeList.stream()
            .map(Employee::getDepartment)
            .filter(d -> d.getNoOfEmployees() > 50)
            .distinct()
            .forEach(d -> System.out.println(d.getDepartmentName()));


Create a comma separate string of department names sorted alphabetically.

String s = employeeList.stream()
                       .map(e -> e.getDepartment().getDepartmentName())
                       .distinct()
                       .sorted()
                       .reduce("", (a, b) -> (a + "," + b)); 
System.out.println(s); 


Are there any employees from HR Department?

if (employeeList.stream()
                .anyMatch(e -> e.getDepartment().getDepartmentName().equalsIgnoreCase("HR"))) { 
    System.out.println("Found employees frm HR department"); 
}


Print all employee's name who are working for account department.

employeeList.stream()
            .filter(e -> e.getDepartment().getDepartmentName().equalsIgnoreCase("Account"))
            .map(Employee::getName)
            .forEach(System.out::println);


What is the highest number of of employees in all departments?

employeeList.stream()
            .map(e -> e.getDepartment().getNoOfEmployees())
            .reduce(Integer::max)
            .ifPresent(System.out::print);


Find the department which has the highest number of employees.

employeeList.stream()
            .map(Employee::getDepartment)
            .reduce( (d1, d2) -> d1.getNoOfEmployees() > d2.getNoOfEmployees() ? d1 : d2)
            .ifPresent(d -> System.out.println(d.getDepartmentName()));

The same thing can be done as follows using the max() method.

employeeList.stream()
            .map(Employee::getDepartment)
            .max(Comparator.comparing(Department::getNoOfEmployees))
            .ifPresent(d -> System.out.println(d.getDepartmentName()));


Find the total number of employees in all the departments.

employeeList.stream()
            .map(e -> e.getDepartment())
            .distinct()
            .map(e-> e.getNoOfEmployees())
            .reduce(Integer::sum).ifPresent(System.out::println);

Wednesday, July 4, 2018

Java 8 anyMatch(), allMatch(), noneMatch(), findAny() examples

Finding the existence of some elements among a collection of objects after matching with a specific property is common data processing idiom in programming. The Java 8 Streams API provides such facilities through the allMatch, anyMatch, noneMatch, findFirst, and findAny methods of a stream.

Let's take the following class to write example program for each of these methods. This class represents Dish of a menu.

public class Dish {

    private String name;
    private Boolean vegitarian;
    private Integer calaries;
    private Type type;
 
    public Dish(String name, Boolean vegitarian, Integer calaries, Type type) {
       super();
       this.name = name;
       this.vegitarian = vegitarian;
       this.calaries = calaries;
       this.type = type;
    }

    public String getName() {
       return name;
    }

    public void setName(String name) {
       this.name = name;
    }

    public Boolean getVegitarian() {
       return vegitarian;
    }

    public void setVegitarian(Boolean vegitarian) {
       this.vegitarian = vegitarian;
    }

    public Integer getCalaries() {
       return calaries;
    }

    public void setCalaries(Integer calaries) {
       this.calaries = calaries;
    }

    public Type getType() {
       return type;
    }

    public void setType(Type type) {
       this.type = type;
    }

    public enum Type { MEAT, FISH, OTHER };
}

Using anyMatch() method 

The anyMatch() method accepts a Predicate instance and checks for any matching elements in the stream. This method returns a boolean value, true, if it found a matching, otherwise falseThe anyMatch() method will traverse through the elements of the Stream until it finds a match.

Let's say, we have a  list of Dishes and want to find out if there is any vegetarian Dish among those. Just remind, how did you do this before Java 8. You had to at least write 5 or 6 lines of codes to do the above. In Java 8, you can do this by a single line.

List<Dish> menu = ....
if (menu.stream().anyMatch(Dish::getVegitarian)) {
    System.out.println("The menu is (somewhat) vegetarian friendly!!");
}

Using allMatch() method 

The allMatch() method also accepts a Predicate instance and works similar to anyMatch() method. This method will check to see if all the elements of the stream match the given predicate. For example, you can use it to find out whether the menu is healthy (that is, all dishes are below 1000 calories).

boolean isHealthy = menu.stream().allMatch(d -> d.getCalories() < 1000);

The allMatch() method stops traversing the elements of the stream as soon as one element produces false output.

Using noneMatch() method

The opposite of allMatch() is noneMatch(). It ensures that no elements in the stream match the given predicate. For example, you could rewrite the previous example as follows using noneMatch.

List<Dish> menu = ....
boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() >= 1000);

Using findAny() method

The findAny() method returns an arbitrary element of the current stream. It can be used in conjunction with other stream operations. For example, you may wish to find a dish that’s vegetarian. You can combine the filter method and findAny to express this query.

List<Dish> menu = .....
Optional<Dish> dish = menu.stream().filter(Dish::isVegetarian).findAny();

The Optional<T> class (java.util.Optional) is a container class to represent the existence or absence of a value. In the previous code, it’s possible that findAny() doesn’t find any element. Instead of returning null, which is well known for being error prone, the Java 8 introduced Optional.

Optional interface has few important method as follows,

          isPresent() method which returns true if Optional contains a value, false otherwise.
          ifPresent(Consumer<T> block) executes the given block if a value is present.

if (dish.isPresent()) {
 ///........
}

dish.ifPresent(d->System.out.println(d.getName()); 

Java 8 map(), flatMap() examples

Using map() method

When programming, it is very common, processing data in order to collect some information from a collections of objects. Let's say, we wanted find out the cities from all the employees in a particular company. Our employee class will be as follows. 


public class Employee {
 
    private String name;
    private Integer age;
    private String city;
    private String state; 
    private Department department;
 
    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;
    }
}

I didn't include all the attributes for Employee class, but what I need 'city' attribute in this case. 

So now, we have a list of Employee objects and need to find out distinct cities. Let's see the approach before Java 8. Hopefully, you will write a code as follows in order to get distinct cities. 

List<Employee> employeeList = .....
Set<String> cities = new HashSet<String>();
for (Employee emp : employeeList) {
    cities.add(emp.getCity());
}

Java 8 Stream interface introduces map() method which takes a function as an argument. This function is applied to each element in the stream and returns new stream. The code will look like follows.

List<Employee> employeeList = new ArrayList<Employee>();
List<String> cities = employeeList.stream()
                                  .map(Employee::getCity)
                                  .distinct()
                                  .collect(Collectors.toList());

Using flatMap() method 

Java 8 Stream interface introduces flatMap() method which can be used to merge or flatten few streams into a single stream.

Let's take an example. Suppose, we wanted to filter out distinct words in a text file. Look at the following text file.


Sri Lanka is a beautiful country in Indian ocean.
It is totally surrounded by the sea.

In Java 8, we can read a text file using a single line and it will return a Stream of string. Each element of the stream will be a one line of the text file.

Stream<String> lineStream = Files.lines(Paths.get("data.txt"), Charset.defaultCharset());

If you see the out put of above code by printing 'lineStream' Stream, it will be the lines of the text file. 

Next, we can convert each element of the above Stream into a Stream of words. Then we can use flatMap() method to flatten all Streams of words into a single Stream. If we execute the following code for each element of the 'lineStream' Stream, we will get two Stream of words. See the following code.

line -> Arrays.stream(line.split(" "))

Two Streams of words will be as follows.

Stream 1 : [SriLanka][is][a][beautiful][country][in][Indian][ocean.]} 
Stream 2 : [It][is][totally][surrounded][by][the][sea.]

The flatMap() method can flatten these two into a single Stream of word as follows.

Stream<String> wordStream = lineStream.flatMap(line -> Arrays.stream(line.split(" ")));

If you print the elements of the above 'wordStream', it will be all the words of the text file. But still, you will see duplicate words. You can use distinct() method to avoid the duplicates. Here's the final code.

List<String> wordStream = lineStream.flatMap(line -> Arrays.stream(line.split(" ")))
                                    .distinct()
                                    .collect(Collectors.toList());

If you see closely, you can find the distinct words of a text file just by using two lines of code with Java 8.

Tuesday, July 3, 2018

How to use filter() method in Java 8

Java 8 Stream interface introduces filter() method which can be used to filter out some elements from object collection based on a particular condition. This condition should be specified as a predicate which the filter() method accepts as an argument.

The java.util.function.Predicate interface defines an abstract method named test() that accepts an object of generic type T and returns a boolean.

Let's do some coding to understand the filter method more clearly. Look at the following Dish class.

public class Dish {

     private String name;
     private Boolean vegitarian;
     private Integer calaries;
     private Type type;
 
     public Dish(String name, Boolean vegitarian, Integer calaries, Type type) {
          super();
          this.name = name;
          this.vegitarian = vegitarian;
          this.calaries = calaries;
          this.type = type;
     }

     public Boolean getVegitarian() {
         return vegitarian;
     }

     public void setVegitarian(Boolean vegitarian) {
         this.vegitarian = vegitarian;
     }

     public Type getType() {
         return type;
     }

     public void setType(Type type) {
         this.type = type;
     }

     public enum Type { MEAT, FISH, OTHER };
}


Let's think, we want to filter out only the vegetarian Dishes from a list of all Dishes. Following is the approach  before Java 8.

    List<Dish> vegetarianDishes = new ArrayList<Dish>(); 
    for(Dish d: menu) {
       if(d.getVegetarian()) { 
          vegetarianDishes.add(d);
       } 
    }

The above approach is called external iteration which we explicitly manage the iteration over the collection of data.

How this can be done with Java 8 ? It is just a matter of single line as follows.

List<Dish> menu = ....
List<Dish> vegitarianDishes = menu.stream()
                                    .filter(d -> d.getVegitarian())
                                    .collect(Collectors.toList());

We have passed a Predicate instance into the filter() method in a form of a Lambda expression.

Also, we can use java 8 method references to pass a Predicate instance to the filter() method as follows.
List<Dish> menu = ....
List<Dish> vegitarianDishes = menu.stream()
                                    .filter(Dish::getVegitarian)
                                    .collect(Collectors.toList());

Dish::getVegitarian is the syntax for Java 8 method references. It refers to the getVegitarian() method of Dish class. 

The filter() method returns a Stream of Dishes and the collect() method converts the Stream into a List. The 'collect' operation is called a terminal operation.

Now let's say, we want to get first three Dishes that have more than 300 calories. Streams support the limit(n) method, which returns another stream that’s no longer than a given size. The requested size is passed as argument to limit.

List<Dish> menu = ....
List<Dish> threeHighCalaricDish = menu.stream()
                                         .filter(d -> d.getCalaries() > 300)
                                         .limit(3)
                                         .collect(Collectors.toList());

Similarly, if we want to skip first 3 elements, streams support the skip(n) method to return a stream that discards the first n elements. If the stream has fewer elements than n, then an empty stream is returned. Note that limit(n) and skip(n) are complementary!

Now, an exercise for you ! How would you use streams to filter the first two meat dishes?
List<Dish> menu = ....
List<Dish> meatDishes = menu.stream()
                                  .filter(d -> d.getType() == Dish.Type.MEAT)
                                  .limit(2)
                                  .collect(Collectors.toList())

Saturday, June 9, 2018

Java 8 sorting examples

How many lines of code did you write to sort a collection of objects before Java 8 ? How many, you will need with Java 8 ?
You can do it with a single line in Java 8.

Let's see the following Employee class.

public class Employee {
 
     private String name;
 
     private Integer age;
 
     public Employee(String name, Integer age) {
         super();
         this.name = name;
         this.age = age;
     }

     public String getName() {
         return name;
     }

     public void setName(String name) {
         this.name = name;
     }

     public Integer getAge() {
         return age;
     }

     public void setAge(Integer age) {
        this.age = age;
     }

}

Using the Collection's sort() method, employee list can be sorted. The sort() method expects a Comparator as an argument in order to compare two Employee objects. So our first solution looks like as follows.


public class EmployeeComparotor implements Comparator {
    @Override
    public int compare(Employee e1, Employee e2) {
        return e1.getAge().compareTo(e2.getAge()); 
    }
}

employeeList.sort(new EmployeeComparotor());

Rather than implementing Comparator and instantiating a new instance of it, we can use an anonymous class to improve our program.

employeeList.sort(new Comparator() {
    @Override
    public int compare(Employee e1, Employee e2) {
        return e1.getAge().compareTo(e2.getAge()); 
    }
}); 

Now, let's see, how can we improve this code further in order to reduce the verbosity by using Java 8 features. Java 8 introduces lambda expressions which allows us to pass a code to a method. Lambda expression can be passed to a method where functional interface is expected. A functional interface is an interface defining only one abstract method. In Java 8, the Comparator is a functional interface. The Collection's sort() method expects a Comparator as an argument, which accepts a functional interface. In this case, the Comparator represents BiFunction's descriptor. The BiFunction is a functional interface in Java 8. So then, you can pass a lambda expression into the sort method as follows. In order to sort employee list by their age, you need a single line as follows.

employeeList.sort((Employee e1, Employee e2) -> e1.getAge().compareTo(e2.getAge()));

Java compiler can infer the types of parameters of a lambda expression by using the context in which the lambda appears. So you can remove the types of parameter and rewrite the code as follows.

employeeList.sort((e1, e2) -> e1.getAge().compareTo(e2.getAge()));

Let's try to further reduce the code. Java 8 Comparator has a static method called comparing() that accepts a Function as an argument. This Function should extract the sort key and produce a Comparator object. So the shortest code to sort a list of objects in Java 8 will be,

employeeList.sort(comparing((e) -> e1.getAge()));

In stead of using a lambda expression, we can use method references to make our code slightly less verbose.

employeeList.sort(comparing(Employee::getAge));

If you want to sort the employee list by descending order in age, you can use the reversed() default method of the interface.

employeeList.sort(comparing(Employee::getAge).reversed());

Now, let's see, you want to sort the employees in their age and then, similar age employees by their names. Just remind, how did you do this earlier version of Java. In Java 8, you can simply use thenComparing() method in order to do this.

employeeList.sort(comparing(Employee::getAge).thenComparing(Employee::getName));



Share

Widgets