For any developer aiming to excel in their craft, being familiar with Java 8 and its various features offers a tremendous boost. One such integral feature is the seamless use of lambda expressions to avoid the creation of superfluous objects for functional interfaces.
Consider a situation where you are working on an application that already includes a perfectly implemented functional method inside a class from a functional interface. A common instinct might be to use a lambda expression for this function, but wouldn’t it be more efficient if we could reference the existing methods instead? This is where the power of method references in Java 8 comes into play.
In the earlier versions of Java, before 1.8, developers had to implement interfaces using separate classes. Let’s explore how this works using the example of a simple interface, PrintHello, that includes a single method, print.
package com.java4coding;
interface PrintHello {
public void print();
}
class PrintHelloImpl implements PrintHello {
public void print() {
System.out.println("Hello");
}
}
public class Demo {
public static void main(String[] args) {
PrintHello printHello = new PrintHelloImpl();
printHello.print();
}
}
With the advent of Java 1.8, developers were introduced to lambda expressions – a more succinct and efficient way to implement functional interfaces directly in the main class, circumventing the need for a separate class altogether.
interface PrintHello {
public void print();
}
public class LambdaExpressionDemo {
public static void main(String[] args) {
PrintHello printHello = () -> {System.out.println("Hello");};
printHello.print();
}
}
Not stopping at lambda expressions, the developers of Java 1.8 introduced yet another upgrade – method references. This feature offers developers the possibility to reference already implemented methods in other classes for use in functional interfaces.
Let’s demonstrate this using the previous example:
package com.java4coding;
interface PrintHello {
public void print();
}
class SayHello {
public void sayHello() {
System.out.println("Hello");
}
}
public class Demo {
public static void main(String[] args) {
SayHello sayHello = new SayHello();
PrintHello printHello = sayHello::sayHello;
printHello.print();
}
}
Java’s method reference feature introduces an innovative way to call methods by referring to them with the help of the operator :: without invoking them. This paradigm can be separated into three main types, each with its own usage and purpose:
In the realm of Java, static methods belong to the class rather than an instance of it. They can be invoked without creating an object of the class, making them perfect candidates for method references. The syntax for creating a static method reference is ClassName::methodName.
Let’s consider a practical example, using a class Multiply that has a static method multiplyByTwo(), which takes a single integer as input and returns the product of that integer and 2. A typical invocation might look like this:
public class Multiply {
public static int multiplyByTwo(int num){
return num * 2;
}
}
public class Main {
public static void main(String[] args) {
Multiply.multiplyByTwo(5); // returns 10
}
}
However, with static method references, we could make this even more concise:
public class Main {
public static void main(String[] args) {
IntUnaryOperator multiplyByTwo = Multiply::multiplyByTwo; // method reference
System.out.println(multiplyByTwo.applyAsInt(5)); // prints 10
}
}
Unlike static methods, instance methods pertain to an instance of a class. Thus, these methods require an object of the class to be invoked. The syntax for an instance method reference is instanceName::methodName.
To illustrate, let’s rework our previous example to make multiplyByTwo() an instance method:
public class Multiply {
public int multiplyByTwo(int num){
return num * 2;
}
}
public class Main {
public static void main(String[] args) {
Multiply multiplyInstance = new Multiply();
IntUnaryOperator multiplyByTwo = multiplyInstance::multiplyByTwo; // method reference
System.out.println(multiplyByTwo.applyAsInt(5)); // prints 10
}
}
Dealing with complex object creation scenarios can be made simpler using constructor references in Java. Constructor references allow you to reference a class’s constructor as if it were any other method. The syntax for a constructor reference is ClassName::new.
Let’s visualize this with a Person class:
Let’s visualize this with a Person class:
public class Person {
private String name;
public Person(String name){
this.name = name;
}
public String getName() {
return this.name;
}
}
public class Main {
public static void main(String[] args) {
Function<String, Person> personCreator = Person::new; // constructor reference
Person john = personCreator.apply("John");
System.out.println(john.getName()); // prints "John"
}
}
In conclusion, the advent of method references in Java has revolutionized the way developers approach functional interfaces. It’s a brilliant tool that enables efficient referencing to existing methods, whether they are static, instance, or constructor methods. These method references lead to cleaner, more readable, and more maintainable code, enhancing the overall development process. It’s another testament to Java’s continual adaptation to provide solutions for common coding scenarios, cementing its place as a mainstay in the programming world.
If you’re eager to enhance your Java programming abilities, it’s advisable to also explore how to efficiently clear all elements in a collection, delving into methods such as clear(). Understanding this concept will broaden your understanding of collection manipulation in Java.