Tuesday 27 January 2015

Java 8: Lambdas



The latest version of Java, namely Java 8, has created quite a stir. Not just because it brings in an enhancement over the previous versions with its new Date API, repeating annotations, Java FX etc.  but more so because it makes room for some interesting features of functional programming paradigm in its otherwise truly object oriented kingdom. Two of these features are Lambda and the Streams API.

In this article we are going to talk about Lambda.
Historically, the concept of Lambda traces back to Lambda calculus formulated by Alonzo Church in the 1930s. In a crude interpretation a Lambda can be thought of as representing a function, most often an anonymous function. The concept slowly streamed into programming. Coming to think of it, the advent of computers and programming languages to communicate with them was paved by the need for performing complex mathematical calculations correctly and quickly. Hence the conceptual bonding between mathematics and computer programming. Anyways, it is useful to think of Lambdas as anonymous functions that can be assigned to variables, passed as an argument to functions and can be returned from functions. Many programming languages like Ruby, Python, Scala, JavaScript etc. already have built-in Lambda functionality. And now, Java 8 has accommodated it too. But Java, an inherently Objected Oriented language did some very clever tricks to retain backward compatibility and make this accommodation.

In the kingdom of Java everything is an object. But Lambdas are functions. How do they get ‘citizenship’? Hence, the introduction of the concept of a Functional Interface under which Lambdas could be classed.  But before delving into its nitty-gritty there is one important feature to take note of. Java 8 allows interfaces to have implementation of methods, typed as “default” and “static” methods. These implemented methods can be inherited and overridden just like the methods of “Abstract” classes. Yes, that brings interfaces closer to “Abstract” classes, but they are still interfaces. They still support “multiple-inheritance”, “Abstract” classes don’t.

Example of default and static methods in Java 8 interfaces.

public interface myFunctionalInterface{

    default void method1(){}
    static String doSomething(){ System.out.println("Do something");}

}

Now, a Functional Interface is a normal Java 8 interface that has exactly one abstract method. Sometimes they are tagged as SAM, Single Abstract Method. An annotation, namely “@FunctionalInterface” has also been introduced to mark such interfaces. And this would prompt a compile time error if the agreement is not adhered to, that is if more than one abstract method is introduced in the interface. 

@FunctionalInterface
public interface myFunctionalInterface2{
    public void myAbstractMethod();
    default void myDefaultMethod(){}
   //public void mySecAbstractMethod();  throws compile time error
}

So the type of a Lambda maps to the signature of the abstract method defined in the functional interface.This has been made possible because of the clever exploitation of InvokeDynamics, a feature introduced in Java 7.

Now that we are clear on the basics, let's get to know Lambdas through some short and simple sample programs  to enable a stronger grip on the concept.

Case: Existing SAM Interfaces
There are interfaces in Java written prior to Java 8 that have only one abstract method. And guess what, a Lambda can be supplied to such functional interfaces. 
 Example: Runnable r = () -> System.out.println("Lambda for runnable interface");
                 r.run(); //prints "Lambda for runnable interface"

Case: Overriding default method
Example: @FunctionalInterface
                 public interface myInterface1(){
                          default void methodTest(){ System.out.println("Test Method");}
                         public void abstractMethod();
                 }

                 public interface myInterface2 extends myInterface1(){
                           default void methodTest(){
                                   System.out.println("Overridden Test Method");
                           }
                  }


Case: Conflict scenario with default method
Example: @FunctionalInterface                                                   @FunctionalInterface
                  interface A{                                                                  interface B{
                        default void someMethod(){};                                       default void someMethod(){};
                       public void abstractMethodA();                                     public void abstractMethodB();
                  }                                                                                    }

                Class AB implements A,B{
                         .............}
               //A compile error would be thrown if class AB does not explicitly specify which 'someMethod()' it refers to OR provides its own implementation of 'someMethod()'
                                                                             
Case: Multiple-inheritance with default methods
 Example: @FunctionalInterface
                  interface Parent{
                         default void testOne() {
                              System.out.println("Test Parent");
                         }
                         public void abstractMethod();
                  }

                  @FunctionalInterface
                   interface Child extends Parent{
                           @Override
                            default void testOne(){
                                        System.out.println("Test Child");
                            }
                            public void abstractChildMethod();
                    }

                  class Test implements Child{
                          ...........}

                  Invoking testOne() on an instance of Test class would print "Test Child"

 
Case: Local Variable access within Lambda
Local variable which are declared final or effectively final, meaning their values are not modified are accessible within Lambda.
Example: public void someMethod(){
                          int x = 10;
                          Runnable r = () -> System.out.println("The variable is: "+x);
                          r.run();
                 }

Case: Using one of the functional interfaces provided in java.util.* package
There are some 43 functional interfaces provided in Java 8. Supplier, Consumer, Predicate etc. for ease of use by the developers. One can simply provide a Lambda that matches the signature of the abstract method defined in these interfaces.
Example: Consumer<Integer> consumer = x -> System.out.println("Printing x: "+ x);
                List<Integer> list = Arrays.asList(34,22,12,456);
       
                list.forEach(consumer);
Case: Using existing custom methods as Lambda
Example: @FunctionalInterface
                 public interface FuncInterface1 {
                      public String encodePassword(String passwd, int id);
                  }
              public class SampleFunctionalIf{ 
                       public static FuncInterface1 sampleEncode(){
                              return (password,i) -> password.toLowerCase(); **
                       }
                      public static void doSomething(FuncInterface1 func){
                           String str = func.encodePassword("PassWord", 123);
                           System.out.println(str);
                      }   
                   public static void main(String[] args){
                           doSomething((password,i) -> password.toUpperCase()); **
               
                          FuncInterface1 f = sampleEncode();
                         doSomething(f);               
                    }
               }

Note: ** The underlined Lambdas map to the encodePassword() in the functional interface. Note that the signature of the Lambda has to match the signature of the abstract method.

Case: Method references
 Lambdas also allow referencing methods of classes or class instances using the "::" <colon> operator. 
Example: public class Person {
                    String firstName, lastName;
                    public Person(){
                          System.out.println("New Person created from default constructor");
                       }
                   }
                  
             public interface PersonFactory<P extends Person> {
                    P create(String fname, String lname);
             }

           public class MethodRefSample1 {
              public static void main(String[] args){
                     PersonFactory<Person> p = Person::new;
                     p.create("Carlie", "Hebdo");
               }
           } 

So, that’s how Lambdas make programming much more succinct, quick and easy. And of course, it could be used to write pretty complex codes as well.

In no way do the above examples cover the length and breadth of Lambdas, but they do provide a quick peek at what Lambda in Java 8 is all about and what it is capable of.


The Streams API exploits Lambdas to make it much easier to perform operations on Java Collections both sequentially and in parallel. Will talk more on Streams in another article.


No comments:

Post a Comment