I like using Lambda Expression because it makes my code cleaner. However, I realized I didn’t fully understand why and how Lambda Expression works. In this post, I’d like to share what I learned about Anonymous Class, Functional Interface, and Lambda Expression.
Anonymous Inner Class
Suppose you have an interface Animal.
Let’s declare a class Pig that implements the interface Animal.
We can now instantiate this class with the new keyword.
Animal pig = new Pig();
This was wordy. What if there’s a much shorter way? An Anonymous Class allows you to declare and instantiate a class at the same time.
As you can see, the Anonymous Inner class is self-explanatory. It is an inner class because it is a class within a class. It is anonymous because it has no name. It pretty much looks like a constructor except that its class definition is included in its body.
- Unlike a local class, a declaration, an Anonymous Class is an expression
- Use an Anonymous Class when you want to use the local class once
- Anonymous class usually extends a subclass or implement an interface
Functional Interface is simply an interface that has exactly one abstract method. For example, the interface Animal is a Functional Interface.
- You can annotate functional interfaces with @FunctionalInterface
- Functional Interface is also called Single Abstract Method (SAM) Interface
Functional Interface and Lambda Expression
As you can see, using Anonymous class makes your code much concise. What if you can make it even shorter? When implementing a functional interface, you can use Lambda Expression.
Animal tiger = (sound) -> "Tiger " + sound;
How is this possible? Tiger’s type is Animal and since Animal is an interface with only one method, the compiler is smart enough to know that we are overriding sound(). Compare how short the code is compared to Pig Class.
The basic syntax for Lambda expression is as simple as this:
parameter -> expression
When there is one or more parameter, you can add like this:
(param1, param2) -> expression
No matter how you implemented them, all animals can be called exactly the same!
System.out.println(pig.cry("oink")); // Pig oinkSystem.out.println(cat.cry("meows")); // Cat meowsSystem.out.println(tiger.cry("roar"));// Tiger roar
Example 1: Predefined Functional Interfaces
In fact, Java has provided us with several functional interfaces.
- Supplier<T> represents a supplier, which returns a value, type T.
- Consumer<T> represents an operation that takes one parameter, type T, and consumes T. It returns void.
- Function<T, R> represents a function that takes one parameter, type T, and returns a value, type R.
- Predicate<T> represents a boolean-valued function that takes one parameter, type T, and returns a boolean.
- BiFunction<T, U, R> represents a function that takes two parameters, type T and type U, and returns a value, type R.
Let’s try using one of these predefined functional interfaces for the Animal example above. I’ve slightly tweaked the example above.
The method cry(String sound) takes in one argument, a String, and returns a String value. This is what Function<String, String> does!
We can substitute Animal interface with Function interface.
Or using Anonymous class, we can make it shorter.
Or using Lambda Expression, we can make it even shorter.
Example 2: Real Java Example - stream().forEach()
You might have used stream().forEach before. Simply put, it is a stream(💦 🦦 💦) of data, which you can apply many operations. It’s a more elegant way of using for-loops or for-each. Internally, Java uses functional interface to provide a simple and concise Stream API.
Suppose you have a list of Integers:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
You want to multiply each element by 10 and print each of them. You can easily do this, using one of Stream’s method, forEach().
numbers.stream().forEach(t -> System.out.println(t * 10));
Doesn’t this syntax look familiar to what we’ve been doing? In fact, forEach takes a parameter type of Consumer<T>.
So basically, you can write the same code using an Anonymous class!
The Ultimate Goal — Functional programming
Clearly, using Functional Interface and Lambda Expressions add syntactic sugar to your code. It lets you use functions as arguments or return a function as a result without having to write verbose code. This is similar to how Functional Programming treats functions as first-class citizens. So besides making your code cleaner, it helps you to achieve some benefits of functional programming.
In the next post, I’ll discuss how we can adopt functional programming in Java, which is technically a general-purpose programming language, designed to follow Object-Oriented Programming.