Introduction to Java lambdas
Lambda expressions provide a clear and concise way of representing a method interface using an expression.
Lambdas can only operate on a functional interface, which is an interface with just one abstract method. Functional interfaces can have any number of default or static methods.
Optionally, the @FunctionalInterface
annotation can be used so that a compiler error will be generated if the interface is not a functional interface.
Examples of functional interfaces:
interface Foo1 {
void bar();
}
interface Foo2 {
int bar(boolean baz);
}
interface Foo3 {
String bar(Object baz, int mink);
}
interface Foo4 {
default String bar() { // default so not counted
return "baz";
}
void quux();
}
@FunctionalInterface
interface Foo5 {
void bar();
}
@FunctionalInterface
interface Foo6 {
void bar();
boolean equals(Object obj); // overrides one of Object's method so not counted
}
Conversely, this is not a functional interface, as it has more than one method:
interface BadFoo {
void bar();
void quux(); // <-- Second method prevents lambda: which one do we use?
}
The body of a lambda is used as the body of the functional interface's single method. Thus, these two examples are equivalent:
Foo1 longFoo = new Foo1() {
@Override
public void bar() {
System.out.println("foo");
}
};
Foo1 shortFoo = () -> System.out.println("foo");
// Both of these print "foo":
longFoo.bar();
shortFoo.bar();
Because a lambda is simply an implementation of an interface, nothing special needs to be done to make a method accept a lambda: any function which takes a functional interface can also accept a lambda.
public void passMeALambda(Foo1 f);
passMeALambda(() -> System.out.println("Lambda called"));
Java 8 provides some functional interfaces out of the box, which can be found in the java.util.function
package. Learning these will provide in-depth understanding on functional interfaces
Declare a comparator
Prior to Java 8, the java.util.Comparator
interface was implemented as shown below, in order to sort a collection:
Collections.sort(personList, new Comparator<Person>(){
public int compare(Person p1, Person p2){
return p1.getFirstName().compareTo(p2.getFirstName());
}
});
Starting with Java 8, this anonymous inner class can be replaced with a lambda expression:
Collections.sort(personList,
(Person p1, Person p2) -> p1.getFirstName().compareTo(p2.getFirstName()));
The type information Person
can be removed to simplify the declaration:
Collections.sort(personList,
(p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName()));
This can be simplified further with the use of Comparator.comparing
:
Collections.sort(personList, Comparator.comparing(Person::getFirstName));
Using a static import can improve the readability and conciseness:
import static java.util.Comparator.comparing;
//...
Collections.sort(personList, comparing(Person::getFirstName));
Comparators built this way can also be easily chained together, that is, if we want to compare persons by their first name, and in case of equality, then compare by last name :
Collections.sort(personList,
comparing(Person::getFirstName).thenComparing(Person::getLastName));
Currying
Currying is the technique of translating the evaluation of a function that takes multiple arguments into evaluating a sequence of functions, each with a single argument.
This is normally useful when for example:
- Different arguments of a function are calculated at different times. (see Example 1)
- Different arguments of a function are calculated by different tiers of the application. (see Example 2)
Here's a generic utility that applies currying on a 2-argument function:
class FunctionUtils {
public static <A,B,C> Function<A,Function<B,C>> curry(BiFunction<A, B, C> f) {
return a -> b -> f.apply(a,b);
}
}
The above returned curried lambda expression can also be viewed/written as:
a -> ( b -> f.apply(a,b) );
Example 1
Let's assume that the total yearly income is a function composed by the income and a bonus:
BiFunction<Integer,Integer,Integer> totalYearlyIncome = (income,bonus) -> income + bonus;
Let's assume that the yearly income portion is known in advance:
Function<Integer,Integer> partialTotalYearlyIncome = FunctionUtils.curry(totalYearlyIncome).apply(10000);
And at some point down the line the bonus is known:
partialTotalYearlyIncome.apply(100);
Example 2
Let's assume that the car manufacturing involves the application of car wheels and car body:
BiFunction<String,String,String> carManufacturing = (wheels,body) -> wheels.concat(body);
These parts are applied by different factories:
class CarWheelsFactory {
public Function<String,String> applyCarWheels(BiFunction<String,String,String> carManufacturing) {
return FunctionUtils.curry(carManufacturing).apply("applied wheels..");
}
}
class CarBodyFactory {
public String applyCarBody(Function<String,String> partialCarWithWheels) {
return partialCarWithWheels.apply("applied car body..");
}
}
Notice that the CarWheelsFactory
above curries the car manufacturing function and only applies the wheels.
The car manufacturing process then will take the below form:
CarWheelsFactory carWheelsFactory = new CarWheelsFactory();
CarBodyFactory carBodyFactory = new CarBodyFactory();
BiFunction<String,String,String> carManufacturing = (wheels,body) -> wheels.concat(body);
Function<String,String> partialCarWheelsApplied = carWheelsFactory.applyCarWheels(carManufacturing);
String carCompleted = carBodyFactory.applyCarBody(partialCarWheelsApplied);
Method References
Method references allow predefined static or instance methods that adhere to a compatible functional interface, to be passed as arguments instead of an anonymous lambda expression.
List<String> strings = getStrings();
Instance method reference (to an arbitrary instance)
strings.stream().map(String::toString)
The equivalent lambda
strings.stream().map((s) -> s.toString())
Here, a method reference to the instance method toString()
of type String
, is being passed. Since it's known to be of the collection type, the method on the instance (known later) will be invoked.
Instance method reference (to a specific instance)
strings.foreach(System.out::println);
Since System.out
is an instance of PrintStream
, a method reference to the this specific instance is being passed as an argument.
The equivalent lambda:
strings.foreach((s) -> System.out.println(s));
Static method reference
strings.stream().map(String::valueOf)
This example passes a reference to the static valueOf()
method on the String
type. Therefore the instance object in the collection is passed as an argument to valueOf()
.
The equivalent lambda:
strings.stream().map((s) -> String.valueOf(s))
Reference to a constructor
strings.stream().map(Integer::new)
The single String argument constructor of the Integer
type is being used here, to construct an integer given the string provided as the argument. In this case, as long as the string represents a number, the stream will be mapped to Integers.
The equivalent lambda:
strings.stream().map((s) -> new Integer(s));
Iterating through a HashMap using a BiConsumer
HashMap has the forEach
method that accepts a BiConsumer
. BiConsumer
is a functional interface which contains one method, public void accept(T t, U u)
, where T
and U
are parameterized types.
The following examples are for printing out the Key-Value pairs for a HashMap<String,String>
; however, the concept still applies elsewhere.
Assume that a HashMap<String, String>
is stored in variable hm
, and that java.util.HashMap
is imported for all of the following examples.
Without a lambda expression:
public class StringBiConsumer implements BiConsumer<String, String> {
public void apply(String str1, String str2) {
//Your code here
System.out.println(str1 + ": " + str2);
}
}
hm.forEach(new StringBiConsumer());
We can improve this a little bit without using lambda expressions by writing an anonymous class:
hm.forEach(new BiConsumer<String, String>() {
public void apply(String str1, String str2) {
System.out.println(str1 + ": " + str2);
}
});
A bit more concise, but still bulky. We can do better.
Since functional interfaces (such as BiConsumer
) only contain one unimplemented method, the designers of java guessed that with these types of interfaces, you probably only want to override just that one method. When you write a lambda expression, the "method" that the lambda expression represents automatically overrides the single unimplemented method of the lambda target (in this example, the lambda target is BiConsumer), which must be a functional interface. With that in mind, let's rewrite this as a lambda expression:
hm.forEach((String str1, String str2)->{
//The -> operator tells java that you are writing a lambda expression.
//Note that the parameter types are the same as a BiConsumer<String, String>
System.out.println(str1+ ": " + str2);
//If the return type isn't void, you need a return statement
});
Java can also inference the parameter types (since HashMap<String, String>
only accepts a BiConsumer<? super String, ? super String>
, java infers that you are creating a BiConsumer<String, String>
). Let's see this in action:
hm.forEach((str1, str2)->{
System.out.println(str1 + ": " + str2);
});
Since this isn't a particularly complicated statement, this can be shortened even further:
hm.forEach((str1, str2)->System.out.println(str1 + ": " + str2));
Alternatively, you can reuse the lambda expression:
BiConsumer<String, String> exampleConsumer= (str1, str2)->System.out.println(str1 + ": " + str2);
hm.forEach(exampleConsumer);
someOtherHm.forEach(exampleConsumer);
If you have a method elsewhere that matches the parameter and return types of the lambda target, you can use a method reference. This code is valid:
public class ExampleClass {
public static void someRandomMethod(String str1, String str2) {
System.out.println(str1 + ": " str2);
}
public static void processMap(HashMap<String, String> hm) {
hm.forEach(ExampleClass::someRandomMethod);
}
}
This code is equivalent to:
hm.forEach((String s1, String s2)->ExampleClass.someRandomMethod(s1, s2));
This can also extend to non-static methods, as long as the parameter and return types still match:
hm.forEach(anObject::anotherMethod);
One final note: for all of the examples where the code doesn't directly mention BiConsumer by name, there is no need to import java.util.function.BiConsumer.
`return` only returns from the lambda, not the outer method
The return
method only returns from the lambda, not the outer method.
Beware that this is different from Scala and Kotlin!
void threeTimes(IntConsumer r) {
for (int i = 0; i < 3; i++) {
r.accept(i);
}
}
void demo() {
threeTimes(i -> {
System.out.println(i);
return; // Return from lambda to threeTimes only!
});
}
This can lead to unexpected behavior when attempting to write own language constructs, as in builtin constructs such as for
loops return
behaves differently:
void demo2() {
for (int i = 0; i < 3; i++) {
System.out.println(i);
return; // Return from 'demo2' entirely
}
}
In Scala and Kotlin, demo
and demo2
would both only print 0
. But this is not more consistent. The Java approach is consistent with refactoring and the use of classes - the return
in the code at the top, and the code below behaves the same:
void demo3() {
threeTimes(new MyIntConsumer());
}
class MyIntConsumer implements IntConsumer {
public void accept(int i) {
System.out.println(i);
return;
}
}
Therefore, the Java return
is more consistent with class methods and refactoring, but less with the for
and while
builtins, these remain special.
Because of this, the following two are equivalent in Java:
IntStream.range(1, 4)
.map(x -> x * x)
.forEach(System.out::println);
IntStream.range(1, 4)
.map(x -> { return x * x; })
.forEach(System.out::println);
Furthermore, the use of try-with-resources is safe in Java:
class Resource implements AutoCloseable {
public void close() { System.out.println("close()"); }
}
void executeAround(Consumer<Resource> f) {
try (Resource r = new Resource()) {
System.out.print("before ");
f.accept(r);
System.out.print("after ");
}
}
void demo4() {
executeAround(r -> {
System.out.print("accept() ");
return; // Does not return from demo4, but frees the resource.
});
}
will print before accept() after close()
. In the Scala and Kotlin semantics, the try-with-resources would not be closed, but it would print before accept()
only.
Implementing multiple interfaces
Sometimes you may want to have a lambda expression implementing more than one interface. This is mostly useful with marker interfaces (such as java.io.Serializable) since they don't add abstract methods.
For example, you want to create a TreeSet
with a custom Comparator
and then serialize it and send it over the network. The trivial approach:
TreeSet<Long> ts = new TreeSet<>((x, y) -> Long.compare(y, x));
doesn't work since the lambda for the comparator does not implement Serializable
. You can fix this by using intersection types and explicitly specifying that this lambda needs to be serializable:
TreeSet<Long> ts = new TreeSet<>(
(Comparator<Long> & Serializable) (x, y) -> Long.compare(y, x));
If you're frequently using intersection types (for example, if you're using a framework such as Apache Spark where almost everything has to be serializable), you can create empty interfaces and use them in your code instead:
public interface SerializableComparator extends Comparator<Long>, Serializable {}
public class CustomTreeSet {
public CustomTreeSet(SerializableComparator comparator) {}
}
This way you're guaranteed that the passed comparator will be serializable.
Introduction To Java Closures
A closure uses variables that are outside of the function scope. This is not a problem in traditional procedural programming – you just use the variable – but when you start producing functions at runtime it does become a problem.
make_fun()
is creating and returning a function called func_to_return
, which is then used by the rest of the program.
public class LambdaExample {
public Function<Integer, Integer> make_fun() {
int n = 0;
return arg -> {
System.out.print(n + " " + arg + ": "); // here 'n' is binded to the outside 'n'
// can't perform: n += 1;
arg += 1;
return n + arg;
};
}
public void try_it() {
Function<Integer, Integer>
x = make_fun(),
for(int i = 0; i < 5; i++)
System.out.println(x.apply(i));
}
public static void main(String[] args) {
new LambdaExample().try_it();
}
}
Note that n in the scope of make_fun is binded to n inside the returned function but the reference is "effectively final", meaning that in the returned function it is not allowed to change n's value, for example, n += 1
;
To overcome this, it is possible to implement a class containing the variable int n and change its value using set:
class myInt {
private int n = 0;
public myInt(int n){this.n = n;}
public void setN(int n){this.n = n;}
public int getN(){return this.n;}
}
And in the above example:
public Function<Integer, Integer> make_fun() {
myInt n = new myInt(0);
return arg -> {
System.out.print(n.getN() + " " + arg + ": ");
arg += 1;
n.setN(n.getN() + 1);
return n.getN() + arg;
};
}
Lambda - Listener Example
Anonymous class listener
JButton btn = new JButton("My Button");
btn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button was pressed");
}
});
Lambda listener
JButton btn = new JButton("My Button");
btn.addActionListener(e -> {
System.out.println("Button was pressed");
});
Lambdas and Execute-around Pattern
There are several good examples of using lambdas as a FunctionalInterface in simple scenarios. A fairly common use case that can be improved by lambdas is what is called the Execute-Around pattern. In this pattern, you have a set of standard setup/teardown code that is needed for multiple scenarios surrounding use case specific code. A few common example of this are file io, database io, try/catch blocks.
interface DataProcessor {
void process( Connection connection ) throws SQLException;;
}
public void doProcessing( DataProcessor processor ) throws SQLException{
try (Connection connection = DBUtil.getDatabaseConnection();) {
processor.process(connection);
connection.commit();
}
}
Then to call this method with a lambda it might look like:
public static void updateMyDAO(MyVO vo) throws DatabaseException {
doProcessing((Connection conn) -> MyDAO.update(conn, ObjectMapper.map(vo)));
}
This is not limited to I/O operations. It can apply to any scenario where similar setup/tear down tasks are applicable with minor variations. The main benefit of this Pattern is code re-use and enforcing DRY (Don't Repeat Yourself).
Using lambda expression with your own functional interface
Lambdas are meant to provide inline implementation code for single method interfaces and the ability to pass them around as we have been doing with normal variables. We call them Functional Interface.
For example, writing a Runnable in anonymous class and starting a Thread looks like:
//Old way
new Thread(
new Runnable(){
public void run(){
System.out.println("run logic...");
}
}
).start();
//lambdas, from Java 8
new Thread(
()-> System.out.println("run logic...")
).start();
Now, in line with above, lets say you have some custom interface:
interface TwoArgInterface {
int operate(int a, int b);
}
How do you use lambda to give implementation of this interface in your code? Same as Runnable example shown above. See the driver program below:
public class CustomLambda {
public static void main(String[] args) {
TwoArgInterface plusOperation = (a, b) -> a + b;
TwoArgInterface divideOperation = (a,b)->{
if (b==0) throw new IllegalArgumentException("Divisor can not be 0");
return a/b;
};
System.out.println("Plus operation of 3 and 5 is: " + plusOperation.operate(3, 5));
System.out.println("Divide operation 50 by 25 is: " + divideOperation.operate(50, 25));
}
}