October 28, 2022

Method Reference in Java

In the post Lambda expression in Java we have already seen how Lambda expression provides an instance of functional interface and implements the abstract method of the functional interface. Though sometimes, a lambda expression is used just to call an existing method. In those cases you can refer to the existing method by name using Method references in Java. Method reference is a compact and more readable lambda expressions for methods that already have a name.

For example consider the following lambda expression-

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.forEach((Integer a) -> System.out.println(a));

Here lambda expression is just calling an existing method which can be done using method reference making the code more readable and concise.

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.forEach(System.out::println);

Method reference also needs a target type

Java method reference can be termed as a special form of lambda expression as method reference also needs a target type context (a compatible functional interface) and it also creates an instance of functional interface just like lambda expression does. Which also means method reference can only be used for a single method.

Where these two differ is lambda expression can also provide implementation for an abstract method where as method reference refers to an existing method.

Java Method reference syntax

As we have already seen in the example a new double colon operator (::) is added in Java to be used with method reference.

Class or object that contains the method is on the left side of the double colon operator and the name of the method in on the right side of the operator.

Kinds of Method References

There are four kinds of method references in Java

Kind Example
Reference to a static method ContainingClass::staticMethodName
Reference to an instance method of a particular object containingObject::instanceMethodName
Reference to an instance method of an arbitrary object of a particular type ContainingType::methodName
Reference to a constructor ClassName::new

Static method reference

Following example shows how to use static method reference in Java.

public class MethodRef {
  public static <T> List<T> filter(List<T> myList, Predicate<T> predicate) {
    List<T> filterdList = new ArrayList<T>();
    for(T element: myList) {
      if(predicate.test(element)) {
        filterdList.add(element);
      }
    }
    return filterdList;
  }
  public static boolean isMoreThanValue(int i) {
    return i > 10;
  }
  public static void main(String[] args) {
    List<Integer> myList = Arrays.asList(25, 5, 17, 1, 7, 14, 9, 11);
    System.out.println("Method call as lambda expression");
    List<Integer> filterdList = filter(myList, (i) -> MethodRef.isMoreThanValue(i));
    System.out.println("Filtered elements- " + filterdList);
    System.out.println("Method call using method reference");
    filterdList = filter(myList, MethodRef::isMoreThanValue);
    System.out.println("Filtered elements- " + filterdList);
  }
}
Output
Method call as lambda expression
Filtered elements- [25, 17, 14, 11]
Method call using method reference
Filtered elements- [25, 17, 14, 11]

In the example static method isMoreThanValue() is called using method reference MethodRef::isMoreThanValue.

In the filter method one of the argument is of type Predicate. Predicate is a functional interface which has an abstract method test() which evaluates this predicate on the given argument and return a boolean value (true or false).

Static method isMoreThanValue() is an implementation of the abstract method test() of the Predicate functional interface. When you make a method call filter(myList, MethodRef::isMoreThanValue), Java can infer from the context that isMoreThanValue() is an implementation for Predicate interface.

Method reference to an instance method

In this case you use an object of the class to refer the method rather than using the class name.

public class MethodRef {
  public <T> List<T> filter(List<T> myList, Predicate<T> predicate) {
    List<T> filterdList = new ArrayList<T>();
    for(T element: myList) {
      if(predicate.test(element)) {
        filterdList.add(element);
      }
    }
    return filterdList;
  }
  public boolean isMoreThanValue(int i) {
    return i > 10;
  }
  public static void main(String[] args) {
    List<Integer> myList = Arrays.asList(25, 5, 17, 1, 7, 14, 9, 11);
    MethodRef obj = new MethodRef();
    System.out.println("Method call as lambda expression");
    List<Integer> filterdList = obj.filter(myList, (i) -> obj.isMoreThanValue(i));
    System.out.println("Filtered elements- " + filterdList);
    System.out.println("Method call using method reference");
    filterdList = obj.filter(myList, obj::isMoreThanValue);
    System.out.println("Filtered elements- " + filterdList);
  }
}
Output
Method call as lambda expression
Filtered elements- [25, 17, 14, 11]
Method call using method reference
Filtered elements- [25, 17, 14, 11]

It is the same example as the static method reference only change is now instance of the class is used for method reference. Methods are also not required to be static now.

Reference to an instance method of an arbitrary object of a particular type

In the previous example specific object of the class is used but you may have a scenario in which you want to specify an instance method that can be used with any object of a given class. In that case method reference will have the following form-

ClassName::instanceMethodName

In this case, the first parameter of the functional interface matches the object that has been used to invoke the method and any other parameters are passed to the method.

In the example there a class Person with fields firstName, lastName, age and you need to get the count of Persons with age greater than 50. In this scenario isAgeGreater() method has to be invoked for all the Person objects.

class Person {
  private String firstName;
  private String lastName;
  private int age;
  public Person(String firstName, String lastName, int age){
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
  }

  public String getFirstName() {
    return firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public int getAge() {
    return age;
  }
  // This becomes the abstract method (test) implementation
  // of the functional interface
  boolean isAgeGreater(int age) {
    return this.getAge() > age;
  }
}
@FunctionalInterface
interface TestInterface {
  boolean test(Person person, int age);
}
public class MethodRef {
  public static void main(String[] args) {
    List<Person> tempList = createList();
    int count = ageCounter(tempList, Person::isAgeGreater, 50);
    System.out.println("Person count age greater than 50- " + count);
  }

  static int ageCounter(List<Person> list, TestInterface ref, int age) {
    int count = 0;
    for(Person person : list) {
      // first param becomes the invoking object
      // other parameters are passed to method
      if(ref.test(person, age))
        count++;
    }
    return count;
  }

  private static List<Person> createList(){
    List<Person> tempList = new ArrayList<Person>();
    tempList.add(new Person("Joe","Root", 28));
    tempList.add(new Person("Mathew","Hayden", 42));
    tempList.add(new Person("Richard","Hadlee", 55));
    tempList.add(new Person("Sunil","Gavaskar", 65));
    tempList.add(new Person("Brian","Lara", 45));     
    return tempList;
  }
}

Constructor reference

You can also reference a constructor which is similar to method reference except that the name of the method is new in this case.

Syntax for the constructor reference is as follows-

classname::new

Constructor reference Java example

In the method copyElements() one of the parameter is of type Supplier which is a functional interface defined in java.util.function package. The functional interface Supplier contains one method get that takes no arguments and returns an object. A new ArrayList instance is passed to Supplier as a constructor reference.

public class MethodRef {
  public static void main(String[] args) {
    List<Integer> myList = Arrays.asList(25, 5, 17, 1, 7, 14, 9, 11);
    List<Integer> tempList = copyElements(myList, ArrayList::new);
    System.out.println("Copied list- " + tempList);
  }

  public static List<Integer> copyElements(List<Integer> sourceList, Supplier<List<Integer>> destList) {      
    List<Integer> list = destList.get();
    for (Integer i : sourceList) {
      list.add(i);
    }
    return list;
  }
}

That's all for the topic Method Reference in Java. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment