Method References in Java 8: Types

In Java 8 method references has introduced, which will refer to methods or constructors without invoking them. These syntactic shortcuts create lambdas from existing methods or constructors. Basically, lambda expressions in Java are methods implemented without an enclosing class body and constructors created by lambdas are often referred to as constructor references, which consider to be a subset of method references.

In short, not all methods can be implemented as lambdas. But only those which declared inside candidate functional interface (an interface which contains only one abstract method called as functional interface) are the privileged ones.

So, we will see here how we can convert method parameters based on anonymous implementations of functional interfaces into lambdas:

Any implementation of a Functional interface can be converted to lambda expression.

When we will observe here, all what we need to be done is eliminating the elements which could be implicitly available to the compiler and separating the parameters and the actual expression by “->” operator.

So, one question here arises then when we should go for method references?
Simply we can use method references anywhere we can use Lambda Expressions, which means that a Functional Interface is needed, but only if the Lambda Expression would invoke a single, already defined, method & do nothing else.
The method signature must also match that of the Functional Interface being used. For eg, when used in conjunction with a Predicate, which requires a single input argument & returns a Boolean value, that method would need to accept an argument & return a Boolean value.
We will not be able to use method references if we need to invoke more than one method within a Lambda Expression or if we need to pass extra arguments into the method.

In Java 8, we can perform method reference by following 4 types:

  1. Reference to a static type:
  
 package com.advance.computing.java8.methodReference.staticMethods;
  
 public class ReferencesToStaticMethodsDemo {
      
      public static void main(String[] args)
         {
            new Thread(ReferencesToStaticMethodsDemo::getThreadValue).start();
            new Thread(() ->
 getThreadValue()).start();
            new Thread(new Runnable()
                       {
                          @Override
                          public void run()
                          {
                               getThreadValue();
                          }
                       }).start();
         }
  
         static void getThreadValue()
         {
            String name = Thread.currentThread().getName();
            for (int i = 0; i < 20; i++)
            {
               System.out.printf("%s: %d%n", name, i);
              
               try
               {
                  Thread.sleep((int) (Math.random()*50));
               }
               catch (InterruptedException ie
)
               {
                 ie.printStackTrace();
               }
            }
         }
 } 
OutPut:

 Thread-0: 0
 Thread-1: 0
 Thread-1: 1
 Thread-1: 2
 Thread-0: 1
 Thread-1: 3
 Thread-0: 2
 Thread-0: 3
 Thread-0: 4
 Thread-0: 5
 Thread-0: 6
 Thread-0: 7
 Thread-1: 4
 Thread-1: 5
 Thread-2: 0
 Thread-1: 6
 Thread-2: 1
 Thread-2: 2
 Thread-2: 3
 Thread-1: 7
 Thread-2: 4
 Thread-2: 5
 Thread-2: 6
 Thread-2: 7 

Here, there are three ways to pass a unit of work described by the getThreadValue() method to a new Threadobject whose associated thread is started:

  • It will pass a method reference to the static getThreadValue() method
  • It will pass  an equivalent lambda whose code block executes getThreadValue()
  • It will pass an instance of an anonymous class that implements Runnable and runs getThreadValue() method

2. Reference to an Instance Method of a Particular Object

The object reference on which an instance method is invoked is known as the Receiver of the method invocation.

We can specify the receiver of the method invocation: provide it implicitly when the method is invoked.

a) Bound receiver it can provide Explicitly when the method is invoked,

b) Unbound receiver it can provide implicitly when the method is invoked.

a) Bound receiver:

Syntax: objectRef::instanceMethod

 package com.advance.computing.java8.methodReference.instanceMethod;
    
 import   java.util.function.Function;
    
 public class   ReferencesToInstanceBound {
             
                 public Integer factorial(int n) {         // instance method 
                        if(n==0   || n==1){
                                  return 1;
                        }
                   return n *   factorial(n-1);
                 }
    
                 public static void   main(String[] args) {
                        
                      //using   lambda  
                      ReferencesToInstanceBound cal = new ReferencesToInstanceBound();
                   Function<Integer, Integer> funLambda = (a) ->   cal.factorial(a);
                   System.out.println("By   Using lambda expression: "+funLambda.apply(4));
    
                   // bound type
                   Function<Integer, Integer> funBoundType = cal::factorial;
                   System.out.println("Using   a method References To Instance bound type: "+funBoundType.apply(5));
    
                 }
   }      

OutPut:
By References To Instance: 24
References To Instance bound type: 120

b) Unbound receiver:

package com.advance.computing.java8.methodReference.instanceMethod;
    
import   java.util.function.BiFunction;
    
public class   ReferencesToInstanceUnBound {
    
             public Integer factorial(int   n) { // instance method
                      if (n == 0 || n ==   1) {
                                return 1;
                      }
                      return n *   factorial(n - 1);
             }
    
             public static void   main(String[] args) {
    
                      ReferencesToInstanceUnBound   referencesToInstanceUnBound = new ReferencesToInstanceUnBound();
                      // using lambda
    
                      BiFunction<ReferencesToInstanceUnBound,   Integer, Integer> funLambda = (a, b) -> a.factorial(b);
                      System.out
.println("By   Using lambda expression: " +   funLambda.apply(referencesToInstanceUnBound, 5));
    
                      // UnBound type
                      BiFunction<ReferencesToInstanceUnBound,   Integer, Integer> funUnBoundType = ReferencesToInstanceUnBound::factorial;
                      System.out
.println("Using   a method References To Instance Unbound type: "
                                         + funUnBoundType.apply(referencesToInstanceUnBound,   6));
    
             }
   }
       

OutPut:
By Using lambda expression: 120
Using a method References To Instance Unbound type: 720

BiFunction : Represents a function that accepts two arguments and produces a result. This is the two-arity specialization of Function.

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

package com.advance.computing.java8.methodReference.instanceMethodArbitrary;
    
   import   java.util.function.Function;
    
   interface   Factorial {
             default int   calculate(int n) {
                      return n ==   0 || n == 1 ? 1 : n * calculate(n -   1);
             }
   }
    
   public class   ReferenceToInstanceArbitraryObj implements   Factorial {
    
             @Override
             public int   calculate(int n) { // calculate Fibonacci series using recursion
                      return n   <= 1 ? n : calculate(n - 1) + calculate(n -   2);
    
             }
    
             public void   calculateValue(int value) {
                      //   By Using Uses this::calculate method
                      Function<Integer,   Integer> function1 = this::calculate;
                      System.out.println("this::calculate():   Fibonacci Series = ");
                      for (int i =   0; i <= value; i++) {
                                System.out.print(function1.apply(i) + "   ");
                      }
                      //   By Using Factorial.calculate() method
                      Function<Integer,   Integer> function2 = Factorial.super::calculate;
                      System.out.println("\nFactorial::calculate():   Factorial = " + function2.apply(value));
             }
    
             public static void   main(String[] args) {
                      ReferenceToInstanceArbitraryObj   obj = new ReferenceToInstanceArbitraryObj();
                      obj.calculateValue(6);
             }
   }
          

Output:
this::calculate(): Fibonacci Series = 0 1 1 2 3 5 8
Factorial::calculate(): Factorial = 30

4. Reference to a constructor

package com.advance.computing.java8.methodReference.toConstructor;
    
   @FunctionalInterface
   interface   ISquare {
        Square getSquare(int fact);
   }
    
   class Square {
        public Square(int n) {
                 System.out.print("Sqauare   Value by Reference to a constructor: " + n * n);
        }
   }
    
   public class   MethodRefToCnstructor {
    
        public static void main(String[] args) {
                 ISquare sqr = Square::new;
                 sqr.getSquare(6);
    
        }
    
   }      

Output:
Sqauare Value by Reference to a constructor: 36

Leave a Reply

Your email address will not be published. Required fields are marked *