What is Lambda Expression?
Lambda expression is also known as Anonymous Function. A function without the name nor bounded to an identifier.
It is First-class Function. That's mean we can pass the anonymous function as an argument to other functions, return it as a value from other functions, assign it to variables or store it into a data structure.
It models code as data which can be passed around and invoked in the later stage in the other functions.
It is First-class Function. That's mean we can pass the anonymous function as an argument to other functions, return it as a value from other functions, assign it to variables or store it into a data structure.
It models code as data which can be passed around and invoked in the later stage in the other functions.
Why does Java need Lambda?
- To eliminate the boilerplate code (of using anonymous inner class) in modeling the "code as data" so that it become lightweight, shorter and straight to the point. This is handy when working with libraries and frameworks that are designed to accept callback object which provides nothing but just a function. The typical example is to provide a sole purpose ActionListener to a JButton in Swing framework.
button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { label.setText(e.getActionCommand() + " is clicked"); } });
Using lambda expression, we can do the same thing in just one line as below.
button.addActionListener(e -> label.setText(e.getActionCommand() + " is clicked"));
- To gain the advantage of declarative programming. "Declarative Programming - A style of building the structure and elements of computer programs—that expresses the logic of a computation without describing its control flow" - Wikipedia. In short, it is a programming style that describes the what to be achieved and hides the how to accomplish it. For example, prior to Java 8, iteration on a Java Collection has to be done explicitly and each element has to be processed in sequence.
// for each of the increment of the integer i, start from 0 until we reach the size of list for (int i=0; i<list.size(); i++) { String e = list.get(i); // get the element from the list System.out.println(e); // print the element }
Using lambda expression in Java 8, the iteration can be abstracted and done internally by the library.
// for each element in the list, print the element list.forEach((e) -> System.out.println(e));
As you can see, this abstraction makes the code easier to read. No more grandmother's tales. Besides, this abstraction also provides the opportunity to the Java Collections library to manage the control flow more efficiently and better performance by using of reordering of the data, parallelism, short-circuiting, or laziness. All these implementations detail are hidden from the programmer. - To be friendly with functional programming. The programmer now has another choice of approach/design to resolve a problem beside object-oriented pattern. The Java Collections framework had adopted the functional design pattern and provides the Streams API which is a good place for the programmer to getting start with functional programming. Furthermore, the idea of stateless and immutable data in functional programming make the program run safely and predictable in the multi-threaded environment. You can find more examples in "Functional programming in Java" section.
Lambda expression in Java
The lambda expression appears in the form of function. In general, a function is a piece of calculation that takes the input and return an output. The arrow token -> is used to separate the parameter (on the left) and expression body that might return value (on the right). Below are examples of lambda expression in Java. Note, the first four examples are doing the same thing.
// parameter -> expression body (int x) -> { return x * x; }; // without explicit type information (x) -> { return x * x; }; // without return syntax and curly bracket (x) -> x * x; // without bracket for parameter x -> x * x; // without parameter and return value () -> System.out.println("x");
Lexical Scope
Unlike the inner class, lambda expression does not create a new level of scoping. It is lexically scoped which mean the names (and this) in the lambda expression body are interpreted just as they are in its enclosing environment. Refer to the code example below, see the different and get the idea.
public class HelloWorld { Runnable inner1 = new Runnable() { // New scope is created @Override public void run() { // "this" refers to inner1 instance itself System.out.println(this); } }; Runnable inner2 = new Runnable() { // New scope is created @Override public void run() { // "toString()" of inner class has shadowed the "HelloWorld.toString()" System.out.println(toString()); } }; // "this" refers to this HelloWorld instance Runnable lambda1 = () -> System.out.println(this); // "HelloWorld.toString()" Runnable lambda2 = () -> System.out.println(toString()); public String toString() { return "Hello, world!"; } public static void main(String... args) { new HelloWorld().inner1.run(); // HelloWorld$1@548c4f57 new HelloWorld().inner2.run(); // HelloWorld$2@1218025c new HelloWorld().lambda1.run(); // Hello, world! new HelloWorld().lambda2.run(); // Hello, world! } }
How does Java promote Function to First-Class Citizen?
A programming language supports "first-class function" if it treats the function as the first class citizen. Java is primary object-oriented programming language where a function must reside in an object/class. In order to support the first-class function, Java requires Functional Interface as the type for lambda expression and perform Target Typing to fade out the existence of an object while at the same time only highlight the function of this object.
Functional Interface
Every instance in Java must have a type. Java does not introduce new type such as Function Type for lambda expression due to the challenges of complexities and incompatibility with Java typing system. Instead, Java converts lambda expression to a functional interface type, an interface which only has the single abstract method.
The functional interface is best fit into Java typing system and compatible with all common existing libraries. The existing Java libraries can have benefited from lambda expression without radical change or rewrite.
We can create our own functional interface. By marking the interface class with @FunctionalInterface, the compiler will make sure it fits the requirement of being a functional interface.
We can also make use of the common functional interfaces provided by Java.
The functional interface is best fit into Java typing system and compatible with all common existing libraries. The existing Java libraries can have benefited from lambda expression without radical change or rewrite.
We can create our own functional interface. By marking the interface class with @FunctionalInterface, the compiler will make sure it fits the requirement of being a functional interface.
We can also make use of the common functional interfaces provided by Java.
- Function<T, R>: Represents a function that accepts one argument and produces a result.
e.g. (Integer x) -> x.toString(); - Consumer<T>: Represents an operation that accepts a single input argument and returns no result.
e.g. (String x) -> System.out.println(x); - Supplier<T>: Represents a supplier of results.
e.g. () -> "Hello"; - Predicate<T>: Represents a predicate (boolean-valued function) of one argument.
e.g. (String x) -> x.equals("X"); - UnaryOperator<T>: Represents an operation on a single operand that produces a result of the same type as its operand.
e.g. (Integer x) -> x + 2; - BinaryOperator<T>: Represents an operation upon two operands of the same type, producing a result of the same type as the operands.
e.g. (Integer x, Integer y) -> x + y;
Besides the basic functiona interfaces above, Java also provides common function interfaces for
- Primitive specification such as IntFunction<R> that accepts an int-valued argument and produces a result.
e.g. (int x) -> x.toString(); - Multi arities specification such as BiFunction<T, U, R> that accepts two arguments and produces a result.
e.g. (String x, Integer y) -> x + y;
Target Typing
The functional interface does not appear in the lambda expression as per the following example.
() -> "Hello";
Both the Callable and PrivilegedAction are the valid type for lambda expression above, but which one to choose? The answer depends on the expected type in the context (target type) where the lambda expression appears. The lambda expression must appear in the context whose target type is a functional interface. The Java compiler responsible for inferring the target type gives any compilation error if it can't find the target type for a lambda expression. Below are lambda expressions in the different context and the target types are highlighted in yellow.
The target types for the contexts above are obvious. However, this is not the case for Method argument context. Given the overloaded methods below.
// Assignment Consumer<String> c = x -> System.out.println(x); // Array initializer IntConsumer[] cc = new IntConsumer[] { x -> System.out.println(x + 1), x -> System.out.println(x + 2), x -> System.out.println(x + 3) }; // Lambda expression body Supplier<Consumer<String>> s = () -> (x) -> System.out.println(x); // Conditional expression IntFunction<Integer> f = true ? x -> 1 : x -> 2; // Cast expression Consumer<String> ccast = (Consumer<String>) (x) -> System.out.println(x); // Return statement public Consumer<String> todoLater() { return x -> System.out.println(x); }
The target types for the contexts above are obvious. However, this is not the case for Method argument context. Given the overloaded methods below.
public static void doSomething(Callable r) { } public static void doSomething(PrivilegedAction t) { } // Method argument - Method overloading doSomething(() -> "Hello"); // Compilation error: reference to doSomething is ambiguous
Passing a lambda expression into doSomething() method causes compilation error due to the overloaded methods which lead to ambiguous target type; Java compiler does not know exactly which the method is referencing to. The similar case happens to the Generic method. Given the generic method in Example class below.
public static <T> void process(UnaryOperator<T> c) { } // Method argument - Type argument inference Example.process(x -> x + 1); // Compilation error
In this case, the data type for x is ambiguous. It could be String or Integer. Whenever the Java compiler can't figure out the target type, we can help by providing the explicit type information.
// Method argument - Method overloading doSomething((Callable)() -> "Hello"); // Method argument - Type argument inference Example.<String>process(x -> x + 1);
What are the Method References?
Method references expression has the same characteristics as the lambda expression. It is encoded in a functional interface instance and requires a target type. However, instead of providing a function body, it just refers to an existing method. In other words, we can promote an existing method to become the first-class function.
The following code examples are a different kind of method references expression. I put the lambda expression together with the corresponding method reference expression, so you can see the difference and get the idea. Both of them will give the same result.
A static method (ClassName::methodName)
BiFunction<Integer, Integer, Integer> operation = (x, y) -> Integer.sum(x, y); // lambda BiFunction<Integer, Integer, Integer> operation = Integer::sum; // method reference operation.apply(1, 2); // 3
An instance method of a particular object (instanceRef::methodName)
List<String> list = Arrays.asList("a", "b"); Predicate<String> condition = (x) -> list.contains(x); // lambda Predicate<String> condition = list::contains; // method reference condition.test("a"); // true
A super method of a particular object (super::methodName)
class Person { public void print() { System.out.println("Person"); } } class Worker extends Person { private String name; public Worker(String name) { this.name = name; } @Override public void print() { System.out.println(name); } public Runnable parentPrint() { return () -> super.print(); // lambda return super::print; // method reference } } Worker worker = new Worker("HauChee"); worker.parentPrint().run(); // "Person"
An instance method of an arbitrary object of a particular type (ClassName::methodName)
Worker worker = new Worker("HauChee"); Consumer<Worker> consume = (w) -> w.print(); // lambda Consumer<Worker> consume = Worker::print; // method reference consume.accept(worker); // "HauChee"
A class constructor reference (ClassName::new)
Function<String, Worker> makeWorker = (x) -> new Worker(x); // lambda Function<String, Worker> makeWorker = Worker::new; // method reference makeWorker.apply("Lew"); // create worker with name "Lew"
An array constructor reference (TypeName[]::new)
IntFunction<int[]> makeIntArray = (i) -> new int[i]; // lambda IntFunction<int[]> makeIntArray = int[]::new; // method reference makeIntArray.apply(10); // creates an int[10]
Functional programming in Java
Java 8 lambda expression makes functional programming in Java much easier. It gives us another choice of programming style in resolving our problem. Therefore, why not get to know what is it and get a taste of it?
"Functional programming - A style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state (stateless) and mutable data (immutable)." - Wikipedia.
"Functional programming - A style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state (stateless) and mutable data (immutable)." - Wikipedia.
High Order Function
A function that takes one or more other functions as its input arguments or returns a function as the result. In the code example below, negate is a high order function that takes a function argument and returns a new function which will negate the function argument result.
Function<Integer, Boolean> isZero = x -> x == 0; isZero.apply(0); // true Function<Integer, Boolean> isNegative = x -> x < 0; isNegative.apply(-1); // true // High order function Function<Function<Integer, Boolean>, Function<Integer, Boolean>> negate = (func) -> (x) -> !func.apply(x); Function<Integer, Boolean> isNotZero = negate.apply(isZero); isNotZero.apply(0); // false Function<Integer, Boolean> isPositive = negate.apply(isNegative); isPositive.apply(-1); // false
In the next section, there are 3 different functional programming techniques are demonstrated in order to resolve the following simple formula.
x * y + z where x = 4, y = 10, z = 3
Function Composition
A technique to composites multiple functions into a function When this function is invoked the complete formula is evaluated and return the result.
Function<Integer, Integer> times10 = x -> x * 10; Function<Integer, Integer> plus3 = x -> x + 3; BinaryOperator<Function<Integer, Integer>> compose = (before, after) -> x -> after.apply(before.apply(x)); Function<Integer, Integer> times10plus3 = compose.apply(times10, plus3); // a function that composites times10 and plus3 times10plus3.apply(4); // 43 // composition API provided by Java times10.andThen(plus3).apply(4); // 43
Partial Function Application
A technique that partially resolves a portion of the formula and return a new function which carrying the resolved data. When this function is invoked then the rest of the formula portion is evaluated together with the resolved data.
BiFunction<Integer, Integer, Function<Integer, Integer>> partial = (x, y) -> z -> x * y + z; // 4 * 10 is resolved to 40 first then only plus 3 partial.apply(4, 10).apply(3); // 43
Currying
A technique that takes multiple arguments into evaluating a sequence of functions, each with a single argument. It is like replacing the arguments into the formula one by one and evaluation only happen in the final stage.
Function<Integer, Function<Integer, Function<Integer, Integer>>> curry = x -> y -> z -> x * y + z; r = curry.apply(4).apply(10).apply(3); // 43
Conclusion
The lambda expression might look weird especially for those who only practice object-oriented programming all the time. However, once we understand the concepts behind, get our hands dirty by trying it out, it can also become one of the useful tool for us in our coding life.
References:
http://www.oracle.com/technetwork/articles/java/architect-lambdas-part1-2080972.html
http://www.oracle.com/technetwork/articles/java/architect-lambdas-part2-2081439.html
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html#overview
http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-4.html
http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html
http://cr.openjdk.java.net/~briangoetz/lambda/sotc3.html
https://dzone.com/articles/java-lambda-expressions-vs
https://dzone.com/articles/why-we-need-lambda-expressions
https://www.smashingmagazine.com/2014/07/dont-be-scared-of-functional-programming/
https://dzone.com/articles/functional-programming-java-8
https://en.wikipedia.org/wiki/Anonymous_function
https://en.wikipedia.org/wiki/First-class_function
https://en.wikipedia.org/wiki/Higher-order_function
https://en.wikipedia.org/wiki/Currying
No comments:
Post a Comment