Functional Interfaces and Lambda Expressions — Java 8 Series Part 1

Pooja Kulkarni
The Startup
Published in
11 min readJul 25, 2020

Java, as a programming language, has evolved a lot over the years. With Java 15 underway (to be released in September 2020), Java 8 still holds the position of the biggest Java release yet. This release introduced the concepts of Functional Programming in Java such as Functional Interfaces, Lambda expressions, Streams, etc. It also made major modifications to a few existing APIs such as the Collections API, the Date, and Time API, etc.

Java 8 has been around for quite some time now, as it was released in March 2014. Ever since, many new features of Java 8 have proved to be a backbone for the future releases of Java. It provided a whole new perspective to the way developers could write quality code in Java.

This is the first blog of a series of Java 8 blogs. This series intends to cover almost all new concepts and modifications. In this blog, I am majorly going to focus on the below concepts —

  • Functional Interfaces
  • Default and Static methods in Interfaces
  • Lambda Expressions
  • Method references

Functional Interfaces

The concept of an ‘interface’ is deep rooted in the Object Oriented Programming world of Java. While an interface helps us achieve abstraction, a ‘Functional Interface’ helps us implement it in a functional manner. Now let us understand the Whats, Whys and the Hows!

  • What is this so-called functional interface? It is an interface that has only one abstract method but can have multiple default and static methods.
  • Why was this new concept introduced? A functional interface eliminates the need of writing anonymous classes and helps us write lambda expressions which will be shortly explained.
  • How to define a functional interface is Java 8? Let’s say you want to define an interface ‘Calculator’ which has a single abstract method ‘calculate’. Here’s how you would define it as a functional interface —
@FunctionalInterface
public interface Calculator {

public int calculate(int num1, int num2);

}

So what’s the difference between this and a plain old ‘interface’?

  • As you must have noticed, there’s an annotation @FunctionalInterface which suggests the compiler that this is a Functional Interface and should have only one abstract method
  • This annotation is optional. An interface can be a functional interface even without this annotation, if it strictly has only one abstract method. However if you add this annotation and still add a second abstract method, the compiler will throw an error:
Error:(16, 46) java: incompatible types: main.java.myproject.Calculator is not a functional interface
multiple non-overriding abstract methods found in interface main.java.myproject.Calculator

Since the annotation is optional, many existing interfaces defined before Java 8, that strictly had a single abstract method, became functional by definition.

How to use this Functional Interface without writing any classes (named or anonymous) will be explained shortly in the Lambda expressions section.

Default and Static methods in Interfaces

By definition, prior to Java 8, an interface could only have abstract methods. These abstract methods could then be defined by creating named or anonymous classes that would implement this interface. Well, that’s not the case ever since the concepts of default and static methods were introduced in Java 8!

Note: A default or static method can be added to any interface, not just a functional interface.

Default methods

A default method, as the name suggests, is a method defined inside the interface itself to give it a default implementation. For instance, if we want to define a method which will, by default, find the square root of a number inside our calculator —

public interface Calculator {
public int calculate(int num1, int num2);

default double findSquareRoot(int num1) {
return Math.sqrt(num1);
}

}

Now we simply need to create an object of a class that implements the above interface and call the findSquareRoot method on this object

public class MyCalculator implements Calculator {

@Override
public int calculate(int num1, int num2) {
return num1 + num2;
}

public static void main(String[] args) {
MyCalculator myCalculator = new MyCalculator();
double squareRoot = myCalculator.findSquareRoot(25);
System.out.println(squareRoot);
}

}

A default method can be overridden in a class that implements the interface. Here, we can easily override the findSquareRoot method in the MyCalculator class and modify the default definition if necessary.

Why were default methods introduced in Java 8? The answer is simple — to change existing interfaces in such a way that they remain backward compatible.

What does this mean? Let’s say there are 20 classes implementing the interface Calculator. Now, you add another abstract method in the Calculator interface. So all the 20 classes will break as they don’t have an implementation for this new abstract method that you just added.

This is where a default method comes into the picture. You can declare as well as define a new default method in the existing Calculator interface itself and it will be directly available to all the classes implementing this interface.

An example of such a method is the default implementation added for the ‘forEach’ method in the Iterable interface in Java 8. The Collection interface implements the Iterable interface and other interfaces such as List, Set, etc. implement Collection interface. Since forEach is defined directly in Iterable, classes that implement the Collection interface can directly leverage this ‘forEach’.

Static methods

As we all know, a static method in Java is a method that can be called without actually instantiating a class.

What is a static method in an interface? It is similar to a default method. You can add a default definition of a static method in the interface itself.

public interface Calculator {
public int calculate(int num1, int num2);

static double findSquareRoot(int num1) {
return Math.sqrt(num1);
}

}

Now, we can directly access this static findSquareRoot method without requiring a class to implement the Calculator interface —

public class MyCalculator {
public static void main(String[] args) {
double squareRoot = Calculator.findSquareRoot(25);
System.out.println(squareRoot);
}
}

One thing to note here is that, since the method findSquareRoot is a static method, we can’t override this method in any class that implements the Calculator interface, unlike default methods.

Lambda Expressions

One of the most significant features included in Java 8 was the ability to define a lambda expression. A lambda expression significantly improves code quality and readability and gives Java a slightly functional aspect.

So, what is a lambda expression? It is an expression used to define the single abstract method declared in a functional interface.

The expression syntax is (argument list) -> { // body } . Here, the body is the function definition and the argument list is the list of arguments of the single abstract method.

Although there are several ways of defining this single abstract method in Java, defining it with the help of a lambda expression is much more cleaner and easier. Let’s go through these different ways and gradually understand how lambda expressions make our life easy.

The Calculator interface, as defined earlier:

@FunctionalInterface
interface Calculator {
public int calculate(int num1, int num2);
}

We can define calculate by creating a class and implementing the interface, as we usually do:

public class MyCalculator implements Calculator {
@Override
public int calculate(int num1, int num2) {
return num1 + num2;
}
public static void main(String[] args) {
MyCalculator calc = new MyCalculator();
System.out.println(calc.calculate(4, 5));
}
}

This is the usual way in which we define the calculate method of the Calculator interface and call the method by creating an instance of MyCalculator class.

Now, instead of having a named class we can have an anonymous class and implement the calculate method directly:

public class MyCalculator {
public static void main(String[] args) {
Calculator calc = new Calculator() {
@Override
public int calculate(int num1, int num2) {
return num1 + num2;
}
};

System.out.println(calc.calculate(4, 5));
}
}

Here, we haven’t created any named class that implements Calculator interface, we have simply defined the calculate method while creating the instance itself.

This extra work of overriding can be eliminated by defining calculate via a simple lambda expression

public class MyCalculator {
public static void main(String[] args) {
Calculator calc = (num1, num2) -> { return num1 + num2; };
System.out.println(calc.calculate(4, 5));
}
}

Here, we have replaced the definition by a single line of code. num1 and num2 are the arguments expected by the calculate method of the Calculator interface and the method returns a sum of num1 and num2 .

If the function definition is of a single line, as is the case here, we can skip the return keyword and the method body can simply be replaced by the sum:

Calculator myCalculator = (num1, num2) -> num1 + num2;

As you must have noticed, when we define a lambda expression, we don’t provide the name of the function we are defining (calculate in our case) as it is implicit that the definition must be for the one and only method we have declared in the interface. This is the reason why a functional interface, such as Calculator in this case, must strictly have a single abstract method.

Note that the type of a lambda expression is a functional interface.

Now you can easily define as many ways as you want your Calculator to behave with less lines of clean and readable code!

Calculator adder = (num1, num2) -> num1 + num2;
Calculator multiplier = (num1, num2) -> num1 * num2;
Calculator subtractor = (num1, num2) -> num1 > num2 ? num1 - num2 : num2 - num1;

Method References

Another new feature included in Java 8 was that of a method reference. When we read the term ‘method reference’, we can interpret that this concept has something to do with referring to a method definition. Let’s see how that is achieved.

So, what exactly is a method reference? It is nothing but an easier way of writing a lambda expression. It is done by referring, the single abstract method, to another existing method. This is achieved with help of a double colon (::) operator introduced in Java 8.

There are four different ways in which you can refer methods

Referring to static methods

When you refer to a static method, in a way you delegate the definition of your single abstract method to a static method that you have already defined

@FunctionalInterface
interface Calculator {
public int calculate(int num1, int num2);
}
public class MyCalculator {
public static int sum(int num1, int num2 ) {
return num1 + num2;
}

public static void main(String[] args) {
Calculator calc = MyCalculator::sum;
System.out.println(calc.calculate(4,5));
}
}

Here, we have defined a static method sum . We are using this static method to provide a definition to the calculate method of our interface Calculator . Again, we don’t need to specify the name ‘calculate’ while referring as it is implicit because of the nature of a functional interface.

This is very useful in terms of reusability. Instead of defining our very own sum method, we can also reuse the existing sum method of the Integer wrapper class which will basically give the same result —

public class MyCalculator {
public static void main(String[] args) {
Calculator calc = Integer::sum;
System.out.println(calc.calculate(4,5));
}
}

Referring to instance methods of objects

If static method reference refers to a static method, an instance method reference refers to a method that is accessible by creating an instance of a class.

public class MyCalculator {
public int subtract(int num1, int num2 ) {
return num1 > num2 ? num1 - num2 : num2 - num1;
}

public static void main(String[] args) {
MyCalculator myCalculator = new MyCalculator();
Calculator calc = myCalculator::subtract;
System.out.println(calc.calculate(4,5));
}
}

Here, we have defined a subtract method in our class MyCalculator which is not static. As we know, to access the subtract method, we need to create an object of this class. Then we can directly refer to the subtract method via this object for defining the calculate method using ::

Referring to instance methods of arbitrary objects of a particular type

In the previous example, we understood how to refer to a method via an instance of the MyCalculator class. Similarly, we can also refer to an instance method of an ‘arbitrary’ object.

What does an ‘arbitrary object of a particular type’ mean? An Arbitrary object is any object of some type that you have created upon which the referred method is guaranteed to be available. Let’s understand what this means via a different example.

Let’s create a DataSorter class that implements the Comparable interface. Since we’re implementing Comparable, we have to override the compareTo method. This class simply holds an Integer and the compareTo method will help us sort the DataSorter objects

class DataSorter implements Comparable<DataSorter> {
Integer number;
public DataSorter(Integer number) {
this.number = number;
}
@Override
public int compareTo(DataSorter dataSorter) {
return this.number.compareTo(dataSorter.number);
}

}

Now, if we create an array of objects of the type DataSorter and intend to sort these integers, we can do this with the help of an Arrays.sort method

public class MyCalculator {
public static void main(String[] args) {
DataSorter[] integerArray = {
new DataSorter(8),
new DataSorter(9),
new DataSorter(6),
new DataSorter(1),
new DataSorter(4) };
Arrays.sort(integerArray, DataSorter::compareTo);
System.out.println(Arrays.toString(integerArray));
}
}

Here, we refer the compareTo method of the DataSorter class to the Arrays.sort method. This means that every arbitrary object of the DataSorter class in the array is expected to know the implementation of the compareTo method. This is similar to referring to a method of an instance object, the only difference being that here the objects are arbitrary.

Constructor references

A constructor reference, as you might interpret from the name, is used to create an object of a named class by ‘referring’ to its constructor.

What would a constructor reference achieve? A constructor reference helps us implement the factory pattern in a functional manner. We define a functional interface which acts as a factory. The single method in the interface has the same argument list as the constructor of the class for which we are defining the factory and returns an object of that class.

Let’s continue with the same Calculator example. Let’s say we want to define a CalculatorFactory interface

@FunctionalInterface
interface CalculatorFactory {

MyCalculator getMyCalculator(int num1, int num2);
}

The above functional interface CalculatorFactory has a getMyCalculator method that accepts two integers and returns a MyCalculator object

Now, let’s define a MyCalculator class. Here, we don’t need MyCalculator to extend the CalculatorFactory interface.

public class MyCalculator {
int num1, num2;

public MyCalculator(int num1, int num2) {
System.out.println("Constructor is called!");
this.num1 = num1;
this.num2 = num2;
}

@Override
public String toString() {
return "MyCalculator: [num1=" + num1 + ", num2=" + num2 + "]";
}
}

Now, we can create an instance of the MyCalculator class by simply using a constructor reference:

public static void main(String[] args) {
CalculatorFactory calculator = MyCalculator::new;
System.out.println("Constructor is not called yet");
MyCalculator myCalculator = calculator.getMyCalculator(4, 5);
System.out.println(myCalculator);
}

Here, we have created an instance of the MyCalculator class via the CalculatorFactory using the concept of Constructor Reference. This is a functional implementation of the factory pattern. MyCalculator::new will refer to the constructor of the MyCalculator class. Since the single abstract method getMyCalculator in the functional interface has the same function description as the constructor of the MyCalculator class, it will return an object of this class.

If we run the above code, we will get the below output:

Constructor is not called yet
Constructor is called!
MyCalculator: { num1 = 4, num2 = 5 }

As you can notice here, the string “Constructor is not called yet” is printed even after we have referred to the constructor. This is because MyCalculator::new only refers to the constructor but doesn’t actually call it.

This is because the constructor is called lazily. What does this mean? This means that the constructor will be called only when we actually call the getMyCalculator method of the interface. This is when the actual instantiation happens.

Summary

In this blog, we have explored four major concepts that were introduced in Java 8, which considerably improved the way developers could write quality code in Java. Let’s summarize these four concepts:

  • A functional interface is an interface that strictly has a single abstract method. It can have multiple default and/or static methods.
  • Default and static methods can be added to an interface to give a default definition to the methods inside the interface itself.
  • A lambda expression is used to define the single abstract method in a functional interface without having to write named/anonymous classes
  • Method references allow us to write lambda expressions in an easier way by referring the single abstract method to existing methods, including constructors.

Overall, it is pretty clear that coding in the ‘Java 8 way’ simply makes the code more readable and maintainable. To be a good Java developer, you should be able to understand and implement these concepts.

In the next blog, I’ll be explaining a few useful predefined functional interfaces that really come in handy when writing lambda expressions.

A few useful references:

  1. https://www.baeldung.com/java-8-lambda-expressions-tips
  2. https://www.amitph.com/java-method-and-constructor-reference/

--

--

Pooja Kulkarni
The Startup

Software Engineer Intern at Salesforce | MS Computer Science student at ASU | Ex-Morgan Stanley