In Java we use lambda syntax to create function objects. We can specify methods inside other methods—and even pass methods as arguments to other methods.
A lambda has a shape, one determined by its parameters and return values (if any) and their types. Classes like Function, Supplier, Consumer, accept lambdas with specific shapes.
This program creates a Function object from a lambda expression. The lambda expression accepts one argument, an Integer, and returns another Integer.
apply()
on the Function object. This executes and returns the expression—10 is changed to 20.import java.util.function.*; public class Program { public static void main(String[] args) { // Create a Function from a lambda expression. // ... It returns the argument multiplied by two. Function<Integer, Integer> func = x -> x * 2; // Apply the function to an argument of 10. int result = func.apply(10); System.out.println(result); } }20
A Supplier object receives no arguments. We use an empty argument list to specify a lambda expression with no arguments.
get()
on it to retrieve its value—it may return different values when called more than once.import java.util.function.*; public class Program { static void display(Supplier<Integer> arg) { System.out.println(arg.get()); } public static void main(String[] args) { // Pass lambdas to the display method. // ... These conform to the Supplier class. // ... Each returns an Integer. display(() -> 10); display(() -> 100); display(() -> (int) (Math.random() * 100)); } }10 100 21
Predicate
Lambda, ArrayList
The term predicate is used in computer science to mean a boolean-returning method. A Predicate
object receives one value and returns true or false.
ArrayList
receives a Predicate
. Here, we remove all elements starting with the letter "c."import java.util.ArrayList; public class Program { public static void main(String[] args) { // Create ArrayList and add four String elements. ArrayList<String> list = new ArrayList<>(); list.add("cat"); list.add("dog"); list.add("cheetah"); list.add("deer"); // Remove elements that start with c. list.removeIf(element -> element.startsWith("c")); System.out.println(list.toString()); } }[dog, deer]
Opposite a Supplier, a Consumer acts upon a value but returns nothing. It means a void
method. We can use a consumer to call println
or other void
methods.
ArrayList
or even just a class
field.import java.util.function.*; public class Program { static void display(int value) { switch (value) { case 1: System.out.println("There is 1 value"); return; default: System.out.println("There are " + Integer.toString(value) + " values"); return; } } public static void main(String[] args) { // This consumer calls a void method with the value. Consumer<Integer> consumer = x -> display(x - 1); // Use the consumer with three numbers. consumer.accept(1); consumer.accept(2); consumer.accept(3); } }There are 0 values There is 1 value There are 2 values
UnaryOperator
This functional object receives a value of a certain type (like Integer) and returns a same-typed value. So it operates on, and returns, a value.
import java.util.function.*; public class Program { public static void main(String[] args) { // This returns one value of the same type as its one parameter. // ... It means the same as the Function below. UnaryOperator<Integer> operator = v -> v * 100; // This is a generalized form of UnaryOperator. Function<Integer, Integer> function = v -> v * 100; System.out.println(operator.apply(5)); System.out.println(function.apply(6)); } }500 600
UnaryOperator
, ArrayList
This example uses a lambda expression as a UnaryOperator
argument to the ArrayList
's replaceAll
method. It adds 10 to all elements.
forEach
method on ArrayList
does not change element values. ReplaceAll
allows this action.import java.util.ArrayList; public class Program { public static void main(String[] args) { // Add ten to each element in the ArrayList. ArrayList<Integer> list = new ArrayList<>(); list.add(5); list.add(6); list.add(7); list.replaceAll(element -> element + 10); // ... Display the results. System.out.println(list); } }[15, 16, 17]
BiConsumer
, HashMap
A BiConsumer
is a functional object that receives two parameters. Here we use a BiConsumer
in the forEach
method on HashMap
.
forEach
lambda here, which is a valid BiConsumer
, prints out all keys, values, and the keys lengths.import java.util.HashMap; public class Program { public static void main(String[] args) { HashMap<String, String> hash = new HashMap<>(); hash.put("cat", "orange"); hash.put("dog", "black"); hash.put("snake", "green"); // Use lambda expression that matches BiConsumer to display HashMap. hash.forEach((string1, string2) -> System.out.println(string1 + "..." + string2 + ", " + string1.length())); } }cat...orange, 3 snake...green, 5 dog...black, 3
These do not matter in a lambda expression. The identifiers do not impact external parts of the program, but can be accessed on both sides of the lambda.
import java.util.function.Consumer; public class Program { public static void main(String[] args) { // The identifier in the lambda expression can be anything. Consumer<Integer> consumer = carrot -> System.out.println(carrot); consumer.accept(1989); } }1989
Here we benchmark a Function object, which we invoke with apply()
, against a static
method. Both code blocks do the same thing.
apply()
method to execute it many times.import java.util.function.*; public class Program { static int method(int element) { return element + 1; } public static void main(String[] args) { Function<Integer, Integer> function = element -> element + 1; long t1 = System.currentTimeMillis(); // Version 1: apply a function specified as a lambda expression. for (int i = 0; i < 10000000; i++) { int result = function.apply(i); if (result == -1) { System.out.println(false); } } long t2 = System.currentTimeMillis(); // Version 2: call a static method. for (int i = 0; i < 10000000; i++) { int result = method(i); if (result == -1) { System.out.println(false); } } long t3 = System.currentTimeMillis(); // ... Benchmark results. System.out.println(t2 - t1); System.out.println(t3 - t2); } }93 ms, Function apply() 6 ms, method call
This method works on streams like IntStream
. It returns a modified stream. And we can use methods like findFirst
to access elements from filtered streams.
With a lambda expression, IntStream
and the reduce()
method we can sum an array. This approach has better parallel potential. But it is slow in simple cases.
At first, functional object names in Java are confusing. With practice, and some effort, the functional object system in this language is powerful and expressive.