Skip to content
On this page

Basics of Lambda

Before diving into Lambda, let’s review Java methods.

Java methods can be categorized into instance methods, such as the equals() method defined in the Integer class:

java
public final class Integer {
    boolean equals(Object o) {
        ...
    }
}

And static methods, such as the parseInt() method defined in the Integer class:

java
public final class Integer {
    public static int parseInt(String s) {
        ...
    }
}

Whether it is an instance method or a static method, they essentially function as equivalent to functions in procedural languages. For example, in C, we have a function like this:

c
char* strcpy(char* dest, char* src);

The main difference is that Java instance methods implicitly pass a this variable, meaning that instance methods always have this hidden parameter.

Functional Programming (Functional Programming) treats functions as the fundamental units of computation. Functions can be treated as variables, can accept functions as parameters, and can also return functions. The theoretical study of functional programming historically revolves around Lambda calculus, which is why we often refer to the coding style that supports functional programming as Lambda expressions.

Lambda Expressions

In Java programs, we often encounter a plethora of single-method interfaces, where an interface defines only one method, such as:

  • Comparator
  • Runnable
  • Callable

Taking Comparator as an example, when we want to call Arrays.sort(), we can pass a Comparator instance defined using an anonymous class like this:

java
String[] array = ...
Arrays.sort(array, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
});

The above syntax is quite verbose. Since Java 8, we can replace single-method interfaces with Lambda expressions. The previous code can be rewritten as follows:

java
// Lambda
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, (s1, s2) -> {
            return s1.compareTo(s2);
        });
        System.out.println(String.join(", ", array));
    }
}

Observe the syntax of the Lambda expression; it only needs to specify the method definition:

java
(s1, s2) -> {
    return s1.compareTo(s2);
}

Here, the parameters are (s1, s2), and the parameter types can be omitted because the compiler can automatically infer that they are of type String. The -> { ... } denotes the method body, allowing all code to be written inside. Lambda expressions do not require a class definition, making them very concise.

If there is only a single line with return xxx, it can be simplified even further:

java
Arrays.sort(array, (s1, s2) -> s1.compareTo(s2));

The return type is also automatically inferred by the compiler. In this case, the inferred return value is int, so as long as it returns an int, the compiler will not throw an error.

FunctionalInterface

Interfaces that define a single method are referred to as FunctionalInterface and are annotated with @FunctionalInterface. For example, the Callable interface:

java
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Now, let's look at the Comparator interface:

java
@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);

    boolean equals(Object obj);

    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }

    default Comparator<T> thenComparing(Comparator<? super T> other) {
        ...
    }
    ...
}

Although the Comparator interface has many methods, it only contains one abstract method, int compare(T o1, T o2), with the other methods being default or static methods. Additionally, note that boolean equals(Object obj) is defined in the Object class and is not counted among the interface methods. Therefore, Comparator is also a FunctionalInterface.

Exercise

Implement case-insensitive sorting using Lambda expressions.

Summary

  • Single-method interfaces are called FunctionalInterface.
  • When receiving a FunctionalInterface as a parameter, you can replace the instantiated anonymous class with a Lambda expression, greatly simplifying the code.
  • The parameters and return values of Lambda expressions can both be automatically inferred by the compiler.
Basics of Lambda has loaded