Pages

Showing posts with label Lambda. Show all posts
Showing posts with label Lambda. Show all posts

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

Share

Widgets