Lambda – functional interface and it must contain exactly one abstract method declaration. Since default methods are not abstract you’re free to add default methods to your functional interface. It is like abstract class with single abstract method right? Not exactly. There are 2 differences:
- Functional Interface cannot carry state while abstract class can.
- Class can inherit many interfaces but only single abstract/concrete class.
You may ask, “wait…isn’t the diamond problem from multiple inheritances will be surfaced out because of that?”. Short answer is yes. If your class inherits 2 Functional Interfaces A and B and both have the same method name and signature m. Your class need to explicitly tell which one you are intended to use via “A.super.m()” or “B.super.m()”. You can read this article for a bit more detail. However, Function Interface is not created just to make multiple inheritance possible. Its main job is to free up methods from classes and carry them around. I will go over this point in more detail below. First, lets take a look at how we code differently after lambda was introduced.
How we do the same things in different ways?
I have borrowed some examples from the web to illustrate some syntax sugar and usages of lambda here. If you are curious about where I took those examples, you can check the reference section below.
How for loop get replaced by lambda
This example shows you that some methods like forEach, that take Function Interface as input parameter, were introduced to the Collection classes since Java 8. When a method is defined to take Function Interface as input parameter, you can pass lambda to it. Because of the forEach method above, you now can move the external iteration to internal. What is the benefit of it apart from less code to write? It separates the what from the how of the regular for-each loop to make optimization possible. As the regular loop is inherently sequential, and must process the elements in the order specified by the collection.
This may appear to be a small syntactic change, but the difference is significant. The control of the operation has been handed off from the client code to the library code, allowing the libraries not only to abstract over common control flow operations, but also enabling them to potentially use laziness, parallelism, and out-of-order execution to improve performance.
How anonymous inner class get replaced by lambda
Before lambda, you use anonymous inner class to pass a function around. It is quite verbose and boilerplate code just to make it possible. With lambda, the code becomes much cleaner. Underneath the hood, lambda is not just a syntax sugar for anonymous inner class. When you compile the code with lambda, you don’t see an inner class file generated b/c it uses invokedynamic introduced in Java 7 to do the magic. As a result, the program has smaller class file in size, less in memory consumption and more efficient.
Different Lambda Expression Syntax
Create your first Functional Interface
Define Functional Interface
If you want to create a Functional Interface, I recommend you to put the @FunctionalInterface annotation along your interface class so your compiler can help you to check if it meets requirements. It is optional but a good practice. For the example, we create a function interface that takes in one input data type F and return another data type T.
Have you notice the code above that we didn’t define any lambda expression for the Converter? Instead we simply use an existing static method from Integer and an instance method from a class we just define? Both of these methods have the signature conformed with the Converter Functional Interface that takes single input of a type to an output of another or same type. Method name is not even considered. The requirement is quite loose, isn’t it? That is to say, Functional Interface allows us to port methods out from classes and move them around. This implication is big in Java especially to the ways we design our program. You can see a handful of design patterns may become too verbose if not unnecessary after lambda is introduced. We will talk about this in more detailed on another article.
Take a Deeper Look at How Lambda been used in Java 8
Now you know about Functional interface that allows only one abstract method and how to write lambda expression and use method reference. It is time to check out some real life examples in Java 8 and learn from them. Start from some predefined Functional Interfaces in Java 8 and what are their characteristics.
- Consumer <T> – void accept(T t)
- single input without output.
- it has a default method andThen(Consumer<? super T> after) that let you chain a list of Consumer and execute them in order.
- if you need to take 2 input parameters, you can use BiConsumer<T,U>.
- Supplier <T> – T get()
- no input with an output of type T
- Predicate <T> – boolean test(T t)
- single input with a boolean return.
- it has 4 default methods: and, or, negate and isEqual that let you to combine Predicates into a Predicate.
- Function <T, R> – R apply(T t)
- single input with single output and both of them can be in different types.
- it has 3 default methods: 2 chaining related methods and an identity method. For chaining methods, it has compose method that run your input Function first before this Function while andThen method will run your input Function after this Function.
- If you need to take 2 input parameters, you can use BiFunction<T,U,R>
First, I will take a look at CompletableFuture. It is another important feature introduced by Java 8 apart from Lambda and Stream. If you have used Future before and felt that it is so limited, CompletableFuture will be a good news for you. This article provides you a great introduction of it in case you don’t know about it. Our focus here is to examine its api design and understand how they apply Lambda to make the API so flexible and powerful. For CompletableFuture, most of its methods take Functional Interface as input and return CompletableFuture back. This design allows you to chain lambda in various ways. With some constructs like thenCombined and acceptEither, you can even define dependency among lambda. Lets examine some interesting methods there first.
- Methods like thenAccept and thenRun normally put at the end of the chain as its input job returns nothing back to fuel the downstream of the pipe.
- Methods like thenApply takes Function as input and return CompletableFuture<U> so you can put them in the middle of the processing pipe.
Stream is another good example that shows you how they chain up Predicate for filtering. Actually, it is so powerful that I will have another article to discuss about it in detailed.