Method Reference in Java 8

Method Reference in Java 8 allows a functional interface method to be mapped to a specific method using the :: (double colon) operator. This technique simplifies the implementation of functional interfaces by directly referencing existing methods. The referenced method can be either a static method or an instance method. It’s important that the functional interface method and the specified method have matching argument types, while other elements such as return type, method name, and modifiers can differ.

If the specified method is a static method, the syntax is:

ClassName::methodName

If the method is an instance method, the syntax is:

ObjectReference::methodName

A functional interface can refer to a lambda expression and can also refer to a method reference. Therefore, a lambda expression can be replaced with a method reference, making method references an alternative syntax to lambda expressions.

Example with Lambda Expression

class Task {
    public static void main(String[] args) {
        Runnable r = () -> {
            for (int i = 0; i <= 10; i++) {
                System.out.println("Child Thread");
            }
        };
        Thread t = new Thread(r);
        t.start();

        for (int i = 0; i <= 10; i++) {
            System.out.println("Main Thread");
        }
    }
}

Example with Method Reference

class Task {
    public static void printChildThread() {
        for (int i = 0; i <= 10; i++) {
            System.out.println("Child Thread");
        }
    }

    public static void main(String[] args) {
        Runnable r = Task::printChildThread;
        Thread t = new Thread(r);
        t.start();

        for (int i = 0; i <= 10; i++) {
            System.out.println("Main Thread");
        }
    }
}

In the above example, the Runnable interface’s run() method is referring to the Task class’s static method printChildThread().

Method Reference to an Instance Method

interface Processor {
    void process(int i);
}

class Worker {
    public void display(int i) {
        System.out.println("From Method Reference: " + i);
    }

    public static void main(String[] args) {
        Processor p = i -> System.out.println("From Lambda Expression: " + i);
        p.process(10);

        Worker worker = new Worker();
        Processor p1 = worker::display;
        p1.process(20);
    }
}

In this example, the functional interface method process() is referring to the Worker class instance method display().

The main advantage of method references is that we can reuse existing code to implement functional interfaces, enhancing code reusability.

Constructor Reference in Java 8

We can use the :: (double colon) operator to refer to constructors as well.

Syntax:

ClassName::new
Example:
class Product {
    private String name;

    Product(String name) {
        this.name = name;
        System.out.println("Constructor Executed: " + name);
    }
}

interface Creator {
    Product create(String name);
}

class Factory {
    public static void main(String[] args) {
        Creator c = name -> new Product(name);
        c.create("From Lambda Expression");

        Creator c1 = Product::new;
        c1.create("From Constructor Reference");
    }
}

In this example, the functional interface Creator is referring to the Product class constructor.

Note: In method and constructor references, the argument types must match.

Here is the link for the Java 8 quiz:
Click here

Related Articles:

Java 8 Functional Interfaces: Features and Benefits

Java 8 Functional Interfaces: Features and Benefits

Java 8 functional interfaces, which are interfaces containing only one abstract method. The method itself is known as the functional method or Single Abstract Method (SAM). Examples include:

Predicate: Represents a predicate (boolean-valued function) of one argument. Contains only the test() method, which evaluates the predicate on the given argument.

Supplier: Represents a supplier of results. Contains only the get() method, which returns a result.

Consumer: Represents an operation that accepts a single input argument and returns no result. Contains only the accept() method, which performs the operation on the given argument.

Function: Represents a function that accepts one argument and produces a result. Contains only the apply() method, which applies the function to the given argument.

BiFunction: Represents a function that accepts two arguments and produces a result. Contains only the apply() method, which applies the function to the given arguments.

Runnable: Represents a task that can be executed. Contains only the run() method, which is where the task logic is defined.

Comparable: Represents objects that can be ordered. Contains only the compareTo() method, which compares this object with the specified object for order.

ActionListener: Represents an action event listener. Contains only the actionPerformed() method, which is invoked when an action occurs.

Callable: Represents a task that returns a result and may throw an exception. Contains only the call() method, which executes the task and returns the result.

Java 8 Functional Interfaces

Benefits of @FunctionalInterface Annotation

The @FunctionalInterface annotation was introduced to explicitly mark an interface as a functional interface. It ensures that the interface has only one abstract method and allows additional default and static methods.

In a functional interface, besides the single abstract method (SAM), any number of default and static methods can also be defined. For instance:

interface ExampleInterface {
    void method1(); // Abstract method

    default void method2() {
        System.out.println("Hello"); // Default method
    }
}

Java 8 introduced the @FunctionalInterface annotation to explicitly mark an interface as a functional interface:

@FunctionalInterface
interface ExampleInterface {
    void method1();
}

It’s important to note that a functional interface can have only one abstract method. If there are more than one abstract methods, a compilation error occurs.

Functional Interface in java

Inheritance in Functional Interfaces

If an interface extends a functional interface and does not contain any abstract methods itself, it remains a functional interface. For example:

@FunctionalInterface
interface A {
    void methodOne();
}

@FunctionalInterface
interface B extends A {
    // Valid to extend and not add more abstract methods
}

However, if the child interface introduces any new abstract methods, it ceases to be a functional interface and using @FunctionalInterface will result in a compilation error.

Lambda Expressions and Functional Interfaces:

Lambda expressions are used to invoke the functionality defined in functional interfaces. They provide a concise way to implement functional interfaces. For example:

Without Lambda Expression:

interface ExampleInterface {
    void methodOne();
}

class Demo implements ExampleInterface {
    public void methodOne() {
        System.out.println("Method one execution");
    }

    public class Test {
        public static void main(String[] args) {
            ExampleInterface obj = new Demo();
            obj.methodOne();
        }
    }
}

With Lambda Expression:

interface ExampleInterface {
    void methodOne();
}

class Test {
    public static void main(String[] args) {
        ExampleInterface obj = () -> System.out.println("Method one execution");
        obj.methodOne();
    }
}

Advantages of Lambda Expressions:

  1. They reduce code length, improving readability.
  2. They simplify complex implementations of anonymous inner classes.
  3. They can be used wherever functional interfaces are applicable.

Anonymous Inner Classes vs Lambda Expressions:

Lambda expressions are often used to replace anonymous inner classes, reducing code length and complexity. For example:

With Anonymous Inner Class:

class Test {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("Child Thread");
                }
            }
        });
        t.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("Main Thread");
        }
    }
}

With Lambda Expression:

class Test {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("Child Thread");
            }
        });
        t.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("Main Thread");
        }
    }
}

Differences between Anonymous Inner Classes and Lambda Expressions

Anonymous Inner ClassLambda Expression
A class without a nameA method without a name (anonymous function)
Can extend concrete and abstract classesCannot extend concrete or abstract classes
Can implement interfaces with any number of methodsCan only implement interfaces with a single abstract method
Can declare instance variablesCannot declare instance variables; variables are treated as final
Has separate .class file generated at compilationNo separate .class file; converts into a private method
In summary, lambda expressions offer a concise and effective way to implement functional interfaces, enhancing code readability and reducing complexity compared to traditional anonymous inner classes.
Click here

Related Articles: