Wednesday, 10 June 2015

Method reference in Java 8

We generally use lambda expressions to create anonymous methods but sometimes lambda expressions are also used to call an existing method. There is another feature provided in Java 8 related to lambda expression called method reference that provides a clearer alternative to refer to the existing method by name.

Important thing to note about method reference is that they provide a way to refer to a method, they don't execute the method.

Method reference relates to lambda expression as they also require a target type context and at the time of execution they also create an instance of functional interface.
How they differ is where lambda expressions let us define anonymous methods which can be used as an instance of functional interfaces. Method references do the same thing, but with existing methods.

Kinds of Method References

There are four kinds of method references:

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

Notice that the class name is separated from the method name by a double colon. The :: is a new separator (known as double colon operator) that has been added in Java 8.

Method References to static methods

The following example code shows a static method reference:

@FunctionalInterface
interface IMyStringFunc{
    String[] stringFunc(String[] names);
}

public class MethodRefDemo {
    //First param is a functional interface. Thus, it can be passed a method      reference.
    //  which becomes an instance of functional interface 
    static String[] stringMeth(IMyStringFunc sf, String[] names) {
        return sf.stringFunc(names);
    }

    public static void main(String[] args) {
         //String array of names
        String[] strNames = new String[]{"Ram", "shyam", "Ramesh", "John", "brad", 
                "Suresh"};
        // method reference to the method sortName is passed as first param
        String[] sortedNames = stringMeth(SortClass::sortName, strNames);
        for(String name : sortedNames){
            System.out.println(name);
        }
    }

}

class SortClass{
    // A static method that sorts an array
    static String[] sortName(String[] names) {
        //Sorting array using sort method (case sensitive)
        Arrays.sort(names);
        return names;
    }
}

Output

John
Ram
Ramesh
Suresh
brad
shyam

In the code in this line stringMeth(SortClass::sortName, strNames); method reference to sortName declared inside SortClass is passed as the first argument to stringMeth. This works because sortName is compatible with the IMyStringFunc functional interface. Thus, the expression SortClass::sortName evaluates to a reference to an object in which method sortName provides the implementation of abstract method stringFunc in functional interface IMyStringFunc.

Method References to Instance Methods

To pass a reference to an instance method of a particular object is similar to static method reference, instead of class we need to use object

objRef::methodName

Let's see the same example again with object

@FunctionalInterface
interface IMyStringFunc{
    String[] stringFunc(String[] names);
}

public class MethodRefDemo {
    //First param is a functional interface. Thus, it can be passed a method reference.
    //  which becomes an instance of functional interface 
    static String[] stringMeth(IMyStringFunc sf, String[] names) {
        return sf.stringFunc(names);
    }

    public static void main(String[] args) {
        // creating an object
        SortClass sc = new SortClass();
         //String array of names
        String[] strNames = new String[]{"Ram", "shyam", "Ramesh", "John", "brad", 
                "Suresh"};
        // method reference to the method sortName is passed as first param
        String[] sortedNames = stringMeth(sc::sortName, strNames);
        for(String name : sortedNames){
            System.out.println(name);
        }
    }

}

class SortClass{
    // A non-static method that sorts an array
    String[] sortName(String[] names) {
        //Sorting array using sort method (case sensitive)
        Arrays.sort(names);
        return names;
    }
}

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

There may be a situation when you want to specify an instance method that can be used with any object of a given class without any particular object. In that case name of the class is used even when a non-static method is specified.

ClassName::instanceMethodName

Let's take one example where we have a Person class and we have a list of Person object. We want to call getFirstName() method on all those person objects.

Person Class

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

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }
    public char getGender() {
        return gender;
    }
    
    public String toString(){
        StringBuffer sb = new StringBuffer();
        sb.append(getFirstName()).append(" ");
        sb.append(getLastName()).append(" ");
        sb.append(getAge()).append(" ");
        sb.append(getGender());
        return sb.toString();
        
    }
}
@FunctionalInterface
interface IMyStringFunc<T, R>{
    R stringFunc(T t);
}

public class MethodRefDemo {

    public static void main(String[] args) {
        List<Person> personList = createList();
         List allNames = MethodRefDemo. listAllNames (personList, Person::getFirstName);
         System.out.println("" + allNames);
        
    }
    
    //Utitlity method to create list
    private static List<Person> createList(){
        List<Person> tempList = new ArrayList<Person>();
        IMyFunc createObj = Person::new;
        Person person = createObj.getRef("Ram","Tiwari", 50, 'M');
        tempList.add(person);
        person = createObj.getRef("Prem", "Chopra", 13, 'M');
        tempList.add(person);
        person = createObj.getRef("Tanuja", "Trivedi", 30, 'F');
        tempList.add(person);
        person = createObj.getRef("Manoj", "Sharma", 40, 'M');
        tempList.add(person);
        person = createObj.getRef("John", "Trevor", 70, 'M');
        tempList.add(person);
        person = createObj.getRef("Alicia", "Sliver", 17, 'F');
        tempList.add(person);
        System.out.println("List elements are - ");
        System.out.println(tempList);
        return tempList;
    }

    private static List<String> listAllNames(List<Person> person, IMyStringFunc<Person, String> func){
        List<String> result = new ArrayList<String>();
        person.forEach(x -> result.add(func.stringFunc(x)));
        return result;
    }
}

Notice this line Person::getFirstName here we are calling getFirstName method on all the objects of the list not any one particular object.

Reference to a Constructor

A constructor can be referenced in the same way as a static method by using new.

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

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }
    public char getGender() {
        return gender;
    }
    
    public String toString(){
        StringBuffer sb = new StringBuffer();
        sb.append(getFirstName()).append(" ");
        sb.append(getLastName()).append(" ");
        sb.append(getAge()).append(" ");
        sb.append(getGender());
        return sb.toString();
        
    }
}
@FunctionalInterface
interface IMyFunc {
    Person getRef(String firstName, String lastName, int age, char gender);
}

public class LambdaDemo {
    
    
    public static void main(String[] args) {
        List<Person> personList = createList();
        System.out.println("Name - " + personList.get(0).getFirstName());
    }
    // Utitlity method to create list
     private static List<Person> createList(){
         List<Person> tempList = new ArrayList<Person>();
         IMyFunc createObj = Person::new;
         Person person = createObj.getRef("Ram","Tiwari", 50, 'M');
         tempList.add(person);
         person = createObj.getRef("Prem", "Chopra", 13, 'M');
         tempList.add(person);
         person = createObj.getRef("Tanuja", "Trivedi", 30, 'F');
         tempList.add(person);
         person = createObj.getRef("Manoj", "Sharma", 40, 'M');
         tempList.add(person);
         person = createObj.getRef("John", "Trevor", 70, 'M');
         tempList.add(person);
         person = createObj.getRef("Alicia", "Sliver", 17, 'F');
         tempList.add(person);
         System.out.println("List elements are - ");
         System.out.println(tempList);
         return tempList;
     }
}

Output

List elements are - 
[Ram Tiwari 50 M, Prem Chopra 13 M, Tanuja Trivedi 30 F, Manoj Sharma 40 M, John Trevor 70 M, Alicia Sliver 17 F]
Name - Ram

In the program notice that the getRef method of the IMyFunc returns a reference of type Person and has String, String, int and char as parameters. Also notice that in the Person class there is a constructor which specifies String, String, int and char as parameters.

Using this line Person::new a constructor reference to a Person constructor is created. Functional interface method also takes the same params thus the constructor of the Person class is referred through it.

That's all for this topic Method reference in Java 8. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. Overview of lambda expressions
  2. Functional interfaces & lambda expression
  3. Functional interface annotation in Java 8
  4. Lambda expression and variable scope
  5. Lambda expression and exception handling
  6. Lambda expression as method parameter
  7. Lambda expression examples in Java 8
  8. How to resolve Local variable defined in an enclosing scope must be final or effectively final error

You may also like -

1 comment:

  1. Raj Gopal Bhallamudi26 March 2017 at 13:26

    An example for reference to an instance method of an arbitrary object of a particular type


    public class Example16 {
    public static void main(String[] args) {
    List res = print(String::toUpperCase,
    Arrays.asList("raj", "gopal", "bhallamudi"));
    System.out.println(res);

    }
    static List print(Functionf , List obj)
    {
    List result = new ArrayList();
    for(String p:obj)
    {
    result.add(f.apply(p));
    }
    return result;
    }
    }


    ReplyDelete