What is lambda function in java? Officially (java 8 docs), lambda expressions basically express instances of functional interfaces (An interface with single abstract method is called functional interface. An example is java.lang.Runnable). Lambda expressions implement the only abstract function and therefore implement functional interfaces
Lambda expressions are added in Java 8 and provide below functionalities.
- Enable to treat functionality as a method argument, or code as data.
- A function that can be created without belonging to any class.
- A lambda expression can be passed around as if it was an object and executed on demand. ( from GeeksForGeeks https://www.geeksforgeeks.org/lambda-expressions-java-8/)
Before im going to explain about clean code in using java lambdas, let me show your following code:
class Menu{
private String menuName;
private Integer price;
private String menuType;
//AllArgsConstructor
//Getters and Setters
}
//enum type
enum MenuType{
VEGETABLES("vegetables"),
FRUIT("fruit"),
CEREAL("cereal"),
MEATS("meats");
private final String menuTypeCode;
MenuType(String menuTypeCode) {
this.menuTypeCode = menuTypeCode;
}
public String getMenuTypeCode(){
return this.menuTypeCode;
}
}
/// in main class
Collection<Menu> menus = new ArrayList<>(){{
add(new Menu("Chicken",1300,"meats"));
add(new Menu("Pig",2500,"meats"));
add(new Menu("CocoCrunch",450,"cereal"));
add(new Menu("Milk",500,"drink"));
}};
Collection<String> menuNameAndPriceOfMeatsOrExpensiveMenus =
menus.stream().filter(menu -> {
if (menu.getMenuType() == MenuType.MEATS) {
return true;
}
if (price > cheapPrice) {
return true;
}
return false;})
.map(menu -> menu.getMenuName() + " " + menu.getPrice()).collect(Collectors.toList());
What do you think about that code?
For me it is just kind of dumb code. Although we can find out what the usage of that function by reading line by line of the function, but to be clean, it should explain explicitly the purpose, limitation and requirement. Also there is some ugly conditional checking in the stream which is kind of making it longer or we can say it is convoluted code.
Let us reduce some noise on those code.
First of all, we need to clean all objects that participate in that function,
if we look back to the function, we work with Menu, and MenuType
on Menu, its menuType is String, which mean any value of String type. We need to restrict menuType to make it strict and clean.
- Modify the menuType of Menu to type of MenuType class :
class Menu{
private String menuName;
private Integer price;
private MenuType menuType;
//AllArgsConstructor
//Getters and Setters
}
- For Conditional Checking, we can add a special utitility class to perform that:
class MenuTypeChecker{
public static boolean isMeats(Menu menu){
return menu.getMenuName().equals(MenuType.MEATS);
}
public static boolean isExpensive(Menu menu){
int cheapPrize = 1000;
return menu.getPrice()>cheapPrize;
}
}
So now we can simplify it like following:
Collection<Menu> menus = new ArrayList<>(){{
add(new Menu("Chicken",1300,MenuType.MEATS));
add(new Menu("Pig",2500,MenuType.MEATS));
add(new Menu("CocoCrunch",450,MenuType.CEREAL));
add(new Menu("Milk",500,MenuType.DRINK));
}};
Collection<String> menuNameAndPriceOfMeatsOrExpensiveMenus = menus.stream().filter(m->{
if(MenuTypeChecker.isMeats(m))return true;
if(MenuTypeChecker.isExpensive(m))return true;
return false;
}).map(m-> m.getMenuName()+" "+m.getPrice()).collect(Collectors.toList());
It is more readable, right? But, we can still do more improvement on this code.
What more can we do? We can give name to particiapating lambdas to make it cleaner and readable.
In java 8 there is Predicate and Function class to help us dealing with cleaning lambda function usages.
Here we go again.
- The if condition in the filtering context can be explained by Predicate. We need to store it to predicate of menu as we want to filter object with type of menu:
Predicate<Menu> isMeatsOrExpensiveMenu = m->(MenuTypeChecker.isMeats(m) || MenuTypeChecker.isExpensive(m));
- And the purpose of mapping context can be explained by store it to variable with type of Function :
Function<Menu,String> getMenuNameAndPrice = m->m.getMenuName()+" "+m.getPrice();
- Lastly, we can modify the our main function like following:
menus.stream().filter(isMeatsOrExpensiveMenu).map(getMenuNameAndPrice);
- Here is the final look of our main function
Collection<Menu> menus = new ArrayList<>(){{
add(new Menu("Chicken",1300,MenuType.MEATS));
add(new Menu("Pig",2500,MenuType.MEATS));
add(new Menu("CocoCrunch",450,MenuType.CEREAL));
add(new Menu("Milk",500,MenuType.DRINK));
}};
Predicate<Menu> isMeatsOrExpensiveMenu = m-> (MenuTypeChecker.isMeats(m) || MenuTypeChecker.isExpensive(m));
Function<Menu,String> getMenuNameAndPrice = m->m.getMenuName()+" "+m.getPrice();
Collection<String> menuNameAndPriceOfMeatsOrExpensiveMenus = menus.stream().filter(isMeatsOrExpensiveMenu).map(getMenuNameAndPrice).collect(Collectors.toList());
Now it is cleaner. We really know the purpose and requirement of each block in our code. Actually, this can be more cleaner.
But this should be clear enough for now.
Thank you for reading this article :)
Top comments (0)