Motivation for passing functions
Java, unlike some other languages, did not originally allow you to store a function in a variable. Instead, we use interfaces to simulate the ability of passing a function. An interface with just one method is used to represent the signature of the function. Then, a class implementing that interface represents the body of the function. Finally, an instance of that class can be passed around as a variable, and the method can be called to invoke the function.
Here we create to functions via the "SquareFunction" and "DoubleFunction" classes, both of which implement the "MyInterface" interface with method "doMath":
public class MyProgram {
public static void main(String[] args) {
MyInterface squareNumber = new SquareFunction();
System.out.println(doFunctionTwice(squareNumber, 3));
MyInterface doubleNumber = new DoubleFunction();
System.out.println(doFunctionTwice(doubleNumber, 3));
}
public static int doFunctionTwice(MyInterface function, int number) {
number = function.doMath(number);
number = function.doMath(number);
return number;
}
}
interface MyInterface {
public int doMath(int x);
}
class SquareFunction implements MyInterface {
public int doMath(int x) {
return x * x;
}
}
class DoubleFunction implements MyInterface {
public int doMath(int x) {
return x * 2;
}
}
The above method to pass functions is very verbose. In addition, writing the function definition in its own class separates the definition with where we actually use the code. We will combine both with an anonymous class.
Anonymous classes:
public class MyProgram {
public static void main(String[] args) {
//The following 5 lines are all one statement:
MyInterface anonymousFunction = new MyInterface(){
public int doMath(int x) {
return x * x;
}
}; //statement ends
System.out.println(doFunctionTwice(anonymousFunction, 3));
//You can inline the anonymous function as well:
System.out.println(doFunctionTwice(new MyInterface(){
public int doMath(int x) {
return x * 2;
}
}, 3)); //statement ends
}
public static int doFunctionTwice(MyInterface function, int number) {
number = function.doMath(number);
number = function.doMath(number);
return number;
}
}
interface MyInterface {
public int doMath(int x);
}
In the above code, the anonymousFunction class definition is inlined with the variable declaration. Thus, we cannot refer to the class name anywhere else in the code, but it shortens our code. In addition, we do not even need to store this anonymous class in a variable, as we can pass it directly to another function. Much shorter!
Lambdas
An anonymous class is also sometimes called a "lambda", a "functor" or a "closure". Although each of these terms have their own specific meaning and context, programmers sometimes use them interchangeably.
Because lambdas are used so often, Java 8 introduced new syntax to make it even easier to write:
public class MyProgram {
public static void main(String[] args) {
//This is the pre-Java-8 style anonymous class:
System.out.println(doFunctionTwice(new MyInterface(){
public int doMath(int x) {
return x * x;
}
}, 3));
//That Java 8 Lambda:
System.out.println(doFunctionTwice( (x) -> x * 2, 3)); //statement ends
}
public static int doFunctionTwice(MyInterface function, int number) {
number = function.doMath(number);
number = function.doMath(number);
return number;
}
}
interface MyInterface {
public int doMath(int x);
}
Thus, we use the very short (x)->x*2
which actually compiles to the same thing as this:
new MyInterface(){
public int doMath(int x) {
return x * 2;
}
}
Java is able to infer the function name because doFunctionTwice takes a MyInterface interface, and the only method in that interface is called doMath. In fact, you may only use lambdas with interfaces that only have one method.
Java is also able to infer the argument type (the function signature), so rather than typing (int x), the lambda simply has the variable name (x). Since the lambda must return a value, Java infers the return statement, so all you need to write is (x)->x*2. Lambda expressions can even infer generics from the context!