Trying Out Lambda Expressions in the Eclipse IDE

by Deepak Vohra
Published August 2013

Learn how to make the best of lambdas and virtual extension methods.

Lambda expressions, also called closures, are a short-form replacement for anonymous classes. Lambda expressions simplify the use of interfaces that declare a single abstract method, which are also called functional interfaces. In Java SE 7, a single method interface can be implemented with one of the following options.

  • Create a class that implements the interface.
  • Create an anonymous class.

A lambda expression can be used to implement a functional interface without creating a class or an anonymous class. Lambda expressions can be used only with interfaces that declare a single method.

Lambda expressions are designed to support a multicore processor architecture, which relies on software that provides parallelism, which in turn, improves performance and reduces completion time.

Benefits of lambda expressions include the following:

  • Concise syntax
  • Method references and constructor references
  • Reduced runtime overhead compared to anonymous classes

Prerequisites

To follow along with the examples in this article, download and install the following software:

Syntax of Lambda Expressions

The syntax of a lambda expression is as follows.

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


The parameter list is a comma-separated list of formal parameters that match the formal parameters of the single method in a functional interface. Specifying the parameter types is optional; if the parameter types are not specified, the types are inferred from the context.

The parameter list must be enclosed in within parentheses except when a single parameter is specified without the parameter type; a single formal parameter can be specified without parentheses. If a functional interface method does not specify any formal parameters, empty parentheses must be specified.

The parameter list is followed by the-> operator, which is followed by the lambda body, which is a single expression or a statement block. The lambda body has a result that must be one of the following:

  • void, if the functional interface method result isvoid
  • A Java type, primitive type, or reference type that is the same as the return type of the functional interface method

The lambda body result is returned according to one of the following options:

  • If a single expression is used as the lambda body, the expression value is returned.
  • If the method has a return type and the lambda body is not a single expression, the lambda body must return a value using areturn statement.
  • If the functional interface method result isvoid, areturn statement can be provided, but that is not required.

A statement block must be enclosed within curly braces ({}) unless the statement block is a method invocation statement for a method whose result isvoid. The lambda body result must be the same as the result of the single method in the functional interface. For example, if the functional interface method result isvoid, the lambda expression body must not return a value. If the functional interface method has a return type ofString, the lambda expression body must return aString. If the lambda body is a single statement and the method has a return type, the statement must be areturn statement. When a lambda expression is invoked, the code in the lambda body is run.

Functional Interfaces

A lambda expression is used with a functional interface, which is an interface with essentially one abstract method; the interface can contain a method that is also included in theObject class. Some examples of functional interfaces arejava.util.concurrent.Callable—which has a single method,call()—andjava.lang.Runnable—which has a single method,run().

As a comparison, an anonymous class for an interface involves specifying an instance creation expression for the interface and the compiler creating an instance of a class that implements the interface. Unlike an anonymous class, which specifies the interface type (or class type), a lambda expression does not specify the interface type. The functional interface for which a lambda expression is invoked, also called the target type of a lambda expression, is inferred from the context.

Target Type of a Lambda Expression

A lambda expression has an implicit target type associated with it because an interface type is not explicitly specified. In a lambda expression, the target type of a lambda conversion must be a functional interface. The target type is inferred from the context. Therefore, lambda expressions can be used only in contexts in which the target type can be inferred. Such contexts are

  • A variable declaration
  • An assignment
  • A return statement
  • An array initializer
  • Method or constructor arguments
  • A lambda expression body
  • A ternary conditional expression
  • A cast expression

Using the Eclipse IDE with Java SE 8 Support

To use Java 8 in the Eclipse IDE, you need to download an Eclipse version that supports JDK 8.

  1. In Eclipse, select Windows > Preferences and then select Java > Installed JREs. Install a JRE for JDK 8 using the JDK 8 you downloaded in the "Prerequisites" section.
  2. Select Java > Compiler and set Compiler compliance level to 1.8, as shown in Figure 1.

    Figure 1
    Figure 1

  3. Click Apply and then OK.
  4. Select the JDK 1.8 JRE when creating a Java project in Eclipse.

Next, we discuss how lambda expressions can be used by looking at some examples.

TheHello Application with Lambda Expressions

We are familiar with theHello application that outputs a message when supplied with a name. ClassHello declares two fields, two constructors, and ahello() method to output a message, as shown below.


public class Hello {
   String firstname;
   String lastname;
   public Hello() {}
   public Hello(String firstname, String lastname) {
      this.firstname = firstname;
      this.lastname = lastname;}
   public void hello() {
      System.out.println("Hello " + firstname + " " + lastname);}
   public static void main(String[] args) {
      Hello hello = new Hello(args[0], args[1]);
      hello.hello();
   }
}

Now, we will see how lambda expressions simplify the syntax in theHello example. First, we need to create a functional interface with a method that returns a "Hello" message.

interface HelloService {String hello(String firstname, String lastname);}

Create a lambda expression with two parameters that match the parameters of the interface method. In the lambda expression body, create and return a "Hello" message constructed from thefirstname andlastname using areturn statement. The return value must be the same type as the return type of the interface method, and the target of the lambda expression must be the functional interfaceHelloService. See Listing 1.


public class Hello {
   interface HelloService {
      String hello(String firstname, String lastname);
   }

   public static void main(String[] args) {
      
HelloService helloService=(String firstname, String lastname) -> 
{ String hello="Hello " + firstname + " " + lastname; return hello; };
System.out.println(helloService.hello(args[0], args[1]));
        

    }
}

Listing 1

Before we can run theHello application, we need to provide some program arguments for the method arguments ofhello(). Right-clickHello.java in Package Explorer and select Run As > Run Configurations. In Run Configurations, select the Arguments tab, specify arguments in the Program arguments field, and click Apply. Then click Close.

To run theHello.java application, right-clickHello.java in Package Explorer and select Run As > Java Application. The application output is displayed in the Eclipse console, as shown in Figure 2.

Figure 2
Figure 2

Local Variables in Lambda Expressions

A lambda expression does not define a new scope; the lambda expression scope is the same as the enclosing scope. For example, if a lambda expression body declares a local variable with the same name as a variable in the enclosing scope, a compiler error—Lambda expression's local variable i cannot re-declare another local variable defined in an enclosing scope—gets generated, as shown in Figure 3.

Figure 3
Figure 3

A local variable, whether declared in the lambda expression body or in the enclosing scope, must be initialized before being used. To demonstrate this, declare a local variable in the enclosing method:

int i;

Use the local variable in the lambda expression. A compiler error—The local variable i may not have been initialized—gets generated, as shown in Figure 4.

Figure 4
Figure 4

A variable used in a lambda expression is required to be final or effectively final. To demonstrate this, declare a local variable and initialize the local variable:

int i=5;


Assign the variable in the lambda expression body. A compiler error—Variable i is required to be final or effectively final—gets generated, as shown in Figure 5.

Figure 5
Figure 5

The variablei can be declared final as follows.

final int i=5;

Otherwise, the variable must be effectively final, which implies that the variable cannot be assigned in the lambda expression. Method parameter variables and exception parameter variables from an enclosing context must also be final or effectively final.

Thethis andsuper references in a lambda body are the same as in the enclosing context, because a lambda expression does not introduce a new scope, which is unlike the case with anonymous classes.

A Lambda Expression Is an Anonymous Method

A lambda expression is, in effect, an anonymous method implementation; formal parameters are specified and a value can be returned with areturn statement. The anonymous method must be compatible with the functional interface method it implements, as dictated by the following rules.

  • The lambda expression must return a result that is compatible with the result of the functional interface method. If the result isvoid, the lambda body is void-compatible. If a value is returned, the lambda body is value-compatible. The return value can be of a type that is a subtype of the return type in the functional interface method declaration.
  • The lambda expression signature must be the same as the functional interface method's signature. The lambda expression signature cannot be a subsignature of the functional interface method's signature.
  • The lambda expression can throw only those exceptions for which an exception type or an exception supertype is declared in the functional interface method'sthrows clause.

To demonstrate that a lambda expression must return a result if the functional interface method returns a result, comment out thereturn statement in the lambda expression that has the target typeHelloService. Because thehello() method in the functional interface,HelloService, has a return type ofString, a compiler error is generated, as shown in Figure 6.

Figure 6
Figure 6

If the functional interface method declares the result asvoid and the lambda expression returns a value, a compiler error is generated, as shown in Figure 7.

Figure 7
Figure 7

If the lambda expression signature is not exactly the same as the functional interface method signature, a compiler error is generated. To demonstrate this, make the lambda expression parameter list empty while the functional interface method declares two formal parameters. A compiler error—Lambda expression's signature does not match the signature of the functional interface method—is generated, as shown in Figure 8.

Figure 8
Figure 8

No distinction is made between a vararg parameter and its array equivalent. For example, a functional interface method declares an array-type parameter as follows:


interface Int {
      void setInt(int[] i);

   }  

The parameter list of the lambda expression can declare a vararg parameter:

Int int1 =(int... i)->{};

Exception Handling

A lambda expression body must not throw more exceptions than specified in thethrows clause of the functional interface method. If a lambda expression body throws an exception, thethrows clause of the functional interface method must declare the same exception type or its supertype.

To demonstrate this, do not declare athrows clause in thehello method of theHelloService interface and throw an exception from the lambda expression body. A compiler error message—Unhandled exception type Exception—is generated, as shown in Figure 9.

Figure 9
Figure 9

If the same exception type as the thrown exception is added to the functional interface method, the compiler error is resolved, as shown in Figure 10. But, a compiler error message is generated if thehello method is invoked using the reference variable to which the lambda expression result is assigned, because the exception is not handled in themain method, which is also shown in Figure 10.

Figure 10
Figure 10

A Lambda Expression Is a Poly Expression

The type of a lambda expression is a deduced type, and the type is deduced from the target type. The same lambda expression could have different types in different contexts. Such an expression is called a poly expression. To demonstrate this, define another functional interface that has the same abstract method signature asHelloService, for example:


interface HelloService2 {
        String hello(String firstname, String lastname);

    }

The same lambda expression, for example, the following expression, can be used with the two functional interfaces that declare a method that has the same signature, return type, andthrows clause:


(String firstname, String lastname) -> {
         String hello = "Hello " + firstname + " " + lastname;
         return hello;
      }

Without a context, the preceding lambda expression does not have a type, because it does not have a target type. But, if it is used in the context of a target type, the lambda expression could have different types based on the target type. In the following two contexts, the preceding lambda expression has different types because the target types are different:HelloService andHelloService2.


HelloService helloService =(String firstname, String lastname) -> {
         String hello = "Hello " + firstname + " " + lastname;
         return hello;
      };

HelloService2 helloService2 =(String firstname, String lastname) -> {
         String hello = "Hello " + firstname + " " + lastname;
         return hello;
      };

Generic lambdas are not supported. A lambda expression cannot introduce type variables.

Lambda Expressions in a GUI Application

GUI components in thejava.awt package make use of thejava.awt.event.ActionListener interface to register action events from a component. Thejava.awt.event.ActionListener interface is a functional interface with only one method:actionPerformed(ActionEvent e).

Ajava.awt.event.ActionListener is registered with a component using theaddActionListener(ActionListener l) method. For example, ajava.awt.event.ActionListener could be registered with ajava.awt.Button component using an anonymous inner class in the application to count the number of times aButton object calledb has been clicked, as follows. (See "How to Write an Action Listener" for more details.)


b.addActionListener (new ActionListener() {
          int numClicks = 0;
          public void actionPerformed(ActionEvent e) {
             numClicks++;
                text.setText("Button Clicked " + numClicks + " times");
          }
       });

A lambda expression can be used instead of the anonymous inner class to make the syntax more concise. The following is an example of using a lambda expression to register anActionListener with aButton component:


b.addActionListener(e -> {
         numClicks++;
         text.setText("Button Clicked " + numClicks + " times");
      });

   }

The parentheses for specifying the parameter can be omitted for a single-parameter lambda expression. The target type of the lambda expression—the functional interfaceActionListener—is inferred from the context, which is a method invocation.

Using Lambda Expressions with Some Common Functional Interfaces

In this section, we will discuss how some common functional interfaces can be used with lambda expressions.

TheFileFilter Interface

The FileFilter interface has a single method,accept(), and is used to filter files. In The Java Tutorials ImageFilter example, the ImageFilter class implements the FileFilter interface and provides implementation for the accept() method. The accept() method is used to accept just the image files (and directories) using a Utils class.

We could use a lambda expression that returns a boolean to provide implementation for the FileFilter interface, as shown in Listing 2.


import java.io.FileFilter;
import java.io.File;

public class ImageFilter {

   public static void main(String[] args) {
      FileFilter fileFilter = (f) -> {
         String extension = null;
         String s = f.getName();
         int i = s.lastIndexOf('.');

         if (i > 0 && i << s.length() - 1) {
            extension = s.substring(i + 1).toLowerCase();
         }
         if (extension != null) {
            if (extension.equals("tiff") || extension.equals("tif")
               || extension.equals("gif") || extension.equals("jpeg")
               || extension.equals("jpg") || extension.equals("png")
               || extension.equals("bmp")) {
            return true;
         } else {
            return false;
         }
         }
         return false;
      };

      File file = new File("C:/JDK8/Figure10.bmp");
      System.out.println("File is an image file: " + fileFilter.accept(file));

   }
}

Listing 2

The output from theImageFilter class is shown in the Eclipse console in Figure 11.

Figure 11
Figure 11

TheRunnable Interface

In The Java Tutorials "Defining and Starting a Thread" section, theHelloRunnable class implements theRunnable interface, and aThread is created using an instance of theHelloRunnable class. ARunnable for theThread can be created using a lambda expression, as shown in Listing 3. The lambda expression does not have areturn statement, because the methodrun() inRunnable has result as void.


import java.lang.Runnable;

public class HelloRunnable {

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

Listing 3

The output from theHelloRunnable class is shown in Figure 12.

Figure 12
Figure 12

TheCallable Interface

If we created a class that implements thejava.util.concurrent.Callable<V> generic functional interface, the class would need to implement thecall() method. In Listing 4, classHelloCallable implements the parameterized typeCallable<String>.


package lambda;

import java.util.concurrent.Callable;

public class HelloCallable implements Callable<String> {
   @Override
   public String call() throws Exception {

      return "Hello from Callable";
   }

   public static void main(String[] args) {
      try {
         HelloCallable helloCallable = new HelloCallable();
         System.out.println(helloCallable.call());
      } catch (Exception e) {
         System.err.println(e.getMessage());
      }
   }
}

Listing 4

We can use a lambda expression to provide implementation for thecall() generic method. Because thecall() method does not take any parameters, the parentheses in the lambda expression are empty and, because the method returns aString for the parameterized typeCallable<String>, the lambda expression must return aString.


import java.util.concurrent.Callable;

public class HelloCallable {

   public static void main(String[] args) {
      try {

         Callable<String> c = () -> "Hello from Callable";
         System.out.println(c.call());
      } catch (Exception e) {
         System.err.println(e.getMessage());
      }
   }
}

Listing 5

The output fromHelloCallable is shown in Figure 13.

Figure 13
Figure 13

ThePathMatcher Interface

Thejava.nio.file.PathMatcher interface is used to match paths. The functional interface has a single method,matches(Path path), that is used to match aPath. We can use a lambda expression to provide the implementation for thematches() method, as shown in Listing 6. The lambda expression returns aboolean and the target type is the functional interfacePathMatcher.


import java.nio.file.PathMatcher;
import java.nio.file.Path;
import java.nio.file.FileSystems;

public class FileMatcher {

   public static void main(String[] args) {

      PathMatcher matcher = (f) -> {
         boolean fileMatch = false;
         String path = f.toString();
         if (path.endsWith("HelloCallable.java"))
            fileMatch = true;
         return fileMatch;
      };
      Path filename = FileSystems.getDefault().getPath(
            "C:/JDK8/HelloCallable.java");
      System.out.println("Path matches: " + matcher.matches(filename));

   }
}

Listing 6

The output from the FileMatcher class is shown in Figure 14.

Figure 14
Figure 14

TheComparator Interface

The functional interfaceComparator has a single method:compares(). The interface also has theequals() method, but theequals() method is also in theObject class. A functional interface can haveObject class methods in addition to one other method. If we were to compare instances of anEmployee entity usingComparator, we would first define theEmployee POJO, which has properties and getter/setters forempId,firstName, andlastName, as shown in Listing 7.


import java.util.*;

public class Employee {

   private int empId;
   private String lastName;
   private String firstName;
    

   public Employee() {
   }

   public Employee(int empId, String lastName, String firstName) {
      this.empId = empId;
      this.firstName = firstName;
      this.lastName = lastName;

   }

      // setters and getters
   public int getEmpId() {
      return empId;
   }

   public void setEmpId(int empId) {
      this.empId = empId;
   }

   public String getLastName() {
      return lastName;
   }

   public void setLastName(String lastName) {
      this.lastName = lastName;
   }

   public String getFirstName() {
      return firstName;
   }

   public void setFirstName(String firstName) {
      this.firstName = firstName;
   }

   public  int compareByLastName(Employee x, Employee y) 
   { 
      return x.getLastName().compareTo(y.getLastName()); 
   }

   /**
    * 
    * public static int compareByLastName(Employee x, Employee y) 
   { 
      return x.getLastName().compareTo(y.getLastName()); 
   }
    */
}

Listing 7

As shown in Listing 8, create a class calledEmployeeSort to sort aList ofEmployee entities based onlastName. In theEmployeeSort class, create aList object and addEmployee objects to it. Use theCollections.sort method to sort theList and create aComparator object for thesort() method using an anonymous inner class.


package lambda;

import java.util.*;

public class EmployeeSort {

   public static void main(String[] args) {

      Employee e1 = new Employee(1, "Smith", "John");
      Employee e2 = new Employee(2, "Bloggs", "Joe");
      List<Employee> list = new ArrayList<Employee>();
      list.add(e1);
      list.add(e2);

      Collections.sort(list, new Comparator<Employee>() {
         public int compare(Employee x, Employee y) {
            return x.getLastName().compareTo(y.getLastName());
         }
      });
      ListIterator<Employee> litr = list.listIterator();

      while (litr.hasNext()) {
         Employee element = litr.next();
         System.out.print(element.getLastName() + " ");
      }

   }
}

Listing 8

The anonymous inner class can be replaced with a lambda expression that takes twoEmployee-type parameters and returns anint value based on thelastName comparison, as shown in Listing 9.


import java.util.*;

public class EmployeeSort {

   public static void main(String[] args) {

      Employee e1 = new Employee(1, "Smith", "John");
      Employee e2 = new Employee(2, "Bloggs", "Joe");
      List<Employee> list = new ArrayList<Employee>();
      list.add(e1);
      list.add(e2);

      Collections.sort(list,
            (x, y) -> x.getLastName().compareTo(y.getLastName()));

      ListIterator<Employee> litr = list.listIterator();

       while (litr.hasNext()) {
         Employee element = litr.next();
         System.out.print(element.getLastName() + " ");
      }

   }
}

Listing 9

The output fromEmployeeSort is shown in Figure 15.

Figure 15
Figure 15

How Are the Target Type and Lambda Parameter Type Inferred?

For lambda expressions, the target type is inferred from the context. A lambda expression can be used only in contexts in which the target type can be inferred. Such contexts are a variable declaration, an assignment statement, areturn statement, an array initializer, method or constructor arguments, a lambda expression body, a conditional expression, and a cast expression.

The lambda formal parameters types are also inferred from the context. In all the preceding examples except theHello example in Listing 1, the parameter types are inferred from the context. In subsequent sections, we discuss some of the contexts in which the lambda expressions could be used.

Lambda Expressions inreturn Statements

A lambda expression can be used in areturn statement. The return type of the method in which a lambda expression is used in areturn statement must be a functional interface. For example, include a lambda expression in areturn statement for a method that returns aRunnable, as shown in Listing 10.


import java.lang.Runnable;

public class HelloRunnable2 {

   public static Runnable getRunnable() {
      return () -> {
         System.out.println("Hello from a thread");
      };
   }

   public static void main(String args[]) {

      new Thread(getRunnable()).start();
   }

}

Listing 10

The lambda expression does not declare any parameters, because therun() method of theRunnable interface does not declare any formal parameters. The lambda expression does not return a value, because therun() method has the result asvoid. The output from Listing 10 is shown in Figure 16.

Figure 16
Figure 16

Lambda Expressions as Target Types

Lambda expressions themselves can be used as target types for inner lambda expressions. Thecall() method inCallable returns anObject, but therun() method inRunnable doesn't have a return type. The target type of the inner lambda expression in theHelloCallable2 class is aRunnable, and the target type of the outer lambda expression isCallable. The target type is inferred from the context, which is an assignment statement to a reference variable of typeCallable<Runnable>.

In Listing 11, the inner lambda expression,() -> {System.out.println("Hello from Callable");}, is inferred to be of typeRunnable because the parameter list is empty and the result isvoid; the anonymous method signature and result are the same as therun() method in theRunnable interface. The outer lambda expression,() -> Runnable, is inferred to be of typeCallable<Runnable> because thecall() method inCallable<V> does not declare any formal parameters and the result type is type parameterV.


import java.util.concurrent.Callable;

public class HelloCallable2 {

   public static void main(String[] args) {
      try {

         Callable<Runnable> c = () -> () -> {
            System.out.println("Hello from Callable");
         };
          c.call().run();

      } catch (Exception e) {
         System.err.println(e.getMessage());
      }
   }
}

Listing 11

The output fromHelloCallable2 is shown in Figure 17.

Figure 17
Figure 17

Lambda Expressions in Array Initializers

Lambda expressions can be used in array initializers, but generic array initializers cannot be used. For example, lambda expressions in the following generic array initializer would generate a compiler error:

Callable<String>[] c=new Callable<String>[]{ ()->"a", ()->"b", ()->"c" };

A compiler error—Cannot create a generic array of Callable<String>—gets generated, as shown in Figure 18.

Figure 18
Figure 18

To use lambda expressions in an array initializer, specify a non-generic array initializer, as in theCallableArray class shown in Listing 12.


import java.util.concurrent.Callable;

public class CallableArray  {

public static void main(String[] args) {
try{


Callable<String>[] c=new Callable[]{ ()->"Hello from Callable a", 
()->"Hello from Callable b", ()->"Hello from Callable c" };

System.out.println(c[1].call());
}catch(Exception e){System.err.println(e.getMessage());}
}
}

Listing 12

Each of the array initializer variables inCallableArray is a lambda expression of typeCallable<String>. The parameter list of the lambda expression is empty and the result of the lambda expression is a single expression that evaluates to aString. The target type of each of the lambda expressions is inferred to be of typeCallable<String> from the context. The output fromCallableArray is shown in Figure 19.

Figure 19
Figure 19

Casting Lambda Expressions

Sometimes the target type of a lambda expression can be ambiguous. For example, in the following assignment statement, a lambda expression is used as a method argument to theAccessController.doPrivileged method. The target type of the lambda expression is ambiguous because more than one functional interface—PrivilegedAction andPrivilegedExceptionAction—could be the target type of the lambda expression.

String user = AccessController.doPrivileged(() -> System.getProperty("user.name"));

A compiler error—The method doPrivileged(PrivilegedAction<String>) is ambiguous for the type AccessController—gets generated, as shown in Figure 20.

Figure 20
Figure 20

We can use a cast to the lambda expression to specify the target type asPrivilegedAction<String>, as in theUserPermissions class shown in Listing 13.


import java.security.AccessController;
import java.security.PrivilegedAction;

public class UserPermissions {

   public static void main(String[] args) {

      String user = AccessController
            .doPrivileged((PrivilegedAction<String>) () -> System
               .getProperty("user.name"));
      System.out.println(user);

   }
}

Listing 13

The output fromUserPermissions with a cast in the lambda expression is shown in Figure 21.

Figure 21
Figure 21

Lambda Expressions in Conditional Expressions

Lambda expressions can be used in ternary conditional expressions, which evaluate one of the two operands based on whether aboolean condition istrue orfalse.

In theHelloCallableConditional class shown in Listing 14, the lambda expressions—() -> "Hello from Callable: flag true") and() -> "Hello from Callable: flag false")—constitute the two alternative expressions to evaluate. The target type of the lambda expressions is inferred from the context, which is an assignment statement to aCallable<String> reference variable. Subsequently, the reference variable is used to invoke thecall() method.


import java.util.concurrent.Callable

public class HelloCallableConditional {

   public static void main(String[] args) {
      try {

         boolean flag = true;
         Callable<String> c = flag ? (() -> "Hello from Callable: flag true")
               : (() -> "Hello from Callable: flag false");

         System.out.println(c.call());
      } catch (Exception e) {
         System.err.println(e.getMessage());
      }
   }
}

Listing 14

The output fromHelloCallableConditional is shown in Figure 22.

Figure 22
Figure 22

Inferring the Target Type in Overloaded Methods

When you invoke a method that is overloaded, the method that best matches the lambda expression is used. The target type and the method argument type are used to resolve the best method.

In theHelloRunnableOrCallable class in Listing 15, twohello() methods (thehello() method is overloaded) with return typeString are specified: one with parameter typeCallable and the other with parameter typeRunnable.

Thehello() method is invoked with a lambda expression as a method argument. Because the lambda expression—() -> "Hello Lambda"—returns aString, thehello(Callable) method is invoked, andHello from Callable is output because thecall() method ofCallable has a return type, whereas therun() method ofRunnable doesn't.


import java.util.concurrent.Callable;

public class HelloRunnableOrCallable {

   static String hello(Runnable r) {
      return "Hello from Runnable";
   }

   static String hello(Callable c) {
      return "Hello from Callable";
   }

   public static void main(String[] args) {

      String hello = hello(() -> "Hello Lambda");
      System.out.println(hello);

   }
}

Listing 15

The output fromHelloCallableConditional is shown in Figure 23.

Figure 23
Figure 23

this in Lambda Expressions

Outside a lambda expression,this refers to the current object. In a lambda expression also,this refers to the enclosing current object. Lambda expressions that do not refer to members of the enclosing instance do not store a strong reference to it, which solves a memory leak problem that often occurs in inner class instances that hold a strong reference to the enclosing class.

In the example in Listing 16,Runnable is the target type of a lambda expression. In the lambda expression body, a reference tothis is specified. When an instance ofRunnable r is created and therun() method is invoked, thethis reference invokes the enclosing instance and aString value of the enclosing instance is obtained from thetoString() method. The messageHello from Class HelloLambda gets output.


public class HelloLambda {
     Runnable r = () -> { System.out.println(this); };
  

     public String toString() { return "Hello from Class HelloLambda"; }

     public static void main(String args[]) {
       new HelloLambda().r.run();
       
     }
   }

Listing 16

The output fromHelloLambda is shown in Figure 24.

Figure 24
Figure 24

Lambda Expression Parameter Names

New names are created for the formal parameters of a lambda expression. If the same names as the local variable names in the enclosing context are used as lambda expression parameter names, a compiler error gets generated. In the following example, lambda expression parameter names are specified ase1 ande2, which are also used for local variablesEmployee e1 andEmployee e2.


Employee e1 = new Employee(1,"A", "C");   
  Employee e2 = new Employee(2,"B","D" );   
  List<Employee> list = new ArrayList<Employee>();   
  list.add(e1);list.add(e2);

    Collections.sort(list, (Employee e1, Employee e2) -> e1.getLastName().compareTo(e2.getLastName()));

A compiler error is generated for lambda expression parametere1, as shown in Figure 25.

Figure 25
Figure 25

A compiler error is also generated for lambda expression parametere2, as shown in Figure 26.

Figure 26
Figure 26

References to Local Variables

In providing an alternative to anonymous inner classes, the requirement for local variables to be final in order to be accessed in a lambda expression was removed. The requirement for a local variable to be final to be accessed from an inner class is also removed in JDK 8. In JDK 7, a local variable accessed from an inner class must be declared final.

The requirement for a local variable to be used in an inner class or a lambda expression has been modified from "final" to "final or effectively final."

Method References

A lambda expression defines an anonymous method with a functional interface as the target type. Instead of defining an anonymous method, existing methods with a name can be invoked using method references. In theEmployeeSort example in Listing 9, the following method invocation has a lambda expression as a method argument.

Collections.sort(list, (x, y) -> x.getLastName().compareTo(y.getLastName()));

The lambda expression can be replaced with a method references as follows:

Collections.sort(list, Employee::compareByLastName);

The delimiter (::) can be used for a method reference. The methodcompareByLastName is a static method in theEmployee class.


public static int compareByLastName(Employee x, Employee y) 
{ return x.getLastName().compareTo(y.getLastName()); 

For non-static methods, method references can be used with instances of a particular object. By making thecompareByLastName method non-static, a method reference with anEmployee instance can be used as follows:


Employee employee=new Employee();
Collections.sort(list, employee::compareByLastName); 

The method reference doesn't even have to be an instance method of the object in which it is used. The method reference can be an instance method of any arbitrary class object. For example, aString List can be sorted using thecompareTo method from theString class with a method reference:


String e1 = new String("A");   
  String e2 = new String("B");    
  List<String> list = new ArrayList<String>();   
  list.add(e1);list.add(e2);
Collections.sort(list, String::compareTo);

Method references are a further simplification of lambda expressions.

Constructor References

Method references are used for method invocations, and constructor references are used with constructor invocations. Method references and constructor references are lambda conversions, and the target type of method references and constructor references must be a functional interface.

In this section, we discuss constructor references using aMultimap as an example. Multimap is a Google Collections utility. AMultimap for image types is created as follows:


Multimap<ImageTypeEnum, String> imageTypeMultiMap = Multimaps
        .newListMultimap(
              Maps.<ImageTypeEnum, Collection<String>> newHashMap(),
              new Supplier<List<String>>() { public List<String> get() { 
        return new ArrayList<String>(); 
            } 
        });

In theMultimap example, aSupplier<List<String>> is created using a constructor as follows:


new Supplier<List<String>>() { 
            public List<String> get() { 
                return new ArrayList<String>(); 
            } 
        }

The constructor returns anArrayList<String>. Using a constructor reference, theMultimap can be created with a simplified syntax as follows:


Multimap<ImageTypeEnum, String> imageTypeMultiMap = 
Multimaps.newListMultimap(Maps.<ImageTypeEnum, Collection<String>> newHashMap(),ArrayList<String>::new); 

TheMultimap exampleImageTypeMultiMap class with constructor reference is shown in Listing 17.


import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.google.common.base.Supplier;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;

public class ImageTypeMultiMap {
   enum ImageTypeEnum {
      tiff, tif, gif, jpeg, jpg, png, bmp
   }

   public static void main(String[] args) {
      Multimap<ImageTypeEnum, String> imageTypeMultiMap = Multimaps
            .newListMultimap(
                  Maps.<ImageTypeEnum, Collection<String>> newHashMap(),
               ArrayList<String>::new);

      imageTypeMultiMap.put(ImageTypeEnum.tiff, "tiff");
      imageTypeMultiMap.put(ImageTypeEnum.tif, "tif");
      imageTypeMultiMap.put(ImageTypeEnum.gif, "gif");
      imageTypeMultiMap.put(ImageTypeEnum.jpeg, "jpeg");
      imageTypeMultiMap.put(ImageTypeEnum.jpg, "jpg");
      imageTypeMultiMap.put(ImageTypeEnum.png, "png");
      imageTypeMultiMap.put(ImageTypeEnum.bmp, "bmp");

      System.out.println("Result: " + imageTypeMultiMap);
   }
}

Listing 17

To test theImageTypeMultiMap class, we need to download the Guava libraryguava-14.0.1.jar from https://code.google.com/p/guava-libraries/ and add theguava-14.0.1.jar to the Java build path. The output from ImageTypeMultiMap is shown in Figure 27.

Figure 27
Figure 27

Virtual Extension Methods

Encapsulation and reusability of an interface are the main benefits of an interface. But an interface has a disadvantage in that that all the interface methods must be implemented in a class that implements an interface. Sometimes only a few of the methods are required from an interface, but method implementations for all the interface methods must be provided when the interface is implemented. Virtual extension methods provide a fix for the issue.

Virtual extension methods are the methods in an interface that have a default implementation. If an implementing class does not provide an implementation for the method, the default implementation is used. An implementing class can override the default implementation or provide a new default implementation.

Virtual extension methods add the provision to expand the functionality of interfaces without breaking backward compatibility of classes that are already implementing an earlier version of the interface. The default implementation in a virtual extension method is provided using thedefault keyword. A virtual extension method provides a default implementation and, therefore, cannot be abstract.

Thejava.util.Map<K,V> class in JDK 8 provides several methods that have default implementations:

  • default V getOrDefault(Object key,V defaultValue)
  • default void forEach(BiConsumer<? super K,? super V> action)
  • default void replaceAll(BiFunction<? super K,? super V,? extends V> function)
  • default V putIfAbsent(K key,V value)
  • default boolean remove(Object key,Object value)
  • default boolean replace(K key,V oldValue,V newValue)
  • default V replace(K key,V value)
  • default V computeIfAbsent(K key,Function<? super K,? extends V> mappingFunction)
  • default V computeIfPresent(K key,BiFunction<? super K,? super V,? extends V> remappingFunction)
  • default V compute(K key,BiFunction<? super K,? super V,? extends V> remappingFunction)
  • default V merge(K key,V value,BiFunction<? super V,? super V,? extends V> remappingFunction)

To demonstrate that methods with default implementation do not need to be implemented when an interface is implemented by a class, create a class calledMapImpl that implements theMap<K,V> interface:

public class MapImpl<K,V> implements Map<K, V> { }

The completeMapImpl class with implementation for the methods that do not provide a default implementation is shown in Listing 18.


import java.util.Collection;
import java.util.Map;
import java.util.Set;

public class MapImpl<K,V> implements Map<K, V> {

   public static void main(String[] args) {

   }

   @Override
   public int size() {
 
      return 0;
   }

   @Override
   public boolean isEmpty() {
 
      return false;
   }

   @Override
   public boolean containsKey(Object key) {

      return false;
   }

   @Override
   public boolean containsValue(Object value) {
      
      return false;
   }

   @Override
   public V get(Object key) {

      return null;
   }

   @Override
   public V put(K key, V value) {

      return null;
   }

   @Override
   public V remove(Object key) {

      return null;
   }

   @Override
   public void putAll(Map<? extends K, ? extends V> m) {

   }

   @Override
   public void clear() {    

   }

   @Override
   public Set<K> keySet() {

      return null;
   }

   @Override
   public Collection<V> values() {

      return null;
   }

   @Override
   public Set<java.util.Map.Entry<K, V>> entrySet() {

      return null;
   }

}

Listing 18

The Map<K,V> interface is a predefined interface, but a new interface with virtual extension methods can also be defined. Create an interface called EmployeeDefault with default implementations for all the methods using the default keyword, as shown in Listing 19.


public interface EmployeeDefault {

   String name = "John Smith";
   String title = "PHP Developer";
   String dept = "PHP";

   default  void setName(String name) {
      System.out.println(name);
   }

   default String getName() {
      return name;
   }

   default void setTitle(String title) {
      System.out.println(title);
   }

   default String getTitle() {
      return title;
   }

   default void setDept(String dept) {
      System.out.println(dept);
   }

   default String getDept() {
      return dept;
   }
}

Listing 19

If an interface method is declared with thedefault keyword, the method must provide an implementation, as indicated by the compiler error shown in Figure 28.

Figure 28
Figure 28

The fields of an interface are final by default and cannot be assigned in the default implementation of a default method, as indicated by the compiler error shown in Figure 29.

Figure 29
Figure 29

A class that implements the interfaceEmployeeDefault is not required to provide an implementation for the virtual extension methods. TheEmployeeDefaultImpl class implements theEmployeeDefault interface and does not provide an implementation for any of the virtual extension methods inherited fromEmployeeDefault. TheEmployeeDefaultImpl class invokes the virtual extension methods using method invocation expressions, as shown in Listing 20.


public class EmployeeDefaultImpl implements EmployeeDefault {

   public static void main(String[] args) {
 
      EmployeeDefaultImpl employeeDefaultImpl=new EmployeeDefaultImpl();
      System.out.println(employeeDefaultImpl.getName());
      System.out.println(employeeDefaultImpl.getTitle());
      System.out.println(employeeDefaultImpl.getDept());

   }

}

Listing 20

Conclusion

This article introduced a new feature in JDK 8 called lambda expressions, which provide a concise syntax and a short-form replacement for anonymous classes. It also described virtual extension methods, which are beneficial because they provide interfaces with a default method implementation, which is used if an implementing class does not provide an implementation for a method.

See Also

Lambda Expressions

About the Author

Deepak Vohra is a NuBean consultant, web developer, Sun Certified Java 1.4 Programmer, and Sun Certified Web Component Developer for Java EE.

Join the Conversation

Join the Java community conversation on Facebook, Twitter, and the Oracle Java Blog!