Blog Java 8 Features

Mastering Method References in Java 8

Introduction

In Java 8, method references have been introduced, which are used to refer to methods by their names instead of invoking them directly.

These syntactic shortcuts create a cleaner and more readable code. While Java provides its own set of predefined functional interfaces, we can also define our custom method references.

👉Method references provide a concise syntax for referring to existing methods or constructors, making code more readable and expressive, especially when used in combination with lambda expressions and functional interfaces.

In this blog post, we’ll dive deep into the world of method references, exploring their different types, usage, and best practices.

Method references can be categorized into four types

  1. Static Method Reference
  2. Instance Method Reference
  3. Constructor Reference
  4. Class Method Reference

Let’s explore each type with examples:

1. Static Method Reference

A static method reference is used to refer to a static method of a class. The syntax is ClassName::methodName.

Example:

Java
import java.util.Arrays;

public class StaticMethodReferenceExample {
    public static void main(String[] args) {
        int[] numbers = {3, 1, 4, 1, 5, 9};

        // Using a static method reference to find the maximum value in the array
        int maxValue = Arrays.stream(numbers)
                             .reduce(Math::max)  // Here, Math::max is a static method reference
                             .orElse(0);

        System.out.println("Max value: " + maxValue); // Output: Max value: 9
    }
}

In the example above, Math::max is a static method reference that refers to the max method of the Math class.

2. Instance Method Reference

An instance method reference is used to refer to an instance method of an object. The syntax is objectInstance::methodName or ClassName::methodName (for non-static methods).

Example:

Java
import java.util.Arrays;
import java.util.List;

public class MethodReferenceExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("John", "Jane", "Jim", "Joan");
        names.forEach(System.out::println); // Instance method reference
    }
}

In this example, System.out::println is an instance method reference that refers to the println method of the System.out object.

3. Constructor Reference

A constructor reference is used to refer to a constructor of a class. The syntax is ClassName::new.

Example:

Java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MethodReferenceExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> doubledNumbers = numbers.stream()
                                              .map(Integer::new) // Constructor reference
                                              .collect(Collectors.toList());
        System.out.println(doubledNumbers); // Output: [1, 2, 3, 4, 5]
    }
}

In this example, Integer::new is a constructor reference that refers to the constructor of the Integer class.

4. Class Method Reference

A class method reference is used to refer to a method of a class that takes an instance of that class as the first argument. The syntax is ClassName::methodName.

Example:

Java
import java.util.Arrays;
import java.util.List;

public class MethodReferenceExample {
    public static void main(String[] args) {
        List<String> strings = Arrays.asList("apple", "banana", "cherry");
        strings.stream()
               .map(String::toUpperCase) // Class method reference
               .forEach(System.out::println);
    }
}

In this example, String::toUpperCase is a class method reference that refers to the toUpperCase method of the String class.

Creating a Custom Method Reference

A custom method reference can be created by defining a method in a class and then using the :: operator to refer to that method. Here’s an example:

Java
class MyComparator {
    public int compareByNameLength(String str1, String str2) {
        return Integer.compare(str1.length(), str2.length());
    }
}

In this example, compareByNameLength is a custom method that compares two strings based on their length.

Using a Custom Method Reference

A custom method reference can be used in place of a lambda expression or an anonymous class wherever a functional interface is expected. Here’s how you can use the custom method reference we defined earlier:

Java
MyComparator myComparator = new MyComparator();
String[] stringArray = {"Steve", "Rick", "Aditya", "Negan", "Lucy", "Jon"};
Arrays.sort(stringArray, myComparator::compareByNameLength);

In this code, myComparator::compareByNameLength is a custom method reference. The Arrays.sort method expects a Comparator as the second argument. In this case, we’re providing a reference to the compareByNameLength method of the myComparator object, which acts as a Comparator.

Why Doesn’t It Give an Error?

You might wonder why the code doesn’t give an error when we pass myComparator::compareByNameLength to Arrays.sort, even though Arrays.sort expects a Comparator and we’re passing a MyComparator.

The reason is that myComparator::compareByNameLength is not a MyComparator – it’s a method reference that matches the signature of the compare method in the Comparator interface. The compare method in the Comparator interface takes two strings and returns an integer, just like compareByNameLength. Therefore, myComparator::compareByNameLength can be used wherever a Comparator<String> is expected, and no error occurs.

This is a powerful feature of Java 8 that allows for more concise and readable code. It’s part of the larger theme of functional programming in Java, which includes features like lambda expressions and streams.

Benefits of Using Method References

  1. Concise Syntax: Method references offer a more compact and readable syntax compared to lambda expressions, especially when referring to existing methods or constructors.
  2. Improved Readability: By using method references, your code becomes more self-documenting and easier to understand, as the method reference itself conveys the intended operation.
  3. Compatibility with Functional Interfaces: Method references seamlessly integrate with functional interfaces, enabling you to leverage the power of lambda expressions and functional programming.
  4. Code Reusability: Method references promote code reusability by allowing you to pass existing methods as arguments to higher-order functions, reducing code duplication and increasing modularity.

Best Practices and Considerations:

  1. Readability First: While method references can make your code more concise, readability should always be the top priority. Use method references judiciously, and avoid using them in cases where they might compromise code clarity.
  2. Lambda Expressions vs. Method References: In some cases, lambda expressions might be more appropriate than method references, especially when the logic is more complex or involves multiple statements.
  3. Naming Conventions: When using method references with lambda expressions, follow naming conventions that make the code self-documenting and easy to understand.
  4. Static Imports: Consider using static imports for frequently used static methods to improve code readability and reduce verbosity.
  5. Testing and Refactoring: As with any code, ensure that you have proper test coverage and follow refactoring practices to maintain code quality and maintainability.

Conclusion:

Method references are a powerful addition to Java 8, enabling developers to write more concise, readable, and expressive code. By leveraging method references in combination with lambda expressions and functional interfaces, you can improve code reusability, reduce code duplication, and enhance the overall quality of your codebase. Embrace this feature, follow best practices, and experience the productivity gains it offers in your Java development projects.

Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over