Lambda Expressions

Another major change in Java over the years has been the introduction of Lambda Expressions, they enchance Java firstly the add new syntax elements that increase the expessive power and secondly results in new capabilities being incorporated into the API library. Lambda's have the ability to more easily take advantage of parallel processing on a multi-core server. Lambda expression work extremely well with the Streams API, which supports pipeline operations, also other new features were added due to lambda exporessions, features like default method in interfaces and the method reference which lets you refer to a method without actually executing it.

Lambda expressions seem to be being incorporated in my other programming languages and when you go through the example below you will see why it it make code very easy to read but are very powerful. It's another area which you really need to understand as it can cut development time and make for very easy reading of code.

So what are Lambda Expressions, to start with you need to know two key concepts

Before Lambda expressions, anonymous inner classes were the feature that enabled you to make your code more concise. Anonymous classes are like local classes, except that they do not have a name. You use them if you need to use a local class only once. Lambda expressions replace the bulkiness of anonymous inner classes.

Functional interfaces provide target types for lambda expressions and method references. Each functional interface has a single abstract method, called the function method for that functional interface, to which the lambda's expression's parameter and return types are matched or adapted. Functional interfaces can provide a target type in multiple contexts such as assignment context, method invocation or cast context.

I have another section on Functional Programming - Streams and Lambdas, which covers advanced topics and has many complex examples on how to use streams and lambdas

Lambda Expression Fundamentals

Firstly we need to learn the syntax of Lambda Expressions, the new arrow operator -> divides the lambda into two

Below is the Lambda Expression syntax and some examples and rules

Lambda Expression Syntax
Argument List         Arrow Token           Body
(int x, int y)            ->                x + y
Parameters:
  • A comma-separated list of formal parameters enclosed in parentheses
  • You can obmit the data type of the parameters in a lambda expression
  • If you include data types you must declare a data type for each parameter
  • You can omit the parenthese if there is only one parameter
  • If you have no parameters you can represent this with an empty parenthese set ()
  • Parenthese are always used when you have multiple parameters
Body:
  • Can consist of a single expression
  • You do not have to enclose a void method invocation in braces
  • Can consist of a statement block, a return statement is not an expression, you must enclose statements in braces {}
Valid Lambda Expression
() -> System.out.println("Hello")             # No parameter syntax, () is required
a -> a + 1                                    # Single parameter syntax, no parenthese required
(a) -> a + 1                                  # Single parameter syntax, parenthese valid but optional
(int a) -> a + 1                              # Single parameter syntax, specifying data type
(var a) -> a + 1                              # Single parameter syntax with local varible type inference

(int a, int b) -> a + b                       # Multiple parameter syntax with declared types, all must have a type
(a, b) -> a + b                               # Multiple parameter syntax with no types, if one has not type all must have no types
(var a, var b) -> a + b                       # Multiple parameter syntax the local variable type, if one has var type then all must
have as well

Note: parenthese are always required for multiple parameters

() -> System.out.println("Hello")             # Single line with a statement is valid for methods which have void return type
() -> isTrue && isPlusible                    # Single line expression must be an expression which evaluates to the correct return type or covariant, if method has a return type.
(a) -> {                                      # Using return requires curly braces, all statements must terminate with a semi-colon, you can use local variables
    int count = 1;
    return a + count + 2;
}

Note: You cannot mix var data types with actual data types

Block Lambda Expressions

Most of the time the lambda will be a simple expression, however you can have expression body which contains a block of code which can many statements, you can use variables, loops, if and switch statements, create nested blocks, etc. One thing to remember is that you must explicitly use a return statement to return a value. Lambda expressions can use an instance or static variable in a block lambda, it also has access to this() which refers to the inviking instance of the lambda expressions's enclosing class. Variables (they are effectivity final) inclosed inside a block lambda are local to that block only.

Lambda Block
interface StringFunc { 
  String func(String n); 
} 
 
class BlockLambdaDemo2 { 
  public static void main(String args[]) 
  { 
 
    // This block lambda reverses the characters in a string. 
    StringFunc reverse = (str) ->  { 
      String result = ""; 
      int i; 
 
      for(i = str.length()-1; i >= 0; i--) 
        result += str.charAt(i); 
 
      return result; 
    }; 
 
    System.out.println("Lambda reversed is " + reverse.func("Lambda")); 
    System.out.println("Expression reversed is " + reverse.func("Expression"));  
  } 
}

Generic Lambda Expressions

We learnt about Generics in the previous section, and now we can use this feature with Lambda's

Using Generics with Lambdas
// A generic functional interface. 
interface SomeFunc<T> { 
  T func(T t); 
} 
 
class GenericFunctionalInterfaceDemo { 
  public static void main(String args[]) 
  { 
 
    // Use a String-based version of SomeFunc. 
    SomeFunc<String> reverse = (str) ->  { 
      String result = ""; 
      int i; 
 
      for(i = str.length()-1; i >= 0; i--) 
        result += str.charAt(i); 
 
      return result; 
    }; 
 
    System.out.println("Lambda reversed is " + reverse.func("Lambda")); 
    System.out.println("Expression reversed is " + reverse.func("Expression")); 
 
    // Now, use an Integer-based version of SomeFunc. 
    SomeFunc<Integer> factorial = (n) ->  { 
      int result = 1; 
 
      for(int i=1; i <= n; i++) 
        result = i * result; 
 
      return result; 
    }; 
 
    System.out.println("The factoral of 3 is " + factorial.func(3)); 
    System.out.println("The factoral of 5 is " + factorial.func(5)); 
  } 
}

Passing Lambda Expressions as Arguments

You can pass Lambda Expressions as an argument, this a a very power feature because it gives you a way to pass executable code as an argument to a method

Passing Lambda Expressions
interface StringFunc { 
  String func(String n); 
} 
 
class LambdasAsArgumentsDemo { 
 
  // This method has a functional interface as the type of 
  // its first parameter. Thus, it can be passed a reference to 
  // any instance of that interface, including the instance created 
  // by a lambda expression. 
  // The second parameter specifies the string to operate on. 
  static String stringOp(StringFunc sf, String s) { 
    return sf.func(s);                              // run the lambda passing the parameter s
  } 
 
  public static void main(String args[]) 
  { 
    String inStr = "Lambdas add power to Java"; 
    String outStr; 
 
    System.out.println("Here is input string: " + inStr); 
 
    // Here, a simple expression lambda that uppercases a string 
    // is passed to stringOp( ). 
    outStr = stringOp((str) -> str.toUpperCase(), inStr); 
    System.out.println("The string in uppercase: " + outStr); 
 
    // This passes a block lambda that removes spaces. 
    outStr = stringOp((str) ->  { 
                       String result = ""; 
                       int i; 
 
                       for(i = 0; i < str.length(); i++) 
                       if(str.charAt(i) != ' ') 
                         result += str.charAt(i); 
 
                       return result; 
                     }, inStr); 
 
    System.out.println("The string with spaces removed: " + outStr); 
 
    // Of course, it is also possible to pass a StringFunc instance 
    // created by an earlier lambda expression. For example,  
    // after this declaration executes, reverse refers to a 
    // synthetic instance of StringFunc. 
    StringFunc reverse = (str) ->  { 
      String result = ""; 
      int i; 
 
      for(i = str.length()-1; i >= 0; i--) 
        result += str.charAt(i); 
 
      return result; 
    }; 
 
    // Now, reverse can be passed as the first parameter to stringOp() 
    // since it refers to a StringFunc object. 
    System.out.println("The string reversed: " + stringOp(reverse, inStr)); 
  } 
}

Exceptions and Lambdas

Like all code exceptions can be thrown when running, lambdas are not exempt, they can thrown checked or unchecked exceptions

Lambda Exceptions
interface DoubleNumericArrayFunc { 
  double func(double[] n) throws EmptyArrayException;                 // Notice the throws clause
} 
 
class EmptyArrayException extends Exception { 
  EmptyArrayException() { 
    super("Array Empty"); 
  } 
} 
 
class LambdaExceptionDemo { 
 
  public static void main(String args[]) throws EmptyArrayException 
  { 
    double[] values  = { 1.0, 2.0, 3.0, 4.0 }; 
 
    // This block lambda computes the average of an array of doubles. 
    DoubleNumericArrayFunc average = (n) ->  { 
      double sum = 0; 
 
      if(n.length == 0) 
        throw new EmptyArrayException();                         // throw an exception
 
      for(int i=0; i < n.length; i++) 
        sum += n[i]; 
 
      return sum / n.length; 
    }; 
 
    System.out.println("The average is " + average.func(values)); 
 
    // This causes an exception to be thrown. 
    System.out.println("The average is " + average.func(new double[0]));         // exception thrown becuase length is 0
  } 
}

Method References

A method reference provides the way to refer to a method without executing it, it relates to lambda expressions because it requires a target type context that consists of a compatible functional interface. When envaluated, a method reference also creates an instance of the functional interface. There are three types of method references

Method References to Static Methods
// A functional interface for string operations. 
interface StringFunc { 
  String func(String n); 
} 
 
// This class defines a static method called strReverse(). 
class MyStringOps { 
  // A static method that reverses a string. 
  static String strReverse(String str) { 
      String result = ""; 
      int i; 
 
      for(i = str.length()-1; i >= 0; i--) 
        result += str.charAt(i); 
 
      return result; 
  } 
}     
 
class MethodRefDemo { 
 
  // This method has a functional interface as the type of 
  // its first parameter. Thus, it can be passed any instance 
  // of that interface, including a method reference. 
  static String stringOp(StringFunc sf, String s) { 
    return sf.func(s); 
  } 
 
  public static void main(String args[]) 
  { 
    String inStr = "Lambdas add power to Java"; 
    String outStr; 
 
    // Here, a method reference to strReverse is passed to stringOp(). 
    outStr = stringOp(MyStringOps::strReverse, inStr); 
 
    System.out.println("Original string: " + inStr); 
    System.out.println("String reversed: " + outStr); 
  } 
}
Method References to Instance Methods
// A functional interface for string operations. 
interface StringFunc { 
  String func(String n); 
} 
 
// Now, this class defines an instance method called strReverse(). 
class MyStringOps { 
  String strReverse(String str) { 
      String result = ""; 
      int i; 
 
      for(i = str.length()-1; i >= 0; i--) 
        result += str.charAt(i); 
 
      return result; 
  } 
}     
 
class MethodRefDemo2 { 
 
  // This method has a functional interface as the type of 
  // its first parameter. Thus, it can be passed any instance 
  // of that interface, including method references. 
  static String stringOp(StringFunc sf, String s) { 
    return sf.func(s); 
  } 
 
  public static void main(String args[]) 
  { 
    String inStr = "Lambdas add power to Java"; 
    String outStr; 
 
    // Create a MyStringOps object. 
    MyStringOps strOps = new MyStringOps( ); 
 
    // Now, a method reference to the instance method strReverse 
    // is passed to stringOp(). 
    outStr = stringOp(strOps::strReverse, inStr); 
 
    System.out.println("Original string: " + inStr); 
    System.out.println("String reversed: " + outStr); 
  } 
}
Method References with Generics
// A functional interface that operates on an array 
// and a value, and returns an int result. 
interface MyFunc<T> { 
  int func(T[] vals, T v); 
} 
 
// This class defines a method called countMatching() that 
// returns the number of items in an array that are equal  
// to a specified value. Notice that countMatching() 
// is generic, but MyArrayOps is not. 
class MyArrayOps { 
  static <T> int countMatching(T[] vals, T v) { 
    int count = 0; 
 
    for(int i=0; i < vals.length; i++) 
      if(vals[i] == v) count++; 
 
      return count; 
  } 
}     
 
class GenericMethodRefDemo { 
 
  // This method has the MyFunc functional interface as the 
  // type of its first parameter. The other two parameters 
  // receive an array and a value, both of type T. 
  static <T> int myOp(MyFunc<T> f, T[] vals, T v) { 
    return f.func(vals, v); 
  } 
 
  public static void main(String args[]) 
  { 
    Integer[] vals = { 1, 2, 3, 4, 2 ,3, 4, 4, 5 }; 
    String[] strs = { "One", "Two", "Three", "Two" }; 
    int count; 
 
    count = myOp(MyArrayOps::<Integer>countMatching, vals, 4); 
    System.out.println("vals contains " + count + " 4s"); 
 
    count = myOp(MyArrayOps::<String>countMatching, strs, "Two"); 
    System.out.println("strs contains " + count + " Twos"); 
  } 
}

Constructor References

You can also create constructor references and used to refer to a constructor without instantiating the named class.

Constructor reference example
interface MyConRef {
    ConRefEmployee func(String fname, String lname);
}

public class ConstructorRef1 {

    public static void main(String[] args) {
        MyConRef conRef1 = ConRefEmployee::new;                 // This is the reference to the constructor

        ConRefEmployee emp1 = conRef1.func("Paul", "Valle");
        System.out.println(emp1);
    }
}

class ConRefEmployee {
    private String fname;
    private String Lname;

    public ConRefEmployee(String fname, String lname) {
        this.fname = fname;
        Lname = lname;
    }

    // Getters and Setters ........

    @Override
    public String toString() {
        return fname + " " + Lname;
    }
}
Constructor reference with Generics
// MyFunc is now a generic functional interface. 
interface MyFunc<T> { 
   MyClass<T> func(T n); 
} 
 
class MyClass<T> { 
  private T val; 
 
  // A constructor that takes an argument. 
  MyClass(T v) { val = v; } 
 
  // This is the default constructor. 
  MyClass( ) { val = null;  } 
 
  // ... 
 
  T getVal() { return val; };   
}     
 
class ConstructorRefDemo2 { 
 
  public static void main(String args[]) 
  { 
    // Create a reference to the MyClass<T> constructor. 
    MyFunc<Integer> myClassCons = MyClass<Integer>::new; 
 
    // Create an instance of MyClass<T> via that constructor reference. 
    MyClass<Integer> mc = myClassCons.func(100); 
 
    // Use the instance of MyClass<T> just created. 
    System.out.println("val in mc is " + mc.getVal( )); 
  } 
}

Predefined Functional Interfaces

There are a number of Generic and Primitaive interfaces that statisfy common patterns and several of these are in the java.util.function package

Functional Interface Generic Functional Interface Typed
UnaryOperator<T> DoubleUnaryOperator
IntUnaryOperator
LongUnaryOperator
BinaryOperator<T> DoubleBinaryOperator
IntBinaryOperator
LongBinaryOperator
Consumer<T> DoubleConsumer
IntConsumer
LongConsumer
Predicate<T> DoublePredicate
IntPredicate
LongPredicate
Supplier<T> DoubleSupplier
IntSupplier
LongSupplier
Function<T,R> DoubleFunction<R>
IntFunction<R>
LongFunction<R>

Below is a complete example of a few Lambda expressions

Lambda example
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class SuppliedInterfaceTests {

    public static void main(String[] args) {

        // Set up some test data
        String[] dictionary = {"Angry", "Apple", "Art",
                "Ball", "Box", "Bump",
                "Cap", "Car", "Cone",
                "Dart", "Dog", "Duck"};

        // -- Consumer Example
        // Method returns no result, just does something on
        // the parameter passed
        Consumer str = s -> {
            StringBuilder sb = new StringBuilder(s);
            sb.insert(0, "MyApplication: SuppliedInterfaceTests : ");
            sb.insert(0, LocalDateTime.now().toLocalTime() + ": ");
            System.out.println(sb);
        };
        str.accept("I want to log this statement");

        // -- Predicate Example
        // Method returns a boolean, accepts one parameter
        Predicate aWords = p -> p.indexOf("A") == 0;
        ArrayList a = new ArrayList(Arrays.asList(dictionary));
        // demonstrate with ArrayList.removeIf method which accepts a
        // Predicate as a parameter
        a.removeIf(aWords);
        System.out.println(a);

        // Now, we demonstrate test method on Predicate
        String apple = "Apple";
        if (aWords.test(apple)) {
            System.out.println(apple + " starts with an A");
        }

        // -- Supplier Example
        // Method returns an object, accepts no parameter
        Supplier stringSupplier =
                () -> new String("returning a new String Object");
        System.out.println("stringSupplier.get() = " + stringSupplier.get());

        // -- Function Example
        // Method returns a result, and accepts one parameter
        Function funkyFunction = (s) -> {
            s = s.repeat(5);
            return s;
        };

        System.out.println("funkyFunction.apply() = "
                + funkyFunction.apply("oh no "));
    }
}