Core Java

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 ListArrow TokenBody
(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.

Download
You can download the full source code of this example here: Java 8 Lambda Expressions Tutorial

Ima Miri

Ima is a Senior Software Developer in enterprise application design and development. She is experienced in high traffic websites for e-commerce, media and financial services. She is interested in new technologies and innovation area along with technical writing. Her main focus is on web architecture, web technologies, java/j2ee, Open source and mobile development for android.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button