Getting Started with Lambda Expressions

Version 6

    by Mohan Basavarajappa

     

    See how lambda expressions can reduce the number of lines of code you write and make code maintenance easier.

     

    To answer the question "what's a lambda expression?" you can think of a lambda as an expression that defines the behavior of a system. The syntactical touch of lambda expressions provides the Java programming language with an efficient tool for writing concise and flexible code. In addition to enabling you to write less code, lambda expressions are functional programming idioms that have modularity and provide extensibility aspects to software systems.

     

    Using lambda expressions is a better alternative than writing anonymous classes that have hefty syntax to define small, specific system behavior. Lambda expressions provide a lean approach for writing event handlers, which describe what action to perform on specific events. One classic example of when event handlers are needed in the Swing framework, where we have to provide an implementation for event handler interfaces. We can take advantage of lambda expressions only when a framework is designed for functional programming aspects.

     

    To understand lambda expressions, you need to understand two subtle nuances:

     

    • Functional interfaces, which are a Java interface with only one method declaration. The inherent nature of such an interface enables it to have one method performing only one task. Any reference to this interface is handled by the only method declared.
    • Expressions, which are the way we define an interface implementation. Functional interfaces have an abstract method and an expression defines what the method does.

     

    Now we will see how to do various aspects of Java functional programming.

     

    The Syntax of Functional Interfaces

     

    Listing 1 through Listing 3 show the syntax that can be used for functional interfaces.

     

    public interface Event {

      public void onEventOccurance();

    }

    Listing 1. Interface definition 1

     

    public interface EventProcess {

      public void onEventOccuranceProcess(int i);

     

    Listing 2. Interface definition 2

     

    public interface EventResult {

      public int onEventOccuranceConfirm(int i);

    }

    Listing 3. Interface definition 3

     

    The Syntax of Lambda Expressions

     

    The syntax of a lambda expression comprises three parts:

     

    • A comma-separated list of parameters enclosed within parentheses. You can omit the parentheses if there is only one parameter to the method.
    • The arrow token (->) is the separator between the parameters and the body of the lambda expression.
    • The body of the lambda expression contains a single statement or a block of statements. If it's a single statement, you do not need to use curly braces; a block of statements has to be wrapped within curly braces.

     

    Lambda expressions are lexically scoped and, unlike anonymous classes, they are not prone to shadowing issues. (You can learn more about shadowing issues The Java Tutorials.) Lambda expressions are safe from shadowing issues because variables accessed by a lambda expression should be final or effectively final. If variables don't meet these criteria, you will get a compile time error.

     

    The syntax of lambda expressions is shown in Listing 4 through Listing 7.

     

    The lambda syntax shown in Listing 4 corresponds to the functional interface definition in Listing 1. The method onEventOccurance() in Listing 1 does not accept any parameters; hence, the parentheses are empty.

     

        () -> System.out.println("event occurred : no argument syntax");

     

    Listing 4. Lambda expression 1

     

    This lambda syntax shown in Listing 5 corresponds to the functional interface definition in Listing 2. The method onEventOccuranceProcess(int i) in Listing 2 accepts one parameter; hence, the syntax mentions the parameter name. The parameter name in the interface and in the lambda expression can be anything—for example, i, a, p, and so on—and the name can be different in the interface than in the expression, and vice versa.

     

        

    -> System.out.println("event processed : one argument syntax"+ i);

     

    Listing 5. Lambda expression 2

     

    This lambda syntax shown in Listing 6, which also corresponds to the functional interface definition in Listing 2, shows an alternative syntax.

     

        

    -> { System.out.println("event processed : one argument syntax"+ i);  }

     

    Listing 6. Lambda expression 3, which is an alternative to lambda expression 2

     

    This lambda syntax shown in Listing 7 corresponds to the functional interface definition in Listing 3. The method onEventOccuranceConfirm(int i) in Listing 3 accepts one parameter; hence, the syntax mentions the parameter name. This can be anything, for example, i, a, p, and so on. And the implementation in Listing 7 returns a return value. Failing to include a return statement would result in a compilation error, because the lambda expression would not conform to the interface contract.

     

        

    -> { i = i + 1; return i; }

     

    Listing 7. Lambda expression 4

     

    Invoking a Functional Interface

     

    Now we will look at a Java class that will invoke functional interface methods making use of lambda expressions. The EventOrganizer class in Listing 8 has methods that accept functional interfaces as parameters and invoke their methods.

     

    package com.j2se8.samples.event;

     

    import com.j2se8.samples.event.funct.Event;

    import com.j2se8.samples.event.funct.EventProcess;

    import com.j2se8.samples.event.funct.EventResult;

     

    public class EventOrganizer {

     

      public void testEvent(Event event) {

        event.onEventOccurance();

      }

     

      public void testEventProcess(EventProcess eventProcess) {

        eventProcess.onEventOccuranceProcess(1);

      }

     

      public void testEventResult(EventResult eventResult) {

        int result = eventResult.onEventOccuranceConfirm(1);

        // printing the return value of

        System.out.println("Print result value : " + result);

      }

    }

     

    Listing 8. Class that invokes functional interface methods

     

    Listing 9 shows the Java client that calls the EventOrganizer object and passes the lambda expression for processing the input. In this example, you can see that the business logic is passed to the place where the data resides.

     

    package com.j2se8.samples.event;

     

    public class EventClient {

      public static void main(String[] args) {

        EventOrganizer eventTester = new EventOrganizer();

        // calling no- argument functional interface

        eventTester.testEvent(() -> System.out

            .println("event occurred : no argument syntax"));

     

        // calling single- argument method of functional interface

        eventTester.testEventProcess((i) -> System.out

            .println("event processed : one argument syntax : " + i));

     

        // calling alternative single- argument method of functional interface. alternative

        // syntax of omitting parenthesies

     

        eventTester.testEventProcess(i -> System.out

            .println("event processed : one argument syntax : " + i));

     

        // calling alternative single- argument method of functional interface. alternative

        // syntax of wrapping expression body with curly braces

        eventTester.testEventProcess((i) -> {

          System.out.println("event processed : one argument syntax : " + i);

        });

     

        // calling single- argument method of functional interface which that returns

        // the result

        eventTester.testEventResult((i) -> {

          i = i + 1;

          return i;

        });

      }

    }

     

    Listing 9. Java client

     

    Rethinking the Way We Program

     

    Using lambda expressions can force us to rethink the way we program. It can be more like we have a Java class holding data and exposing API methods that define what operations are permitted on that data. Operational semantics and logic can be injected into those methods based upon what's needed. This allows us to interpret and use the same data in various ways and for various purposes. You can get gist of this in the program shown in Listing 10.

     

    Let's see how we can have different lambda expressions being sent to the same method for different processing objectives. Listing 10 defines a class named EventDispatcher that will hold the list of different events that can be registered and processed as needed.

     

    package com.j2se8.samples.event;

     

    import java.util.ArrayList;

    import java.util.List;

     

    import com.j2se8.samples.event.funct.Event;

    import com.j2se8.samples.event.funct.EventProcess;

    import com.j2se8.samples.event.funct.EventResult;

     

    public class EventDispatcher {

     

      private List<Event> eventList = new ArrayList<Event>();

     

      private List<EventProcess> eventProcessList = new ArrayList<EventProcess>();

     

      private List<EventResult> eventResultList = new ArrayList<EventResult>();

     

      public void registerEventHandler(Event event) {

        eventList.add(event);

      }

     

      public void registerEventProcessHandler(EventProcess eventProcess) {

        eventProcessList.add(eventProcess);

      }

     

      public void registerEventResultHandler(EventResult eventResult) {

        eventResultList.add(eventResult);

      }

    public void dispatchEvent() {

        for (Event event : eventList) {

          event.onEventOccurance();

        }

      }

     

      public void processEvents() {

        int i = 1;

        for(EventProcess process : eventProcessList) {

          process.onEventOccuranceProcess(i++);

        }

      }

     

      public void compute() {

        final int i = 2;

        for(EventResult process : eventResultList) {

          int result = process.onEventOccuranceConfirm(i);

          System.out.println("return result : "+ result);

        }

      }

    }

    Listing 10. Program with lambda expression being sent to the same method

     

    Now let's look at the client, shown in Listing 11, that registers expressions as events and calls EventDispatcher to act upon the registered expressions.

    package com.j2se8.samples.event;

     

    import com.j2se8.samples.event.funct.Event;

    import com.j2se8.samples.event.funct.EventProcess;

    import com.j2se8.samples.event.funct.EventResult;

     

    public class EventDispatcherClient {

     

      public static void main(String[] args) {

     

        EventDispatcher eventDispatcher = new EventDispatcher();

       

        // define no- argument functional interface

        Event event1 = () -> System.out.println("event 1 occurred : no argument syntax");

        Event event2 = () -> System.out.println("event 2 occurred : no argument syntax");

       

        // define single- argument method of functional interface

        EventProcess process1 = -> System.out.println("event processed : one argument syntax : "+i);

        // define alternative single- argument method of functional interface. alternative

        // syntax of omitting parenthesies

        EventProcess process2 = i -> System.out.println("event processed : one argument syntax : "+i);

        // define alternative single- argument method of functional interface. alternative

        // syntax of wrapping expression body with curly braces

        EventProcess process3 = -> {

          System.out.println("event processed : one argument syntax : "+i);

        };

     

        // define single- argument method of functional interface which that returns

        // the result

        EventResult result1 = -> { i = i + 2; return i; };

        EventResult result2 = -> { i = i - 2; return i; };

        EventResult result3 = -> { i = i * 2; return i; };

        EventResult result4 = -> { i = i / 2; return i; };

        EventResult result5 = -> { i = i % 2; return i; };

       

        // registering events

        eventDispatcher.registerEventHandler(event1);

        eventDispatcher.registerEventHandler(event2);

        eventDispatcher.dispatchEvent();

        System.out.println("");

     

        // registering event processes

        eventDispatcher.registerEventProcessHandler(process1);

        eventDispatcher.registerEventProcessHandler(process2);

        eventDispatcher.registerEventProcessHandler(process3);

        eventDispatcher.processEvents();

        System.out.println("");

       

        // registering result- based events

        eventDispatcher.registerEventResultHandler(result1);

        eventDispatcher.registerEventResultHandler(result2);

        eventDispatcher.registerEventResultHandler(result3);

        eventDispatcher.registerEventResultHandler(result4);

        eventDispatcher.registerEventResultHandler(result5);

        eventDispatcher.compute();

      }

    }

    Listing 11. Client that registers multiple expressions as events

     

    Conclusion

     

    We have seen that lambda expressions can be constructed independently and injected into a callee class from a calling class. As we saw in the examples above, lambda expressions provide an implementation for an interface; hence, they can serve as a simple implementation over anonymous classes. This will significantly reduce the number of lines of code that need to be written and might make code maintenance easier, enabling you to introduce new changes with minimal impact. The syntax of lambda expressions is very new to Java programmers and might baffle programmers until it becomes the norm and the Java community becomes more knowledgeable about lambda expressions.

     

    Lambda expressions are a new feature introduced in Java 8, and adoption as part of mainstream programming will take some time. However, this programming approach opens up new programming styles and might change the way we structure our programs. Here, I have tried to explore and introduce a few aspects about using lambda expressions. There are many more to explore. Take your time and explore this topic.

     

    See Also

     

     

    About the Author

     

    Mohan Basavarajappa is a technical architect at Infosys. His areas of interest include Java SE, Java EE, and enterprise content management technologies, such as Oracle WebCenter Content and Oracle WebCenter Sites. Previously, he held the role of a reuse practitioner aiming to increase productivity and reduce time to market by leveraging open source technologies and building organization-wide reusable libraries. He is an open source enthusiast and loves to explore open source frameworks and libraries. He is an active Oracle Technology Network member and you can catch him at https://community.oracle.com/people/Mohan%20Basavarajappa.

     

    Join the Conversation

     

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