Table of Contents
- Table of Contents
- Introduction
- Syntax
- Single Instruction Lambda Expressions
- Multi-Instruction Lambda Expressions
- JaCoCo Code Coverage
- Summary
Introduction
As of Java 8, we can use lambda expressions, which are, in essence, unnamed methods with some additional restrictions. Just like a method, they may have input parameter(s) and return a value.
Syntax
Depending upon the use case, 0 or more parameters may be explicitly passed into the lambda expression.
Zero Explicit Input Parameters
We do not always need to pass any explicit inputs into the lambda expression, such as if we are calling the lambda expression outside of a Java stream. In those cases, we can invoke a Lambda expression prefixed with a () like so:
@ParameterizedTest
@NullSource
void name_whenNullInConstructor_thenExceptionThrown(String name) {
// Setup
// Execution
Executable executable = () -> new App(name);
// Validation
Exception exception = assertThrows(IllegalArgumentException.class, executable);
assertEquals("newName is marked non-null but is null", exception.getMessage());
}
In this example, although there are no explicit inputs, as can be seen by the (), we can reference variables available in the current scope, such as name, as long as they are either final or functionally final (not reassigned).
One Explicit Input Parameter
If we need to pass a single explicit input parameter to the lambda expression, such as if iterating through a list, we can name the explicit input parameter that is coming into the lambda expression from the stream in the following way:
public static String getGreetingForMultipleNames(String[] namesToGreet) {
StringBuilder stringBuilder = new StringBuilder(INITIAL_GREETING_CAPACITY);
String prefix = "Hello";
stringBuilder.append(prefix);
if (namesToGreet != null) {
List<String> distinctNames =
Arrays.stream(namesToGreet)
.filter(item -> !item.isBlank()) // Filter out blank strings
...
In this example, we did not surround the item with parenthesis, but we could have also written this as:
public static String getGreetingForMultipleNames(String[] namesToGreet) {
StringBuilder stringBuilder = new StringBuilder(INITIAL_GREETING_CAPACITY);
String prefix = "Hello";
stringBuilder.append(prefix);
if (namesToGreet != null) {
List<String> distinctNames =
Arrays.stream(namesToGreet)
.filter((item) -> !item.isBlank()) // Filter out blank strings
...
With or without explicit parenthesis, we can use the the item variable inside of the lambda expression because it is called out in this manner.
Two Explicit Input Parameters
If we need to pass two explicit input parameters into the lambda expression, then we can do so in a comma delimited list inside parenthesis:
String lastValue =
distinctNames.stream()
.reduce((first, second) -> second) // Find last value
...
In this scenario, the surrounding parenthesis are mandatory. Here, we are able to define two input parameters, called first and second.
Single Instruction Lambda Expressions
If we only have one instruction within the lambda expression, then the return type is implicitly defined by the return type / output of that instruction, such as:
public static String getGreetingForMultipleNames(String[] namesToGreet) {
StringBuilder stringBuilder = new StringBuilder(INITIAL_GREETING_CAPACITY);
String prefix = "Hello";
stringBuilder.append(prefix);
if (namesToGreet != null) {
List<String> distinctNames =
Arrays.stream(namesToGreet)
.filter((item) -> !item.isBlank()) // Filter out blank strings
...
Here, the String.isBlank() method invoked within the lambda expression returns a boolean value. Therefore, the single-instruction lambda expression also returns a boolean value.
Multi-Instruction Lambda Expressions
If multiple instructions are needed inside of a lambda expression, we can surround them with curly braces {} to indicating the block of instructions – where the lambda expression’s instructions start and end. In this case, we will need to add an explicit return statement, if we want to return anything from the lambda expression, though:
...
distinctNames.forEach(
name -> {
// Separate names with a comma
if (stringBuilder.length() > prefix.length()) {
stringBuilder.append(',');
}
// Space between each segment
stringBuilder.append(' ');
// The last name should be preceded with and
if (lastValue != null && lastValue.equals(name)) {
stringBuilder.append("and ");
}
stringBuilder.append(name);
});
...
The code snippet above contains no return statement, so nothing is returned from the lambda expression. However, were we to add a return statement to that block, our lambda expression would return that type of output.
JaCoCo Code Coverage
As a lambda expression is, in essence, an unnamed method, it is represented as such in JaCoCo Code Coverage reports and minimum code coverage checks. As an example, this method contains 4 lambda expressions:
public static String getGreetingForMultipleNames(String[] namesToGreet) {
StringBuilder stringBuilder = new StringBuilder(INITIAL_GREETING_CAPACITY);
String prefix = "Hello";
stringBuilder.append(prefix);
if (namesToGreet != null) {
List<String> distinctNames =
Arrays.stream(namesToGreet)
.filter(Objects::nonNull) // Filter out null values
.filter((item) -> !item.isBlank()) // Filter out blank strings
.distinct() // Filter out duplicate values
.sorted() // Alphabetized
.toList(); // Save as a list
String lastValue =
distinctNames.stream()
.reduce((first, second) -> second) // Find last value
.orElseGet(() -> null); // Default to null
distinctNames.forEach(
name -> {
// Separate names with a comma
if (stringBuilder.length() > prefix.length()) {
stringBuilder.append(',');
}
// Space between each segment
stringBuilder.append(' ');
// The last name should be preceded with and
if (lastValue != null && lastValue.equals(name)) {
stringBuilder.append("and ");
}
stringBuilder.append(name);
});
}
stringBuilder.append('!');
return stringBuilder.toString();
}
Therefore, they are referenced as lambda…$1, lambda…$2 and lambda…$3 in the JaCoCo report:
- lambda$getGreetingForMultipleNames$0(String) // AKA (item) -> !item.isBlank()
- lambda$getGreetingForMultipleNames$1(String, String) // AKA (first, second) -> second
- lambda$getGreetingForMultipleNames$2() // AKA () -> null
- lambda$getGreetingForMultipleNames$3(StringBuilder, String, String, String) // AKA name -> {
As can be seen on the last method, even though we only explicitly called out the name input, there are additional, implicit inputs that are passed in because they are referenced within the lambda expression. Again, these implicit inputs must be either carry the final keyword, or be effectively final (with no reassignments within the given scope).
Summary
Java’s lambda expressions are unnamed functions that can be defined in-line within the source code where they would be consumed. Lambda expressions have explicit inputs defined in the source code and implicit inputs implied by any variable(s) in the given scope used within the lambda expression. Explicit inputs are often sourced from Java streams. Implicit inputs are references to variables available within the given scope and must either carry the final keyword or be effectively final (have no reassignment within that scope). When examined for Maven plugins, such as JaCoCo code coverage reports or minimum checks, lambda expressions are treated as separate, unnamed functions.

Leave a comment