Introduction
The sweetest syntactic sugar added to Java up until now are definitely Lambda Expressions.
Java is a verbose language and that can get in the way of productivity and readability. Reducing boilerplate and repetitive code has always been a popular task with Java developers, and clean, readable, concise code is generally sought after.
Lambda Expressions removed the need to type cumbersome boilerplate code when it comes to some common tasks by allowing developers to call them without them belonging to a class and passing them as if they were objects.
These expressions have seen major usage with the Java Streams API, and Spring's WebFlux framework for creating reactive, dynamic applications.
Another, really useful feature added to Java 8, are method references, which make Lambda Expressions just that much more concise and simple, by invoking (referencing) the methods using a method name when the Lambda Expression would've been used simply to call a method.
Method References
Method references are essentially shortened Lambda Expressions, used for invoking methods.
They consist of two parts:
Class::method;
And a common example would be printing out the results of say, subscribing to a publisher service or a Java Stream:
someCodeChain.subscribe(System.out::println);
Let's go over an example of imperative code, which we'll then turn to functional code via Lambda Expressions and then finally shorten via Method References.
We'll be making a simple class:
public class Employee {
private int id;
private String name;
private int wage;
private String position;
// Constructor, getters and setters
@Override
public String toString() {
return "Name: " + name + ", Wage: " + wage + ", Position: " + position;
}
public int compareTo(Employee employee) {
if (this.wage <= employee.wage) {
return 1;
} else {
return -1;
}
}
}
If we formed this class into a collection, such as an ArrayList
, we couldn't sort it using the utility method .sort()
since it doesn't implement the Comparable
interface.
What we can do is define a new Comparator
for these objects while calling the .sort()
method:
Employee emp1 = new Employee(1, "David", 1200, "Developer");
Employee emp2 = new Employee(2, "Tim", 1500, "Developer");
Employee emp3 = new Employee(3, "Martha", 1300, "Developer");
ArrayList<Employee> employeeList = new ArrayList<>();
employeeList.add(emp1);
employeeList.add(emp2);
employeeList.add(emp3);
Collections.sort(employeeList, new Comparator<Employee>() {
public int compare(Employee emp1, Employee emp2) {
return emp1.compareTo(emp2);
}
});
System.out.println(employeeList);
Running this code will yield:
[Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]
Here, the anonymous class (Comparator
) is defining the comparison criteria. We can make it a lot simpler and shorter by using a Lambda Expression:
Collections.sort(employeeList, (e1, e2) -> e1.compareTo(e2));
Running this piece of code will yield:
[Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]
Then again, since all we're doing with this Lambda Expression is call a single method, we can reference just that method:
Collections.sort(employeeList, Employee::compareTo);
And this will also yield:
[Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]
Method Reference Types
Method references can be used in a couple of different scenarios:
- Static Methods:
Class::staticMethodName
- Instance Methods of Particular Objects:
object::instanceMethodName
- Instance Methods of Arbitraty Objects:
Class::methodName
- Constructor Reference:
Class::new
Let's go over all of these types through some simple examples.
Static Method References
You can reference any static
method of a class by simply calling its containing class with the method name.
Let's define a class with a static
method and then reference it from another class:
public class ClassA {
public static void raiseToThePowerOfTwo(double num) {
double result = Math.pow(num, 2);
System.out.println(result);
}
}
And now, from another class, let's use the static
utility method:
public class ClassB {
public static void main(String[] args) {
List<Double> integerList = new ArrayList<>();
integerList.add(new Double(5));
integerList.add(new Double(2));
integerList.add(new Double(6));
integerList.add(new Double(1));
integerList.add(new Double(8));
integerList.add(new Double(9));
integerList.forEach(ClassA::raiseToThePowerOfTwo);
}
}
Running this piece of code will yield:
25.0
4.0
36.0
1.0
64.0
81.0
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
There are many Java classes that offer static
utility methods which can be used here. In our example we've used a custom method, albeit not a very useful one in this instance.
Instance Methods of Particular Objects
You can call a method from a particular instantiated object by referencing the method using the reference variable of the object.
This is most often illustrated through a custom comparator. We'll use the same Employee
class from before and the same list to highlight the difference between these two:
public class Employee {
private int id;
private String name;
private int wage;
private String position;
// Constructor, getters and setters
@Override
public String toString() {
return "Name: " + name + ", Wage: " + wage + ", Position: " + position;
}
public int compareTo(Employee employee) {
if (this.wage <= employee.wage) {
return 1;
} else {
return -1;
}
}
}
Now, let's define a CustomComparator
:
public class CustomComparator {
public int compareEntities(Employee emp1, Employee emp2) {
return emp1.compareTo(emp2);
}
}
And finally, let's populate a list and sort it:
Employee emp1 = new Employee(1, "David", 1200, "Developer");
Employee emp2 = new Employee(2, "Tim", 1500, "Developer");
Employee emp3 = new Employee(3, "Martha", 1300, "Developer");
ArrayList<Employee> employeeList = new ArrayList<>();
employeeList.add(emp1);
employeeList.add(emp2);
employeeList.add(emp3);
// Initializing our CustomComparator
CustomComparator customComparator = new CustomComparator();
// Instead of making a call to an arbitrary Employee
// we're now providing an instance and its method
Collections.sort(employeeList, customComparator::compareEntities);
System.out.println(employeeList);
Running this code will also yield:
[Name: Tim, Wage: 1500, Position: Developer, Name: Martha, Wage: 1300, Position: Developer, Name: David, Wage: 1200, Position: Developer]
The main difference is that by adding another layer, through the CustomComparator
, we can add more functionality for comparison and take it away from the class itself. A class like Employee
shouldn't be burdened with complex comparison logic and this results in cleaner and more readable code.
On the other hand, sometimes we don't wish to define custom comparators and introducing one is simply too much of a hassle. In such cases, we'd call a method from an arbitrary object of a particular type, shown in the next section.
Instance Methods of Arbitrary Objects
This example has already been shown in the beginning of the article when we've boiled down the imperative approach into a functional approach via Lambda Expressions.
Though, for good measure, since this approach is used really often, let's take a look at another example:
List<Integer> integerList = new ArrayList<>();
integerList.add(new Integer(5));
integerList.add(new Integer(2));
integerList.add(new Integer(6));
integerList.add(new Integer(1));
integerList.add(new Integer(8));
integerList.add(new Integer(9));
// Referencing the non-static compareTo method from the Integer class
Collections.sort(integerList, Integer::compareTo);
// Referencing static method
integerList.forEach(System.out::print);
Running this piece of code would yield:
125689
While this may seem like it's the same as a call to a static method, it's not. This is equivalent to calling the Lambda Expression:
Collections.sort(integerList, (Integer a, Integer b) -> a.compareTo(b));
Here, the distinction is more obvious. If we were to call a static
method, it would look like:
Collections.sort(integerList, (Integer a, Integer b) -> SomeClass.compare(a, b));
Referencing Constructors
You can reference a class' constructor in the same way you'd reference a static
method.
You could use a reference to a constructor instead of classic class instantiation:
// Classic instantiation
Employee employee = new Employee();
// Constructor reference
Employee employee2 = Employe::new;
Based on the context, if multiple constructors are present, the adequate one will be used if referenced:
Stream<Employee> stream = names.stream().map(Employee::new);
Due to a stream of names, if a Employee(String name)
constructor is present, it will be used.
Another way you could use constructor references is when you'd wanna map out a stream into an array, while keeping the particular type. If you were to simply map it and then call toArray()
, you'd get an array of Object
s instead of your particular type.
If we tried, say:
Employee[] employeeArray = employeeList.toArray();
Of course, we'd be greeted with a compiler error since we .toArray()
returns an array of Object
s. Casting it won't help either:
Employee[] employeeArray = (Employee[]) employeeList.toArray();
But this time, it'll be a runtime exception - ClassCastException
.
We can avoid that with:
// Making a list of employees
List<String> employeeList = Arrays.asList("David", "Scott");
// Mapping a list to Employee objects and returning them as an array
Employee[] employeeArray = employeeList.stream().map(Employee::new).toArray(Employee[]::new);
// Iterating through the array and printing information
for (int i = 0; i < employeeArray.length; i++) {
System.out.println(employeeArray[i].toString());
}
And with that, we get the output:
Name: David, Wage: 0, Position: null
Name: Scott, Wage: 0, Position: null
Conclusion
Method References are a type of Lambda Expressions that are used to simply reference a method in their call. With them, writing code can be a lot more concise and readable.
Lambda Expressions have introduced Java Developers to a more functional approach in programming which allows them to avoid writing verbose code for simple operations.