Lambdas in Java are declared similarly as arrow functions in JavaScript, using -> instead of =>. Unlike other variables,
lambdas cannot be declared using var; they must provide an explicit target type.
Lambdas are assignable to functional interfaces as defined in java.util.function and threading interfaces like Runnable
and Callable<T>; here, we declare a simple lambda using Runnable which prints a message when run:
Runnable lambda = () -> {
System.out.println("Hello from a lambda expression!");
};Like in JS, a lambda can be inlined if the body contains only one line.
Runnable lambda = () -> System.out.println("Hello from a lambda expression!");Note that like in JS, an inlined lambda will return the value of its expression.
Runnable rand = () -> Math.random(); // valid
Supplier<Double> rand = () -> Math.random(); // also valid; rand returns a double
Supplier<Double> rand = () -> { // invalid; rand returns void
Math.random();
};The simplest version of a lambda implements the Runnable interface. Runnable is used for threads, but also defines a
lambda which accepts no arguments and returns void. A Runnable is called using .run().
public class Main {
public static void main(String... args) {
Runnable print = () -> System.out.println("Hello, world!");
callRunnable(print);
}
public static void callRunnable(Runnable func) {
func.run();
}
}Method references are valid as lambdas as long as the types match; here, Main::printHelloWorld is assignable to
Runnable as it takes no arguments and returns void.
public class Main {
public static void main(String... args) {
callRunnable(Main::printHelloWorld);
}
public static void printHelloWorld() {
System.out.println("Hello, world!");
}
public static void callRunnable(Runnable func) {
func.run();
}
}To type a lambda that returns a value, use Supplier<T>. Supplier<T> defines a lambda which takes no arguments and
returns a value of type T. A Supplier's value can be retrieved using .get().
import java.util.function.Supplier;
public class Main {
public static void main(String... args) {
Supplier<Double> rand = () -> Math.random();
printSupplierValue(rand);
}
public static <T> void printSupplierValue(Supplier<T> supplier) {
System.out.println(supplier.get());
}
}Like with all functional interfaces, method references are valid Suppliers as long as they take no arguments, throw no
unchecked exceptions, and return the correct type.
import java.util.function.Supplier;
public class Main {
public static void main(String... args) {
printSupplierValue(Main::rand);
printSupplierValue(Math::random);
}
public static double rand() {
return Math.random();
}
public static <T> void printSupplierValue(Supplier<T> supplier) {
System.out.println(supplier.get());
}
}To type a lambda that takes a single argument and returns void, use Consumer<T>. A Consumer can be called using .accept(T).
import java.util.function.Consumer;
public class Main {
public static void main(String... args) {
Consumer<String> printStr = (String str) -> {
System.out.println("Printed string:");
System.out.println(str);
};
callConsumer(printStr, "Hello, world!");
}
public static <T> void callConsumer(Consumer<T> consumer, T value) {
consumer.accept(value);
}
}Method references are valid as well.
import java.util.function.Consumer;
public class Main {
public static void main(String... args) {
callConsumer(System.out::println, "Hello, world!");
}
public static <T> void callConsumer(Consumer<T> consumer, T value) {
consumer.accept(value);
}
}If a lambda both accepts and returns a value, the type Function<T, R> can be used. Function<T, R> specifies a function
which takes an argument of type T and returns a value of type R, and can be called using .apply(T).
import java.util.function.Function;
public class Main {
public static void main(String... args) {
Function<String, String> ishify = (String str) -> str + "ish";
printFunctionCall(ishify, "cool");
Function<Integer, Double> toDouble = (Integer num) -> num * 1.0;
printFunctionCall(toDouble, 5);
}
public static <T, R> void printFunctionCall(Function<T, R> func, T value) {
System.out.println(func.apply(value));
}
}import java.util.function.Function;
public class Main {
public static void main(String... args) {
int num = callFunction(Integer::parseInt, "5");
System.out.println(num + 7);
}
public static <T, R> R callFunction(Function<T, R> func, T value) {
return func.apply(value);
}
}For taking in two values, use BiConsumer<T, U> and BiFunction<T, U, R>.
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
public class Main {
public static void main(String... args) {
BiConsumer<Integer, Integer> printSum = (Integer x, Integer y) -> System.out.println(x + y);
callBiConsumer(printSum, 5, 7);
BiFunction<Integer, Integer, Integer> sum = (Integer x, Integer y) -> x + y;
int x = callBiFunction(sum, 1, 2);
int y = callBiFunction(Integer::sum, 3, 4);
System.out.println(x + y);
}
public static <T, U> void callBiConsumer(BiConsumer<T, U> consumer, T a, U b) {
consumer.accept(a, b);
}
public static <T, U, R> R callBiFunction(BiFunction<T, U, R> func, T a, U b) {
return func.apply(a, b);
}
}When a lambda's argument type and return type are the same, the UnaryOperator<T> and BinaryOperator<T> interfaces
can be used. UnaryOperator<T> defines a function which takes a single argument of type T and returns a value of type T,
while BinaryOperator<T> defines the same but takes two arguments instead. The "ishify" and "sum" examples from earlier,
using UnaryOperator and BinaryOperator:
import java.util.function.UnaryOperator;
import java.util.function.BinaryOperator;
public class Main {
public static void main(String... args) {
UnaryOperator<String> ishify = (String str) -> str + "ish";
printUnaryOperatorCall(ishify, "cool");
printBinaryOperatorCall(Integer::sum, 1, 2);
}
public static <T> void printUnaryOperatorCall(UnaryOperator<T> func, T value) {
System.out.println(func.apply(value));
}
public static <T> void printBinaryOperatorCall(BinaryOperator<T> func, T a, T b) {
System.out.println(func.apply(a, b));
}
}When a lambda takes one argument and returns a boolean (like a callback to .filter() or .find()), Predicate<T>
can be used. Predicate<T> defines a function which takes one argument of type T and returns boolean, called using
.test(T).
import java.util.function.Predicate;
public class Main {
public static void main(String... args) {
Predicate<Integer> isOdd = (Integer num) -> num % 2 == 1;
System.out.println(testPredicate(isOdd, 6));
System.out.println(testPredicate(Integer.valueOf(5)::equals, 5));
}
public static <T> boolean testPredicate(Predicate<T> pred, T value) {
return pred.test(value);
}
}