Page Content

Tutorials

What is Method Reference in Java with its Types?

Method Reference in Java

Method Reference in Java, which function as a particular kind of lambda expression, offer a succinct way to refer to methods without actually running them. Similar to lambda expressions, they also need a target type context, which includes a functional interface that works. A functional interface instance is created when a method reference is evaluated.

Method references are mostly used to make code easier to read and self-document, particularly when a lambda expression only invokes an already-existing method. While maintaining binary compatibility with earlier code, they make it possible to add new functionality to library interfaces.

JDK 8 introduced the double colon (::) separator to Java specifically for this purpose, and it is used in the general syntax for a method reference.

There are several types of method references:

Method Reference in Java
Method Reference in Java

Method References to Static Methods

A method reference to a static method is created by specifying the method name preceded by its class name, using the general syntax: ClassName::methodName. This reference can be used anywhere it’s compatible with its target functional interface.

Example: Let’s consider a functional interface IntPredicate that has a method test(int n) which returns a boolean. We also have a class MyIntPredicates with static methods like isPrime(int n) and isEven(int n).

// A functional interface for numeric predicates that operate on integer values.
interface IntPredicate {
    boolean test(int n);
}
// This class defines three static methods that check an integer against some condition.
class MyIntPredicates {
    // A static method that returns true if a number is prime.
    static boolean isPrime(int n) {
        if (n < 2) return false;
        for (int i = 2; i <= n / i; i++) {
            if ((n % i) == 0)
                return false;
        }
        return true;
    }
    // A static method that returns true if a number is even.
    static boolean isEven(int n) {
        return (n % 2) == 0;
    }
}
class MethodRefDemo {
    // This method has a functional interface as its first parameter type.
    static boolean numTest(IntPredicate p, int v) {
        return p.test(v);
    }
    public static void main(String args[]) {
        boolean result;
        // Here, a method reference to isPrime is passed to numTest().
        result = numTest(MyIntPredicates::isPrime, 17);
        if (result) System.out.println("17 is prime.");
        // Next, a method reference to isEven is used.
        result = numTest(MyIntPredicates::isEven, 12);
        if (result) System.out.println("12 is even.");
    }
}

Code Output:

17 is prime.
12 is even.

As an illustration, MyIntPredicates::isPrime gives the implementation of test() in IntPredicate, isPrime evaluates to a reference to an object.

Method References to Instance Methods (Specific Object)

The basic syntax for passing a reference to an instance method on a particular object is objRef::methodName. This syntax is comparable to that of static methods, except instead of using a class name, an object reference is used. In relation to objRef, the method in question functions.

Example: Using the IntPredicate interface, we can define a class MyIntNum that stores an int value and has an instance method isFactor(int n).

// A functional interface for numeric predicates that operate on integer values.
interface IntPredicate {
    boolean test(int n);
}
// This class stores an int value and defines the instance
// method isFactor(), which returns true if its argument is a factor of the stored value.
class MyIntNum {
    private int v;
    MyIntNum(int x) { v = x; }
    int getNum() { return v; }
    boolean isFactor(int n) {
        return (v % n) == 0;
    }
}
class MethodRefDemo2 {
    public static void main(String args[]) {
        boolean result;
        MyIntNum myNum = new MyIntNum(12);
        MyIntNum myNum2 = new MyIntNum(16);
        // Here, a method reference to isFactor on myNum is created.
        IntPredicate ip = myNum::isFactor;
        // Now, it is used to call isFactor() via test().
        result = ip.test(3);
        if (result) System.out.println("3 is a factor of " + myNum.getNum());
        // This time, a method reference to isFactor on myNum2 is created
        // and used to call isFactor() via test().
        ip = myNum2::isFactor;
        result = ip.test(3);
        if (!result) System.out.println("3 is not a factor of " + myNum2.getNum());
    }
}

Code Output:

3 is a factor of 12
3 is not a factor of 16

Here, myNum::isFactor generates a method reference in which, upon invoking test(), isFactor() is called on the myNum object.

Method References to Instance Methods (Arbitrary Object of a Particular Type)

An instance method can be defined that works with any object of a class, not just a particular one. For this, the syntax is ClassName::instanceMethodName. In this case, the class name is utilised rather than a particular object. The invoking object and the method’s argument are matched by the functional interface’s first and second parameters, respectively.

Example: Let’s modify the IntPredicate interface to MyIntNumPredicate, where test() takes a MyIntNum object and an int.

// A functional interface for numeric predicates that operate
// on an object of type MyIntNum and an integer value.
interface MyIntNumPredicate {
    boolean test(MyIntNum mv, int n);
}
// This class stores an int value and defines the instance
// method isFactor(), which returns true if its argument is a factor of the stored value.
class MyIntNum {
    private int v;
    MyIntNum(int x) { v = x; }
    int getNum() { return v; }
    boolean isFactor(int n) {
        return (v % n) == 0;
    }
}
class MethodRefDemo3 {
    public static void main(String args[]) {
        boolean result;
        MyIntNum myNum = new MyIntNum(12);
        MyIntNum myNum2 = new MyIntNum(16);
        // This makes inp refer to the instance method isFactor().
        MyIntNumPredicate inp = MyIntNum::isFactor;
        // The following calls isFactor() on myNum.
        result = inp.test(myNum, 3);
        if (result)
            System.out.println("3 is a factor of " + myNum.getNum());
        // The following calls isFactor() on myNum2.
        result = inp.test(myNum2, 3);
        if (!result)
            System.out.println("3 is not a factor of " + myNum2.getNum());
    }
}

Code Output:

3 is a factor of 12
3 is not a factor of 16

MyIntNum::isFactor in this case generates a method reference that may be used with any MyIntNum object. When inp.test(myNum, 3) is called, it translates to myNum.isFactor(3).

Constructor References

A constructor can be accessed without calling it by using a constructor reference. A constructor reference has the following syntax: ClassName::new. If the method of the functional interface accepts parameters, the relevant parameterised constructor will be referred to.

Example: Assume a functional interface MyFunc where func() returns an object of type MyClass and takes a String parameter. And MyClass has a corresponding constructor MyClass(String s).

// MyFunc is a functional interface whose method takes a String and returns a MyClass object.
interface MyFunc {
    MyClass func(String s);
}
class MyClass {
    private String str;
    // A constructor that takes an argument.
    MyClass(String s) { str = s; }
    // The default constructor.
    MyClass() { str = ""; }
    String getStr() { return str; }
}
class ConstructorRefDemo {
    public static void main(String args[]) {
        // Create a reference to the MyClass constructor.
        // Because func() in MyFunc takes an argument, new refers to the parameterized constructor in MyClass.
        MyFunc myClassCons = MyClass::new;
        // Create an instance of MyClass via that constructor reference.
        MyClass mc = myClassCons.func("Testing");
        // Use the instance of MyClass just created.
        System.out.println("str in mc is " + mc.getStr());
    }
}

Code Output:

str in mc is Testing

The MyClass(String s) constructor is referred to here by MyClass::new since MyFunc.func() requires a String argument.

Additionally, there is a reference type for an array constructor, TypeName[]::new, which accepts an int size and returns new TypeName[size].

Benefits and Usage

Code is more understandable and succinct when method references are used instead of similar lambda expressions, particularly when interacting with the Streams API. They provide a more fluid programming style in which process steps are immediately and simply comprehended. Additionally, they are useful when working with generic classes and methods, as it is frequently possible to infer the type argument. As a result, more processes start documenting themselves.

Analogy

Consider method references to be similar to a shortcut button on your TV remote control for a particular function. Rather than clicking “Menu,” going to “Picture Settings,” and then choosing “Brightness,” you may have a button specifically labelled “Brightness.” Writing down the order in which buttons are pressed for a certain task is similar to using a lambda expression. A method reference functions similarly to a single button that has that sequence pre-programmed into it. Because the action (e.g., “turn up the brightness”) is already clearly defined and labelled, it accomplishes the same goal but is simpler to use and more straightforward.

Index