Java 8: An Introduction to Lambda Expressions

13 Andrew Willis

Lambda expressions arrived in Java 8 promising to make it quicker & easier to implement one method interfaces using an expression as well as providing the ability to use functional programming.

The benefits of lambda expressions

As a quick way of seeing what lambda expressions can do for us let’s take a look at the following example from pre-Java 8 code where we would want to submit a task to an executor. Here we must create an instance of the Runnable interface, via an anonymous class, to provide the task to run:

executor.execute(new Runnable()
{
	@Override
	public void run()
	{
		System.out.println("Hello World!");
	}
});

 

There’s nothing wrong with this, it’s just a bit verbose. Using lambda expressions, we can greatly simplify this by do the following:

executor.execute(() -> System.out.println("Hello World!"));

// OR

executor.execute(() ->
{
	System.out.println("Hello World!");
});

// OR

Runnable runnable = () -> System.out.println("Hello World!");
executor.execute(runnable);

 

Isn’t that a lot cleaner and simpler? Behind the scenes you’re still getting an instance of Runnable but all we’re doing now is specifying the actual method body. Effectively lambda expressions enable you to treat functionality as a method argument or code as data.

But why does that work?

Because of functional interfaces.

Functional interfaces are normal interfaces annotated with “@FunctionalInterface” that have just one abstract method – they still may have any number of default or static methods though. Because of this the compiler knows which method you’re implementing & allows you to skip instantiating a new instance of the interface as well as specifying the name of the method.

How do you create a lambda expression?

So now that we know what a lambda expression is and why we can use them, let’s look at the syntax of one.

To start with you declare the comma separated list of parameters specified by the abstract method. These should be enclosed by parentheses (“(” & “)”) although you may omit these if there is only one parameter.

You can specify the data types of the arguments if you wish but there is no need to.

Next you have the arrow token, “->”, followed by the statement block which should be enclosed by braces (“{“ & “}”) unless it is a single expression.

With single expressions, the JVM evaluates the expression & returns its value, if not invoking a void method, allowing you to shorten the code further.

To show this in action let’s take a look at one of the many built functional interfaces, “java.util.function.Predicate”. Here we have a single abstract method called “test” which takes a generic argument & returns a boolean value to show whether the test passed:

 

@FunctionalInterface
public interface Predicate
{
    boolean test(T t);
    
    // Default & static methods as well as Javadoc omitted for brevity
}

To show the different syntax examples let’s assume that we’re testing equality of strings:

// Single expressions - with & without data type & with & without parenthesis (without only allowed because there is a single argument)
Predicate version1 = (String string) -> "some-string".equals(string);
Predicate version2 = (string) -> "some-string".equals(string);
Predicate version3 = string -> "some-string".equals(string);

// Statement blocks
Predicate version4 = (string) ->
{
	// Note that once we use a 'return' statement we need to use a statement  block
	return "some-string".equals(string);
};
Predicate version5 = string ->
{
	// More than one statement requires a block statement
	String transformedString = string.toUpperCase();
	return "SOME-STRING".equals(transformedString);
};

 

How do you run lambda expressions?

So now we know how to create lambda expressions, how do we run them? Easy – we just call the abstract method from the instance of the class that is created for us. For example, using the one of the Predicates created above it would look like:

boolean result = version1.test("some-string");

One final thing to note is that you’re still able to use variables with lambda expressions. The only caveat is that when referencing local variables from one they must be final or effectively final.

In the example below, the local variable can be used without issue because it is effectively final. If you were however to uncomment the line which sets it to another value that would no longer be the case and the compiler would throw an error:

public void doSomething()
{
	String localVariable	= "some-string";
	Predicate test	= string -> localVariable.equals(string);
	boolean result		= test.test("some-string");
	
	//localVariable		= "some-other-string";
}

In conclusion, we’ve seen that lambda expressions can make our lives a lot easier as Java developers by reducing the amount of code that we need to produce, and letting us pass method code around as method arguments or variables.

In a future blog post we’ll look at Streams where lambda expressions can be used to great effect to simplify a lot of tasks. For more information see the Java Tutorial on Lambda Expressions.

Contact our website development solutions team today to find out more about our services 020 3813 5351.