
With over 60% of professional developers still using Java 8 in the beginning of 2021, understanding the features of Java 8 is an essential skill. Java 8 was released in 2014, bringing with it a heap of new features.
Among these changes were features that allowed Java developers to write in a functional programming style. One of the biggest changes was the addition of lambda expressions.
Lambdas are similar to methods, but they do not need a name and can be implemented outside of classes. As a result, they open the possibility for fully functional programs and pave the way for more functional support from Java in the future.
Today, I’ll help you get started with lambda expressions and explore how they can be used with interfaces.
Here’s what this blog cover :
What are lambda expressions?
How to write a lambda expression
Interfaces in Java
What are lambda expressions? Lambda expressions are an anonymous function, meaning that they have no name or identifier. They can be passed as a parameter to another function. They are paired with a functional interface and feature a parameter with an expression that references that parameter. The syntax of a basic lambda expression is: parameter -> expression The expression is used as the code body for the abstract method (a named but empty method) within the paired functional interface. Unlike most functions in Java, lambda expressions exist outside of any object’s scope. This means they are callable anywhere in the program and can be passed around. In the simplest terms, lambda expressions allow functions to behave like just another piece of data.
Lambda use cases in Java: Lambda expressions are used to achieve the functionality of an anonymous class without the cluttered implementation. They’re great for repeating simple behaviors that could be used in multiple areas across the program, for example, to add two values without changing the input data. These properties make lambda especially useful for functional programming styles in Java. Before Java 8, Java struggled to find tools to meet all the principles of functional programming.
Functional programming has 5 key principles:
Pure functions: Functions that operate independently from the state outside the function and contain only operations that are essential to find the output.
Immutability: Inputs are referenced, not modified. Functions should avoid complex conditional behavior. In general, all functions should return the same value regardless of how many times it is called.
First-class functions: Functions are treated the same as any other value. You can populate arrays with functions, pass functions as parameters, etc.
Higher-order functions: Higher-order functions either one or more functions as parameters or return a function. These are essential to creating complex behaviors with functional programming.
Function Composition: Multiple simple functions can be strung together in different orders to create complex functions. Simple functions complete a single step that may be shared across multiple tasks, while complex functions complete an entire task.
Lambda functions are pure because they do not rely on a specific class scope. They are immutable because they reference the passed parameter but do not modify the parameter’s value to reach their result. Finally, they’re first-class functions because they can be anonymous and passed to other functions. Lambda expressions are also used as event listeners and callback functions in non-functional programs because of their class independence.
How to write a lambda expression in Java
As we saw earlier, the basic form of a lambda expression is passed a single parameter.
parameter -> expression
A single lambda expression can also have multiple parameters:
(parameter1, parameter2) -> expression
The expression segment, or lambda body, contains a reference to the parameter. The value of the lambda expression is the value of the expression when executed with the passed parameters.
For example:
import java.util.ArrayList;
public class main {
public static void main(String[] args) {
ArrayList<Integer> numbers = new ArrayList<Integer>();
numbers.add(5);
numbers.add(9);
numbers.add(8);
numbers.add(1);
numbers.forEach( (n) -> { System.out.println(n); } );
}
}
The parameter n is passed to the expression System.out.println(n). The expression then executes using the value of the parameter n in the print statement. This repeats for each number in the ArrayList, passing each element in the list into the lambda expression as n. The output of this expression is therefore a printed list of the ArrayList’s elements: 5 9 8 1.
Lambda Function Body The lambda function body can contain expressions over multiple lines if encased in curly braces. For example:
(oldState, newState) -> {
System.out.println("Old state: " + oldState);
System.out.println("New state: " + newState);
}
This allows for more complex expressions that execute code blocks rather than a single statement. You can also return from lambda functions by adding a return statement within the function body.
public static Addition getAddition() {
return (a, b) -> a + b; // lambda expression return statement
}
Lambda even has its own return statement: (a, b) -> a + b; The compiler assumes that a+b is our return value. This syntax is cleaner and will produce the same output as the previous example. Regardless of how long or complex the expression gets, remember that lambda expressions must immediately output a consistent value. This means an expression cannot contain any conditional statements like if or while and cannot wait for user input. All code within the expression must have an immutable output regardless of how many times it is run.
Lambdas as Objects
You can send lambdas to other functions as parameters. Imagine that we want to create a greeting program that is open for more greeting functions to be added in different languages.
@FunctionalInterface
public interface Greeting {
...... void greet();
}
Here the expression itself is passed, and the greet(); function is immediately executed. From here, we can add additional greet functions for different languages that will override to print only the correct greeting.
Functional Interfaces:
Lambda expressions can only implement functional interfaces, which is an interface with only one abstract method. The lambda expression essentially provides the body for the abstract method within the functional interface. If the interface had more than one abstract method, the compiler would not know which method should use the lambda expression as its body. Common examples of built-in functional interfaces are Comparator or Predicate. It’s best practice to add the optional @FunctionalInterface annotation to the top of any functional interface. Java understands the annotation as a restriction that the marked interface can have only one abstract method. If there is more than a single method, the compiler will send an error message. Using the annotation ensures that there is no unexpected behavior from lambda expressions that call this interface.
@FunctionalInterface
interface Square
{
int calculate(int x);
}
A functional interface can have methods of object class. See in the following example.
@FunctionalInterface
interface Square
{
int calculate(int x);
}
Default methods in interfaces
While functional interfaces have a limit on abstract methods, there is no limit on default or static methods. Default or static methods can fine-tune our interfaces to share different behaviors with inheriting classes.
Default methods can have a body within an interface. Most importantly, default methods in interfaces to provide additional functionality to a given type without breaking down the implementing classes.
Before Java 8, if a new method was introduced in an interface, all the implementing classes would break. To fix it, we would need to individually provide the implementation of that method in all the implementing classes.
Static methods in interfaces
The static methods in interfaces are similar to default methods, but they cannot be overridden. Static methods are great when you want a method’s implementation to be unchangeable by implementing classes.
Conclusion: Lambda expressions in Java have revolutionized the way developers approach functional programming, paving the way for more expressive, concise, and readable code. By understanding the syntax, benefits, and use cases of lambda expressions, Java developers can unlock the full potential of functional programming and enhance their coding skills. Embrace the power of lambda expressions and embark on a journey towards more elegant and efficient Java development.
Comments