Getting started with Lambda expressions in Java

Posted May 27, 20208 min read

Learn how to use lambda expressions and functional programming techniques in Java programs

Getting Started with Lambda Expressions in Java

Prior to Java SE 8, anonymous classes were usually used to pass functionality to methods. This approach confuses the source code and makes it difficult to understand. Java 8 eliminates this problem by introducing lambda. This tutorial first introduces the lambda language features, and then provides a more detailed introduction to functional programming using lambda expressions and target types. You will also learn how lambda interacts with scope, local variables, thisand super keywords, and Java exceptions.

Please note that the code examples in this tutorial are compatible with JDK 12.

Discover the type yourself

In this tutorial, I will not introduce any non-lambda language features that you have not learned before, but I will demonstrate lambdas through types that have not been discussed before in this series. An example is the java.lang.Math class. I will introduce these types in future Java 101 tutorials. Now, I recommend reading the JDK 12 API documentation to learn more about them.

Download Get Code Download the source code of the sample application in this tutorial. Created by Jeff Friesen for JavaWorld.

Lambdas:Getting started

A lambda expression(lambda) describes a block of code(an anonymous function can be passed to a construct or method for subsequent execution). The constructor or method receives lambda as a parameter. Consider the following example:

\ [In this comprehensive 12-part course, learn Java from introductory concepts to advanced design patterns! ]

()-> System.out.println("Hello")

This example identifies the lambda used to output the message to the standard output stream. From left to right,() identifies the lambda formal parameter list(there is no parameter in the example),-> indicates that the expression is lambda, and System.out.println("Hello") is the code to be executed.

Lambda simplifies the use of functional interfaces. They are annotated interfaces, and each interface accurately declares an abstract method(although they can also declare any combination of default, static, and private methods). For example, the standard class library provides an interface to java.lang.Runnable with a single abstract void run() method. The declaration of the functional interface is as follows:

@FunctionalInterfacepublic interface Runnable {public abstract void run();}

Class library annotations Runnable and @FunctionalInterface, this is an example
java.lang.FunctionalInterface annotation type. FunctionalInterface is used to annotate those interfaces used in lambda context.

Lambda has no clear interface type. Instead, the compiler uses the surrounding context to infer the functional interface to be instantiated when the lambda is specified-the lambda is bound to that interface. For example, suppose I specify the following code snippet, which passes the previous lambda as a parameter to the Thread(Runnable target) constructor of the java.lang.Thread class:

new Thread(()-> System.out.println("Hello"));

The compiler is sure to pass the lambda to it, Thread(Runnable r) because this is the only Runnable function that satisfies the construction of the lambda:it is a functional interface, and the empty parameter list of the lambda() matches the empty parameter list of run() and returns The type(void) is also consistent. Lambda is bound to Runnable.

Listing 1 provides the source code to a small application so that you can use this example.

Listing 1. LambdaDemo.java(version 1)

public class LambdaDemo {public static void main(String []args) {new Thread(()-> System.out.println("Hello")). start();}}

Compile Listing 1(javac LambdaDemo.java) and run the application(java LambdaDemo). You should observe the following output:

Hello

Lambda can greatly simplify the amount of source code you must write, and it can also make the source code easier to understand. For example, if you don't have a lambda, you might specify the more detailed code in Listing 2, which is based on an instance of the anonymous class implemented Runnable.

Listing 2. LambdaDemo.java(version 2)

public class LambdaDemo {public static void main(String []args) {Runnable r = new Runnable() {@Override public void run() {System.out.println("Hello");}}; new Thread(r) .start();}}

After compiling this source code, run the application. You will find the same output as the previously displayed output.

Lambdas and Streams API

In addition to simplifying the source code, lambda also plays an important role in Java's feature-oriented Streams API. They describe the functional units passed to various API methods.

Java Lambda

In order to use lambda effectively, you must understand the syntax of lambda expressions and the concept of target types. You also need to understand how lambda interacts with scope, local variables, thisand and super keywords, and exceptions. I will cover all these topics in the following sections.

How to achieve lambda

Lambda is implemented according to the invokedynamic instruction of the Java virtual machine and java.lang.invokeAPI. Watch the video "Lambda:Learn More" to learn about the Lambda architecture.

Lambda syntax

Each lambda follows the following syntax:

(formal-parameter-list)-> {expression-or-statements}

The formal-parameter-list is a comma-separated list of formal parameters, which must match the parameters of an abstract method of the functional interface at runtime. If you omit their types, the compiler will infer these types from the context where lambda is used. Consider the following example:

(double a, double b) //types explicitly specified(a, b) //types inferred by compiler

Lambdas and var

Starting with Java SE 11, you can replace the type name with var. For example, you can specify(var a, var b).

You must specify parentheses for multiple or no formal parameters. However, when specifying a single formal parameter, parentheses can be omitted(although it is not necessary).(This only applies to parameter names-when you also specify the type, you must use parentheses.) Consider the following other examples:

x //parentheses omitted due to single formal parameter(double x) //parentheses required because type is also present() //parentheses required when no formal parameters(x, y) //parentheses required because of multiple formal parameters

The formal-parameter-list is followed by-> tokens, followed by expression-or-statements-expressions or statement blocks(called the main body of the lambda). Unlike expression-based bodies, statement-based bodies must be placed between open({) and close(}) brace characters:

(double radius)-> Math.PI * radius * radiusradius-> {return Math.PI * radius * radius;} radius-> {System.out.println(radius); return Math.PI * radius * radius;}

The expression-based lambda body of the first example does not have to be placed between parentheses. The second example converts an expression-based body to a statement-based body, where return must be specified to return the value of the expression. The last example demonstrates multiple statements that cannot be expressed without parentheses.

Lambda entities and semicolons

Please note; there is no semicolon() in the previous example. In each case, the lambda body will not be terminated with a semicolon, because lambda is not a statement. However, in a statement-based lambda body, each statement must end with a semicolon.

Listing 3 provides a simple application that demonstrates the lambda syntax; note that this listing is based on the first two code examples.

Listing 3. LambdaDemo.java(version 3)

@FunctionalInterfaceinterface BinaryCalculator {double calculate(double value1, double value2);} @ FunctionalInterfaceinterface UnaryCalculator {double calculate(double value);} public class LambdaDemo {public static void main(String []args) {System.out.printf("18 + 36.5 =%f%n ", calculate((double v1, double v2)-> v1 + v2, 18, 36.5)); System.out.printf(" 89/2.9 =%f%n ", calculate((v1, v2)-> v1/v2, 89, 2.9)); System.out.printf("-89 =%f%n", calculate(v-> -v, 89)); System.out.printf("18 * 18 =%f%n", calculate((double v)-> v * v, 18));} static double calculate(BinaryCalculator calc, double v1, double v2) {return calc.calculate(v1, v2);} static double calculate(UnaryCalculator calc, double v) {return calc.calculate(v);}}

Listing 3 first introduces the BinaryCalculator and UnaryCalculator interfaces, and their calculate() methods perform calculations on two input parameters or a single input parameter, respectively. This list also introduces a LambdaDemo class whose main() method demonstrates these functional interfaces.

The functional interface is demonstrated in the static double calculate(BinaryCalculator calc, double v1, double v2) and static double calculate(UnaryCalculator calc, double v) methods. Lambda passes the code as data to these methods, which are received as BinaryCalculator or UnaryCalculator instances.

Compile Listing 3 and run the application. You should observe the following output:

18 + 36.5 = 54.50000089/2.9 = 30.689655-89 = -89.00000018 * 18 = 324.000000

Target type

Lambda is associated with an implicit target type, which identifies the type of object to which Lambda is bound. The target type must be a functional interface inferred from the context, which restricts the lambda to appear in the following context:

  • Variable declaration
  • Allocation
  • Return statement
  • Array initializer
  • Method or constructor parameters
  • Lambda
  • Ternary conditional expression
  • list of actors

Listing 4 provides an application that demonstrates the context of these target types.

Listing 4. LambdaDemo.java(version 4)

import java.io.File; import java.io.FileFilter; import java.nio.file.Files; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.FileVisitor; import java.nio.file.FileVisitResult; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio. file.attribute.BasicFileAttributes; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java .util.concurrent.Callable; public class LambdaDemo {public static void main(String []args) throws Exception {//Target type # 1:variable declaration Runnable r =()-> {System.out.println("running");}; r.run(); //Target type # 2:assignment r =()-> System.out.println("running"); r.run(); //Target type # 3:return statement(in getFilter()) File []files = new File("."). listFil es(getFilter("txt")); for(int i = 0; i <files.length; i ++) System.out.println(files [i]); //Target type # 4:array initializer FileSystem fs = FileSystems .getDefault(); final PathMatcher matchers []= {(path)-> path.toString(). endsWith("txt"),(path)-> path.toString(). endsWith("java")}; FileVisitor <Path> visitor; visitor = new SimpleFileVisitor <Path>() {@Override public FileVisitResult visitFile(Path file, BasicFileAttributes attribs) {Path name = file.getFileName(); for(int i = 0; i <matchers.length; i ++) {if(matchers [i].matches(name)) System.out.printf("Found matched file:'%s'.%n", file);} retu rn FileVisitResult.CONTINUE;}}; Files.walkFileTree(Paths.get("."), visitor); //Target type # 5:method or constructor arguments new Thread(()-> System.out.println("running ")). start(); //Target type # 6:lambda body(a nested lambda) Callable <Runnable> callable =()->()-> System.out.println(" called "); callable.call() .run(); //Target type # 7:ternary conditional expression boolean ascendingSort = false; Comparator <String> cmp; cmp =(ascendingSort)?(s1, s2)-> s1.compareTo(s2):(s1 , s2)-> s2.compareTo(s1); List <String> cities = Arrays.asList("Washington", "London", "Rome", "Berlin", "Jerusalem", "Ottawa", "Sydney", "Moscow"); Collections.sort(cities, cmp); for(int i = 0; i <cities.size(); i ++) System.out.println(cities.get(i)); //Target type # 8:cast expression String user = AccessController.doPrivileged((PrivilegedAction <String>)()-> System.getProperty("user.name")); System.out.println(user);} static FileFilter getFilter(String ext) {return(pathname)-> pathname.toString(). endsWith(ext);}}

If there are errors or you have a better way, please leave a message to point out ~

For more knowledge of Java dry goods, please follow me ~

Thank you ~