Lambda Expressions


Since Java 8 we have Lambda expressions which is a function defined under some interface contract and the main purpose is to implement functional programming in Java. Basically lambda expression is an argument list follow by an arrow token and a body or code expression.

Example 1:

(x, y)  ->  x + y;

Example 2:

() -> "Hello World!";

Example 3:

x -> x.toLowerCase();

Lambdas should be an expression, not a narrative

Avoid Specifying Parameter Types:

Type to the parameters is optional and can be omitted.

Do this:

(x, y) -> x + y;

Instead of this:

(Integer x, Integer y) -> x + y;

Avoid Parentheses Around a Single Parameter:

Do this:

x -> x.toLowerCase();

Instead of this:

(x) -> x.toLowerCase();

Avoid Return Statement and Braces:

Braces and return statements are optional in one-line lambda bodies.

Do this:

x -> x.toLowerCase();

Instead of this:

x -> { return x.toLowerCase() };

Use Method References:

It is very useful to use another Java 8 feature: method references.

So, the lambda expression:

string -> string.toLowerCase();

Could be substituted by:

String::toLowerCase;

The Power of Lambda Expressions

Java uses functional interfaces to represent lambda expression types. While it’s best to use a built-in functional interface whenever possible, you sometimes need a custom functional interface.

To create your own functional interface, do two things:

  • Annotate the interface with @FunctionalInterface, which is the Java 8 convention for custom functional interfaces.
  • Ensure that the interface has just one abstract method.

Let’s consider the following code:

public interface StringAnalyzer {
  public Boolean analyze(String text, String keyword);
}

StringAnalyzer implementation in a traditional way could be:

public class ContainsAnalyzer implements StringAnalyzer {

  public Boolean analyze(String text, String keyword){
    return text.contains(keyword);
  }

}

We can test code functionality as follow:

@Test
@DisplayName("Understands how to validate a keyword is present")
public void shouldKnowContainKeyword(){
  StringAnalyzer analyzer = new ContainsAnalyzer();
  assertTrue(analyzer.analyze("In the end, it's not the years in your life that count. It's the life in your years", "life"));
}

Using functional interfaces could be:

@FunctionalInterface
public interface StringAnalyzer {
  public Boolean analyze(String text, String keyword);
}

Test implementation:

@Test
@DisplayName("Understands how to validate a keyword is present using lambda expressions")
public void shouldKnowContainKeywordUsingLambda(){
  StringAnalyzer analyzer = (text, keyword) -> text.contains(keyword);
  assertTrue(analyzer.analyze("In the end, it's not the years in your life that count. It's the life in your years", "life"));
}

Since we are defining a interface StringAnalyzer we can use a lambda expression to implement this interface, that’s it, you define or use a functional interface that accepts and returns exactly what you want. In this case we are replacing ContainsAnalyzer by a lambda expression.

Using lambda expressions we can create more expressive code like this one:

@Test
@DisplayName("Understands how to validate a keyword is present and ends with using lambda expressions")
public void shouldAnalyzeUsingContainsAndEndsWith(){
  StringAnalyzer analyzerContains = (text, keyword) -> text.contains(keyword);
  StringAnalyzer analyzerEndsWith = (text, keyword) -> text.endsWith(keyword);

  assertTrue(analyzerContains.analyze("In the end, it's not the years in your life that count. It's the life in your years", "life"));
  assertTrue(analyzerEndsWith.analyze("In the end, it's not the years in your life that count. It's the life in your years", "years"));
}

Here is the complete test:

package com.jos.dem.lambda.analyzer;

import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;

public class AnalyzerTest {

  @Test
  @DisplayName("Understands how to validate a keyword is present")
	public void shouldKnowContainKeyword(){
		StringAnalyzer analyzer = new ContainsAnalyzer();
		assertTrue(analyzer.analyze("In the end, it's not the years in your life that count. It's the life in your years", "life"));
	}

  @Test
  @DisplayName("Understands how to validate a keyword is present using lambda expressions")
	public void shouldKnowContainKeywordUsingLambda(){
		StringAnalyzer analyzer = (text, keyword) -> text.contains(keyword);
		assertTrue(analyzer.analyze("In the end, it's not the years in your life that count. It's the life in your years", "life"));
	}

  @Test
  @DisplayName("Understands how to validate a keyword is present and ends with using lambda expressions")
	public void shouldAnalyzeUsingContainsAndEndsWith(){
		StringAnalyzer analyzerContains = (text, keyword) -> text.contains(keyword);
		StringAnalyzer analyzerEndsWith = (text, keyword) -> text.endsWith(keyword);

		assertTrue(analyzerContains.analyze("In the end, it's not the years in your life that count. It's the life in your years", "life"));
		assertTrue(analyzerEndsWith.analyze("In the end, it's not the years in your life that count. It's the life in your years", "years"));
	}

}

To browse the code go here, to download the code:

git clone git@github.com:josdem/java-workshop.git
cd lambda-expressions/string-analyzer

To test the code using Gradle:

gradle test

To test the code using Maven:

mvn test

Return to the main article