Java 8 Lambda Expressions Tutorial
Lambda expressions are an important new feature in Java 8 which provide a clear and concise way to represent a functional interface using an expression. They also improve some functionality in Collection libraries such as iterate, filter and extracting data from a Collection.
1. Lambda Expression Syntax
A lambda expression is included three parts.
Argument List | Arrow Token | Body |
(int x, int y) | -> | x + y |
- The argument can be zero, one or more statement in lambda expressions. The type of parameter can be declared or can be inferred from the context. This is called “target typing”.
- The body can be either a single expression or a statement block. In the expression form, the body is simply evaluated and returned. In the block form, the body is evaluated like a method body and a return statement returns control to the caller of the anonymous method.
Lets take a look at these examples:
() -> 33 (String str) -> { System.out.println(str); } (int x, int y) -> x + y
The first expression takes no arguments and uses the expression form to return an integer 33. The second expression takes a string and uses the block form to print the string to the console, and returns nothing. The third expression takes two integer arguments, named x
and y
and uses the expression form to return x+y
.
2. Why Lambda expressions?
In Java, anonymous inner classes provide a way to implement classes that may occur only once in an application. Lambda expressions are a useful replacement for anonymous inner classes to simplify the code.
In the following example, the Runnable
lambda expression, converts five lines of the code into one line.
public static void main(String[] args) { Runnable ar = new Runnable(){ @Override public void run(){ System.out.println("Anonymous Runnable!"); } }; Runnable lr = () -> System.out.println("Lambda Runnable!"); ar.run(); lr.run(); }
Lets have a look at the following example for using comparator interface and how we can replace part of the code with lambda expressions:
List<Node> nodes = new ArrayList<Node>(); // Sort with Inner Class Collections.sort(nodes, new Comparator<Node>() { public int compare(Node n1, Node n2) { return n1.getValue().compareTo(n2.getValue()); } }); System.out.println("=== Sorted Ascending ==="); for(Node node:nodes){ node.toString(); } // Replace with lambdaExpression System.out.println("=== Sorted Ascending ==="); Collections.sort(nodes, (Node n1, Node n2) -> n1.getValue().compareTo(n2.getValue())); for(Node node:nodes){ node.toString(); } System.out.println("=== Sorted Descending ==="); Collections.sort(nodes, (n1, n2) -> n2.getValue().compareTo(n1.getValue())); for(Node node:nodes){ node.toString(); }
The first lambda expression in line 17 declares the parameter type passed to the expression. However, as you can see from the second expression in line 24, this is optional. Lambda supports “target typing” which infers the object type from the context in which it is used. Because we are assigning the result to a Comparator
defined with a generic, the compiler can infer that the two parameters are of the Node
type.
2.1. How to define a functional interface
A Functional Interface includes only an abstract method. A lambda expression can be implicitly assigned to a functional interface as bellow.
@FunctionalInterface public interface Print { public abstract void print(); }
Functional Interfaces can have only one abstract method. If you try to add one more abstract method in it, it throws compile time error.
Error: java: Unexpected @FunctionalInterface annotation main.Print is not a functional interface multiple non-overriding abstract methods found in interface main.Print
Once the Functional interface is defined, we can simply use it in our development.
public static void main(String[] args) { // Functional Interface Print p = () -> System.out.println("Test"); p.print(); }
3. The java.util.function package
There are number of different functional interfaces in java.util.function
package. Predicate
is one of the functional interface in this package which includes a test
method. The method takes a generic class and returns a boolean result. Predicate
is useful for filtering mechanisms.
public interface Predicate<T> { boolean test(T t); }
The following example uses the Predicate interface to compare the distance between two cities with a constant value. As it is mentioned bellow, it will print the distance if it is greater than 30.
public static void main(String[] args) { List<Edge> cities = new ArrayList<>(); cities.add(new Edge(new Node("LA"), new Node("NY"), 50)); cities.add(new Edge(new Node("Sydney"), new Node("Melbourne"), 30)); cities.add(new Edge(new Node("Rome"), new Node("Venice"), 20)); Predicate<Edge> edgePredicate = e -> e.getDist() >= 30; filter(edgePredicate, cities); } public static void filter(Predicate<Edge> predicate, List<Edge> items) { for(Edge item: items) { if(predicate.test(item)) { System.out.println(item.getOrigin().getValue().toString() + " to " + item.getDest().getValue().toString() + " : " + item.getDist()); } } }
Output:
LA to NY : 50 Sydney to Melbourne : 30
4. The java.util.stream package
In java, java.util.Stream
represents a stream which one or more operations can be performed. A Stream
represents a sequence of elements on which various methods can be chained. By default, once elements are consumed they are no longer available from the stream. Therefore, a chain of operations can occur only once on a particular Stream
.
Now, lets have a look at some examples with stream.The first feature to look at is the new forEach
method available to any collection class. forEach
is a new addition to Iterable
interface in java 8.
List<String> myList = Arrays.asList("area", "block", "building", "city", "country"); myList.stream() .filter(s -> s.startsWith("c")) .map(String::toUpperCase) .sorted() .forEach(System.out::println);
Output:
CITY COUNTRY
Stream operations are either intermediate or terminal. Intermediate operations return a stream so we can chain multiple intermediate operations without using semicolons. Terminal operations are either void or return a non-stream result. In the above example filter
, map
and sorted
are intermediate operations whereas forEach
is a terminal operation. The above example shows a chain of stream operations which is known as “operation pipeline”.
5. Download the Source Code
This was a tutorial for Java 8 Lambda expressions.
You can download the full source code of this example here: Java 8 Lambda Expressions Tutorial