if you have not read the previous chapter on interfaces, starts by it first
Java unlike JavaScript or Python, don't let you pass a method as argument of a method without ceremony
Let say i want to write a method that do either the sum of an array of values or the sum of their square, it can write if that way
int sumOf(boolean squareSum, int... array) {
var sum = 0;
for(var value: array) {
if (squareSum) {
sum = sum + value * value;
} else {
sum = sum + value;
}
}
return sum;
}
System.out.println(sumOf(true, 1, 2, 3));
but you every values of the array, squareSum will have the same value so it's equivalent to write
int sumOf(boolean squareSum, int... array) {
var sum = 0;
if (squareSum) {
for(var value: array) {
sum = sum + value * value;
}
} else {
for(var value: array) {
sum = sum + value;
}
}
return sum;
}
System.out.println(sumOf(true, 1, 2, 3));
and at that point, you have code duplication. Usually testing a condition in the middle of a computation is a code smell. There is a way to solve that, it's to take the part of the computation that change as parameter so sumOf instead of a boolean that take a function as parameter more or less like this
/*
int sumOf(??? function, int... array) {
var sum = 0;
for(var value: array) {
sum = sum + function(value);
}
return sum;
}
*/
the question is what ??? is. The answer in simple in Java, if it can be either a value or another one, then it's an interface. Exactly like in the previous chapter, we have introduce an interface in between two records. Here my interface is a function that takes an int and return an int so
interface Fun {
int apply(int value);
}
int sumOf(Fun function, int[] array) {
var sum = 0;
for(var value: array) {
sum = sum + function.apply(value);
}
return sum;
}
then using the lambda syntax we have seeing in the previous chapter sumOf can be called
var array = new int[] { 1, 2, 3 };
System.out.println(sumOf(x -> x, array));
System.out.println(sumOf(x -> x * x, array));
Because it's not convenient to have to declare an interface every times you want to send a function as parameter, Java already provides a bunch of interfaces in the package java.lang.function, so you often don't have to write your own Moreover most interface also have variant for primitive types
import java.util.function.*;
java.lang.Runnable is equivalent to () -> void
Runnable runnable = () -> { System.out.println("hello"); };
runnable.run();
Supplier is equivalent to () -> T
Supplier<String> supplier = () -> "hello supplier";
System.out.println(supplier.get());
IntSupplier, LongSupplier and DoubleSupplier
IntSupplier supplier = () -> 42;
System.out.println(supplier.getAsInt());
Consumer is equivalent to (T) -> void
Consumer<String> consumer = message -> System.out.println(message);
consumer.accept("hello consumer");
IntConsumer, LongConsumer and DoubleConsumer
DoubleConsumer consumer = message -> System.out.println(message);
consumer.accept(42);
Predicate is equivalent to (T) -> boolean
Predicate<String> predicate = text -> text.length() < 5;
System.out.println(predicate.test("hello predicate"));
IntPredicate, LongPredicate and DoublePredicate
DoublePredicate isPositive = v -> v >= 0;
System.out.println(isPositive.test(17.3));
Function<T,U> is equivalent to (T) -> U
Function<String, String> fun = s -> "hello " + s;
System.out.println(fun.apply("function"));
IntFunction, LongFunction and DoubleFunction
IntFunction<String[]> arrayCreator = size -> new String[size];
System.out.println(arrayCreator.apply(5).length);
ToIntFunction, ToLongFunction and ToDoubleFunction
ToIntFunction<String> stringLength = s -> s.length();
System.out.println(stringLength.applyAsInt("hello"));
UnaryOperator is equivalent to (T) -> T
UnaryOperator<String> unaryOp = s -> "hello " + s;
System.out.println(unaryOp.apply("unary operator"));
IntUnaryOperator, LongUnaryOperator and DoubleUnaryOperator
IntUnaryOperator unaryOp = x -> - x;
System.out.println(unaryOp.applyAsInt(7));
BiPredicate is equivalent to (T, U) -> boolean
BiPredicate<String, String> predicate = (s, prefix) -> s.startsWith(prefix);
System.out.println(predicate.test("hello", "hell"));
BiFunction<T,U,V> is equivalent to (T, U) -> V
BiFunction<String, String, String> fun = (s1, s2) -> s1 + " " + s2;
System.out.println(fun.apply("hello", "bi-function"));
BinaryOperator is equivalent to (T, T) -> T
BinaryOperator<String> binaryOp = (s1, s2) -> s1 + " " + s2;
System.out.println(binaryOp.apply("hello", "binary operator"));
IntBinaryOperator, LongBinaryOperator and DoubleBinaryOperator
IntBinaryOperator binaryOp = (a, b) -> a + b;
System.out.println(binaryOp.applyAsInt(40, 2));
Lambda syntax is similar to arrow part the switch syntax
- with 0 parameter: () -> expression
- with 1 parameter: x -> expression
- with 2 or more parameters: (a, b) -> expression
DoubleUnaryOperator op = x -> 2.0 * x;
System.out.println(op.applyAsDouble(2));
instead of an expression, you can have statements between curly braces
DoubleUnaryOperator op = x -> {
return 2.0 * x;
};
System.out.println(op.applyAsDouble(2));
The types of the parameters are optional so you can declare them or not if you don't declare them the parameter types of the abstract method of the interface are used
DoubleUnaryOperator op = (double x) -> 2.0 * x;
System.out.println(op.applyAsDouble(2));
There are 5 kinds of method references
-
a reference to an instance method
Seeing an instance method as a function means you have to take the type of
this
into account, herestartsWith
as one parameter but the function as two
BiPredicate<String,String> predicate = String::startsWith;
System.out.println(predicate.test("hello", "hell"));
-
a bound reference to an instance method
The value of this is fixed so the parameter of the function are the same as the parameter of the instance method
var text = "hello";
IntSupplier supplier = text::length;
System.out.println(supplier.getAsInt());
-
a reference to a static method
No instance here, so the parameter of the function are the same as the parameter of the static method
ToIntFunction<String> function = Integer::parseInt;
System.out.println(function.applyAsInt("42"));
-
a reference to a new instance
The parameter of the function are the same as the parameter of the constructor. The return type is the class of the constructor
record Person(String name) {}
Function<String, Person> factory = Person::new;
System.out.println(factory.apply("John"));
-
a reference to a new array
Same as above, the return type is the array.
IntFunction<String[]> arrayCreator = String[]::new;
System.out.println(arrayCreator.apply(2).length);
A frequent error is to think that String::length is a reference to a static method because the syntax is close to String.length() which is a call to a static method. But for a method reference, the same syntax is used to reference an instance method and a static method. So String::length is a reference to an instance method because the method length() in the class String is declared as an instance method.