Java8 Lambdas
February 22, 2017 Leave a comment
Lambdas enable functional programming in Java. Concept of Lambda is available in many languages but the beauty of it in Java is its BACKWARD COMPATIBILITY. In this post we will discuss below areas
- Concept
- Syntax
- Functional Interfaces
- Variable Capture
- Method References
- Default Methods
Concept
Lambda can be defined as
- a way of defining anonymous functions
- It can be passed to variables
- It can be passed to functions
- Can be returned from functions
What are lambda good for?
- These are the base for functional programming model
- Makes parallel programming easier: If we want to make 100s of cores busy then its easier to do with functional programming than that of the object oriented programming
- Write compact code (Hadoop1 in Java: 200k lines of code VS Spark1 in Scala: 25k Lines of Code)
- Richer Data Structure Collections
- Develop Cleaner APIs
Syntax
List<Integer> integers = Arrays.asList(1,2,3); integers.forEach(x -> System.out.println(x)); OR integers.forEach((x) -> { x = x+10; System.out.println(x); }); OR integers.forEach((Integer x) -> { x = x+10; System.out.println(x); });
We can explicitly mention types, but Java8 compiler is able to do Type Inference
Lambda Expression in Java (from video: A Peek Under the Hood by Brian) is converted into a Function and then we call the generated function
Functional Interfaces (FI)
An interface with just one method (one NON-DEFAULT Method) is called a Functional Interface.
Prior to Java8 we use to write the function with signature, open a curly brace and then write body of the function and close it but In Java8:
// define FI @FunctionalInterface // enforces that the interface is FI (it fails compilation if below interface has more than one method) // Its optional and can be applied only to interfaces public interface Consumer<T> { void accept(T t); } // give the definition Consumer<Integer> consumer = x -> System.out.println(x); // use it List<Integer> integers = Arrays.asList(1,2,3); integers.forEach(consumer);
Few things to notice here:
- Here we are separating the body of the function (Line #10) from its signature(Line #6).
- The method generated from lambda expression must have same signature as that of the FI(see Line#67: lambda takes one arg ‘x’, throws no Exception and returns nothing)
- In Java8, the type of the lambda expression is same as that of the FI that lambda is assigned to. (see Line#67)
Variable Capture (VC)
Lambdas can interact with variables (local, instance and static) defined outside the body of lambda (aka VC).
List<Integer> integers = Arrays.asList(1,2,3); int vc=10; integers.forEach(x -> System.out.println(x+vc));
Note: Local variables accessed and used inside the Lambda are final and cannot be modified.
Lambda vs Anonymous Inner Classes
- Inner classes can have state in the form of class level instance variables whereas lambdas cannot.
- Inner Classes can have multiple methods whereas Lambda’s cannot
- ‘this’ points to the object instance of anonymous inner class whereas it points to the enclosing object for lambda
java.util.function.* contains 43 most commonly used functional interfaces
- Consumer: functions which takes argument of type T and returns void
- Supplier: functions that takes no argument and returns a result of type T
- Predicate: functions which takes argument of type T and returns boolean
- Function<T, R>: function that takes an argument of type T and returns a result of type R
- …
Method References (MRs)
As lambda being a way to define anonymous function, there is a good chance that the function we want to use exists already. In these cases, MRs can be used to pass an existing function in place where lambda is expected
@FunctionalInterface public interface Consumer<T> { void accept(T t); } public void doSomething(Integer x) { System.out.println(x); } Consumer<Integer> cons1 = x -> doSomething(x); cons1.accept(1); // Reuse with MR Consumer<Integer> cons2 = Example::doSomething; cons2.accept(2);
Note: The signature of the referenced method must match the signature of FI method.
By looking at above definition it is obvious that MR works on method with only one argument and no return type
Referencing a Constructor: Constructor method references are quite handy while working with Streams
// Create a new function which has a method that takes 'String' as parameter (LHS to arrow), returns 'Integer' (RHS to arrow as body of method) Function<String, Integer> mapper1 = x -> new Integer(x); System.out.println(mapper1.apply("11")); // Refer a Cons Function<String, Integer> mapper2 = x -> Integer::new; System.out.println(mapper2.apply("22"));
References to a specific object instance method:
Consumer<Integer> cons1 = x -> doSomething(x); cons1.accept(1); // can also be written as: this invokes the println() method on System.out object by passing param '2' Consumer<Integer> cons2 = System.out::println; cons2.accept(2);
Default Methods ***
This is very important feature because it addresses Interface Evolution Problem: How a published interface (like List, Iterable, etc) can be evolved without breaking existing implementations (backward compatible)
Default Method: A default method on a java interface has an implementation provided in the interface and is inherited by the classes that implements it.
public Iterable<T> { Iterator<T> iterator; default void forEach(Consumer<? super T> action) { for(T t: this) { action.accept(t); } } }
References:
https://www.youtube.com/watch?v=MLksirK9nnE
https://www.youtube.com/watch?v=8pDm_kH4YKY
Recent Comments