In Java, we all know we can pass data as arguments, but what about passing functionality as an argument? In this tutorial, we'll see just how to do that. Let me explain that with an ideal use case.
Ideal use case
Suppose you are building a car tracking application for the country, and you want to display all cars of a particular state.For example Tamil Nadu, the vehicle number of a car contains 'TN' in it.
One would represent a Car like this:
public class Car {
String modelName;
int modelNum;
LocalDate registrationDate;
String vehicleNum;
public String getModelName(){..}
public void printCar() {..}
...
}
Approach 1: Create methods for the search criteria.
public static void printCarsIn(List<Car> cars, String state){
for(Car c : cars) {
if(c.getVehicleNum().contains(state)){
c.printCar();
}
}
}
printCarsIn(cars, "TN");
You are checking each instance of the cars list to see if it's vehicle number contains 'TN' in it. If so, you print it.
This approach is more likely to make your application brittle. What if you update Car class so that it contains different members or perhaps different data types for its members. Or what if the search criteria is changed. You have to rewrite a lot of code in all of the defined methods. The search criteria can instead be separated into a different class.
Approach 2: Specify search criteria in a local class.
The following class checks whether an instance of Car
is from Tamil Nadu.
interface CheckCar {
boolean test(Car c, String state);
}
class CheckCarsInState implements CheckCar {
public boolean test(Car c, String state) {
return c.getVehicleNum().contains(state);
}
}
Now, you can create an instance of this CheckCarsInState
class and use it to check the criteria.
public static void printCarsIn(List<Cars> cars, String state, CheckCar tester){
for(Car c : cars){
if(tester.test(c, state)){
c.printCar();
}
}
}
printCarsIn(cars, "TN", new CheckCarsInState());
Although we have separated the search criteria, we have additional code for declaring a new class.
Approach 3: Using an anonymous class
An anonymous class can be used if only one instance is required from a class.
interface CheckCar {
boolean test(Car c, String state);
}
public static void printCarsIn(List<Cars> cars, String state, CheckCar tester){
for(Car c : cars){
if(tester.test(c, state)){
c.printCar();
}
}
}
//method call
printCarsIn(cars, "TN", new CheckCar() {
public boolean test(Car c, String state) {
return c.getVehicleNum().contains(state);
}
});
But still, the anonymous class looks bulky considering the CheckPerson
interface contains only one method. We can instead use a lambda expression.
Approach 4: Lambda expression to pass functionality
The CheckPerson
is a functional interface (an interface with only one abstract method). Lambda expressions can be used against functional interfaces.
interface CheckCar {
boolean test(Car c, String state);
}
public static void printCarsIn(List<Cars> cars, String state, CheckCar tester){
for(Car c : cars){
if(tester.test(c, state)){
c.printCar();
}
}
}
printCarsIn(cars, "TN", (c, state) -> c.getVehicleNum().contains(state));
With lambda expressions, you specify the criteria for which you want to search when calling the function.
Syntax of Lambda expression
- Parameter list: A list of parameters enclosed in parentheses. The parentheses can be avoided if only one parameter is used.
c -> c.printCar();
Arrow token,
->
Body: The body can either be a block of statements or an expression. If it is a single expression, the evaluation will be returned. Alternatively, you can use a
return
statement like so,
c -> {
//...
return c.getVehicleNum();
}
Note: A single return statement is not an expression, so it can't be used without braces.
Thanks for reading the blog. Feel free to provide inputs and suggestions for any areas of improvement. :)
Top comments (1)
Noice and clear!