The Double Colon Operator in Java 8

In this article of Java 8, we will look at the double colon operator introduced in Java 8.

 

Introduction

Java 8 introduced method reference or double colon operator. The main intention of this new operator is to make the code more readable and to reduce some code. Method reference is called a double colon operator because double colon(::) is used to describe it.In this tutorial, we will look into the method reference or double colon operator with different examples of its use.

 

1.  Comparison with a lambda expression and double colon operator:

We use double colon operator to make the code more readable by replacing a lambda expression. For example, we can use the below code snippet to print the values of a list ‘numList’:

numList.forEach(e -> System.out.print(e));

Inside the ‘forEach‘ method, we are using one lambda expression to print the values for each element ‘e’. But the lambda function is just a call to an existing method in this example. Java 8  introduced method reference or double colon operator to replace these types of lambda expressions. Using double colon operator, it becomes:

numList.forEach(System.out::print)

Now, the code is more compact and easy to read. We place the class name before the double colon and we place the method name after the double colon with no argument. Note we can’t use method reference for all lambda expressions. We can use it only with a single method lambda expression. Also, remember that no parenthesis used when we refer to a method using method reference or double colon operator.

Most Java IDE like IntelliJ-IDEA or eclipse will show you one notification to convert a lambda expression to a method reference if it is a valid statement.

 

2. Types of method Reference

There are four different types of method reference available in Java :

  1. Static method reference.

  2. Instance method reference of a particular object.

  3. Instance method reference of an arbitrary object of a particular type.

  4. Constructor reference.

Let’s take one example to understand it more clearly.We will use one common ‘Student’ class throughout the article. This class looks like as below :

class Student {
    private String name;
    private int marks;
    private String gender;

    Student() {
        this.name = "none";
        this.marks = -1;
        this.gender = "none";
    }

    Student(String name, String gender) {
        this.name = name;
        this.gender = gender;
        this.marks = -1;
    }

    Student(String name, int marks, String gender) {
        this.name = name;
        this.marks = marks;
        this.gender = gender;
    }

    public static int compareMarks(Student s1, Student s2) {
        return s1.marks - s2.marks;
    }

    public static Student compareMarksOfTwo(Student s1, Student s2) {
        return s1.marks > s2.marks ? s1 : s2;
    }

    public int getMarks() {
        return marks;
    }

    public String getGender() {
        return gender;
    }

    public String getName() {
        return name;
    }
}

We use this class to hold information about a student. It can store the name, marks and gender of a student. Three different constructors are available to create an object of ‘Student’ class. We can create one ‘Student’ object passing no arguments to the constructor or by passing ‘name’ and ‘gender’ or by passing all ‘name’, ‘marks’, and ‘gender’.

It also has two ‘static’ method to compare the marks of two students. One method takes two ‘Student’ objects as an argument and returns the difference of marks between them. The other method returns the object with larger ‘marks’.

 

3 Static Method Reference

We use a static method reference to refer to a static method of a specific class. We can define it as below:

ClassName :: staticMethodName

As you can see that double colon operator (::) is used to refer to the static method (staticMethodName) of the class ‘ClassName‘. Also, we don’t pass any argument to the static method. Let’s create one ‘Main’ class to use the above ‘Student’ class :

import java.util.Arrays;

class Main {
 public static void main(String[] args) {
  Student[] studentArray = new Student[3];

  studentArray[0] = new Student("Alex", 24, "male");
  studentArray[1] = new Student("Liza", 22, "female");
  studentArray[2] = new Student("Bob", 26, "male");

  Arrays.sort(studentArray, (Student s1, Student s2) -> Student.compareMarks(s1, s2));

  for (Student s: studentArray) {
   System.out.print(s.getName() + " ");
  }
 }
}

 

3.1 Example 1

Here, we have created three ‘Student’ objects with different properties and added them to an array ‘studentArray’. Later, we are sorting the array using ‘Arrays.sort’ method with a lambda expression. The lambda expression uses ‘compareMarks’ static method defined in the ‘Student’ class.

This method is a static method and we can call this method using the double colon operator or method reference. With method reference, the sorting statement looks like below:

Arrays.sort(studentArray, Student::compareMarks);

Semantically, this method reference statement is the same as the lambda expression. If you run the program, it will print the following output: Liza Alex Bob Because the marks of Liza is less than Alex and the marks of Alex is less than Bob.

 

3.2 Example 2

We can also use predefined functional interface to refer a static method in a class. For example:

BiFunction<Student,Student,Student> comparator = Student::compareMarksOfTwo;
    Student result = comparator.apply(studentArray[0],studentArray[1]);
    System.out.println(result.getName()+ " "+result.getMarks()+" "+result.getGender());

Here, we are using ‘BiFunction’ interface to compare two students based on the marks. BiFunction has three parameters. The first parameter is the first argument to the function, the second parameter is the type of the second argument to the function and the third parameter is the result of the function. The above code snippet will find out the student with the higher mark by comparing the first and the second student of the array.It will print Alex 24 male

 

3.3 Example 3

Another way to solve this problem by using a new functional interface. Suppose, we want to compare the marks of two students without using a ‘BiFunction‘. To do that, create one interface first:

interface IStudentComparator{
        Student getResult(Student s1,Student s2);
}

Now, we can compare two students like below.This will have same output as previous example

IStudentComparator comparator = Student::compareMarksOfTwo;
    Student result = comparator.getResult(studentArray[0],studentArray[1]);
    System.out.println(result.getName()+ " "+result.getMarks()+" "+result.getGender())

 

4 Instance Method Reference

In the above example, we have used a method reference to a pre-defined static method. Similarly, we can also use the double colon operator to refer to any instance method of a particular object.

 

4.1 Example 1

Create one new class ‘NameComparator’ to compare the name of two ‘Student’ objects:

public class NameComparator {
 public int compareNames(Student s1, Student s2) {
  return s1.getName().compareTo(s2.getName());
 }
}

This class has one method ‘compareNames’ to compare the names of two ‘Student’ objects it receives as the arguments. Notice that this method is not a static method. If we want to use it, we need one object of type ‘NameComparator’.We can use it to sort the students of the previous example alphabetically:

NameComparator comparator = new NameComparator();
Arrays.sort(studentArray, (Student s1, Student s2) -> comparator.compareNames(s1, s2));

Here, ‘comparator‘ is an object of type ‘NameComparator‘. We are using one lambda expression that uses the ‘comparator’ object to call the instance method ‘compareNames’ defined in the ‘NameComparator’ class. It takes two ‘Student’ object as its parameter. Using method reference, we can write it like below:

Arrays.sort(studentArray, comparator::compareNames);

This is like the previous example of ‘static method reference’. The only difference is that we are using one object of a particular type to invoke an instance method. The JRE can infer the arguments of the method. If you run the above example, it will print out the names of the students in alphabetical order: Alex Bob Liza

 

4.2 Example 2

We can also use BiFunction with method reference:

NameComparator comparator = new NameComparator();
 BiFunction < Student, Student, Student > biFunction = comparator::compareMarks;
 Student result = biFunction.apply(studentArray[0], studentArray[1]);
 System.out.println(result.getName() + " " + result.getMarks() + " " + result.getGender());

It will print Alex 24 male . BiFunction is a pre defined interface and we can use its method ‘apply’ to invoke the instance method referred by the method reference.

 

4.3 Example 3

We can also use one anonymous object with method reference like below:

Student result = new NameComparator().compareMarks(studentArray[0], studentArray[1]);
 System.out.println(result.getName() + " " + result.getMarks() + " " + result.getGender());

 

4.4 Example 4

Similar to the static method reference, we can use functional interface with instance method reference:

IStudentComparator comparator = new NameComparator()::compareMarks;
Student result = comparator.getResult(studentArray[0], studentArray[1]);
System.out.println(result.getName() + " " + result.getMarks() + " " + result.getGender());

Here, we are using one anonymous object. But you can also use any normal object.

 

5. Instance Method Reference of an Arbitrary Object of a Particular Type

A method reference can be used to an instance method of an arbitrary object of a particular type. For example:

String[] nameArray = new String[3];
  int i = 0;
  for (Student s: studentArray) {
    nameArray[i++] = s.getName();
  }

'nameArray' is an array of the names of all ‘Students’ i.e. ['Alex', 'Liza', 'Bob']. We can sort this array using a lambda expression like below:

Arrays.sort(nameArray,(String s1,String s2) -> s1.compareTo(s2));

‘s1’ and ‘s2’ are arbitrary names used to describe the first and the second string. We are using the instance method ‘compareTo’ to compare these strings. The equivalent method reference for the above lambda expression is:Arrays.sort(nameArray,String::compareTo);.

 

6. Constructor Reference

We can use method reference with a constructor to create an object. The process is different for different argument constructors. The ‘Student’ class has three constructors one with no parameters, one with two parameters and one with three parameters. Let’s check how we can use constructor reference with each one of these constructors:

 

6.1 Constructor with no Parameter

The constructor with no parameters of the ‘Student’ class will assign the ‘name’, ‘gender’ as ‘none’ and ‘marks’ as ‘-1’

Student() {
 this.name = "none";
 this.marks = -1;
 this.gender = "none";
}

To use constructor reference for this constructor, create one interface in the main class :

interface IStudent {
  Student create();
 }

You can now use it like below:

IStudent iStudent = Student::new;
Student s = iStudent.create();
System.out.println(s.getName() + " " + s.getMarks() + " " + s.getGender());

It will print "none -1 none" as the output.

 

6.2  Constructor with two Parameter

If your constructor has two parameters, you can use ‘BiFunction’ to create one object of that class type. You can use the method ‘apply’ defined in the 'BiFunction' class. For example:

BiFunction < String, String, Student > biFunction = Student::new;
Student s = biFunction.apply("Alex", "male");
System.out.println(s.getName() + " " + s.getMarks() + " " + s.getGender());

It will invoke the constructor that takes two parameters of the ‘Student’ class. The first argument will assign the ‘name’ variable and the second argument will assign the ‘gender’ variable. The output is “Alex -1 male” for this program.

 

6.3  Constructor with three or over three parameters

If we have a constructor with three or more than three parameters, we will have to create our own functional interface. The functional interface is an interface in Java that contains only one abstract method. To ensure that the functional interface contains only one abstract method, we can use the @FunctionalInterface annotation.Student class has one constructor with three parameters. To use it with method reference, write the following functional interface in the main class.

@FunctionalInterface
interface IStudentCreator {
 Student createStudent(String name, int marks, String gender);
}

Now, we can use it with method reference:

IStudentCreator studentCreator = Student::new;
Student s = studentCreator.createStudent("Alex", 24, "male");
System.out.println(s.getName() + " " + s.getMarks() + " " + s.getGender());

The output will be “Alex 24 male” for this example.Similarly, if you have a constructor with four or more parameters, you can create one functional interface to use the constructor reference as shown above.

 

7  Summary

In this tutorial, we have learned how to use method reference in Java with different examples. Lambda expression is used to replace an anonymous class and method reference can be used to replace that lambda expression if it uses only one method. Method reference or double colon operator makes the code cleaner and helps us to reduce the boiler-plate code.

Scroll to Top