Lambdas
Contents
Introduction
Anonymous classes can be used to specify an implementation of an interface. However when interfaces are very simple and contain just one method then lambda expressions are more concise and clear. A lambda expression allows us to focus on the method being implemented rather than the class. Java API has existing interfaces that contain just one method like ActionListener and Runnable and often there is a need to specify a function/method behavior without going through inheritance or declaring a class. Lambda expressions make this possible.Suppose we have an interface and we need to use the method by creating an object and we are only going to use the object in a very specific narrow scope .
File: l1.java
interface i1 { void foo() ; } class classImplementsI1 implements i1 { public void foo() { System.out.println( "Inside method foo. Using class implementation." ) ; } } public class l1 { public static void main( String args[] ) { System.out.println( "l1" ) ; i1 i1Object = new classImplementsI1() ; i1Object.foo() ; i1Object = new i1() { public void foo() { System.out.println( "Inside method foo." + "Anonymous class" ) ; } } ; i1Object.foo() ; i1 i2Object = () -> System.out.println( "Inside method foo." ) ; i2Object.foo() ; } } C:\WebSite\Learn\2\Java\jdk8>java l1 l1 Inside method foo. Using class implementation. Inside method foo.Anonymous class Inside method foo. C:\WebSite\Learn\2\Java\jdk8> The class "classImplementsI1" implements the interface "i1" that has a single method called "foo" . interface i1 { void foo() ; } class classImplementsI1 implements i1 { public void foo() { System.out.println( "Inside method foo. Using class implementation." ) ; } } We can then create an object of the class and call the method foo. i1 i1Object = new classImplementsI1() ; i1Object.foo() ; Since we are using the interface in a very limited scope we can use anonymous class syntax also. i1Object = new i1() { public void foo() { System.out.println( "Inside method foo." + "Anonymous class" ) ; } } ; i1Object.foo() ; Lambdas goes one step further. i1Object.foo() ; i1 i2Object = () -> System.out.println( "Inside method foo." ) ; i2Object.foo() ;We only needed to write the body for a method and that is captured by the Lambda expression. The task of determining the types and the class is left to the compiler. In this case the compiler looks at the left hand side first and determines that there is an interface with a single method.If there wasn't then the compiler would complain of an error. The "()" means that there are no input parameters to the function and the "->" means that the body follows next. In this case the body does not have any return values but if it did then the compiler would try to determine from the expressions what the return value is.
A lambda ecxpression can take multiple statements also.
File: l2.java
//Multiple statemets in a lambda expression interface i1 { void foo() ; } public class l2 { public static void main( String args[] ) { i1 i1Object = () -> { System.out.println( "Inside method foo. Step 1" ) ; System.out.println( "Inside method foo. Step 2" ) ; //int x1 = 5 /0 ; } ; i1Object.foo() ; } } Output: C:\WebSite\Learn\2\Java\jdk8>java l2 Inside method foo. Step 1 Inside method foo. Step 2A lambda expression can take arguments also.
File: l3.java
//An argument in a lambda expression interface i2 { void foo( int arg1 ) ; } public class l3 { public static void main( String args[] ) { i2 i2Object = (arg1) -> System.out.println( "Inside method foo. Arg: " + arg1) ; i2Object.foo( 5 ) ; } } C:\WebSite\Learn\2\Java\jdk8>java l3 Inside method foo. Arg: 5 i2 i2Object = (arg1) -> System.out.println( "Inside method foo. Arg: " + arg1) ; i2Object.foo( 5 ) ; Note we did not have to state type in the argument. The JDK system will figure that out. Since the "i2" interface has a single method that takes an integer; we must call the function with an integer. We can also specify the type of the argument.
File: l4.java
//Explicit argument in a lambda expression interface i2 { void foo( int arg1 ) ; } public class l4 { public static void main( String args[] ) { i2 i2Object = (int arg1) -> System.out.println( "Inside method foo. Arg: " + arg1) ; i2Object.foo( 5 ) ; } }A lambda expression can have a return type and that return type can be implied or specified.
File: l5.java
//Function returning an argument in a lambda expression interface i2 { boolean foo( int arg1 ) ; } public class l5 { public static void main( String args[] ) { i2 i2Object = (int arg1) -> true ; System.out.println( i2Object.foo( 5 ) ) ; //explicit return i2Object = (int arg1) -> { return true ; } ; System.out.println( i2Object.foo( 5 ) ) ; } } C:\WebSite\Learn\2\Java\jdk8>java l5 true true
Scope
Lambda expressions variables do not have their own scope. What does this mean ? If a variable is defined inside a lambda expression then the scope is shared with the statements outside the lambda expression in the block.File: l6.java
//Scope in a lambda expression interface i1 { void foo( ) ; } public class l6 { public static void main( String args[] ) { int x1 = 4 ; //Compiler error x1 should be effectively //final. //x1++ ; i1 i1Object = () -> { // Declaring x1 causes compiler error //int x1; System.out.println("Inside method foo. "); System.out.println("x1: " + x1); }; i1Object.foo( ) ; } } C:\WebSite\Learn\2\Java\jdk8>java l6 Inside method foo. x1: 4 If we declare the variable "x1" inside the lambda expression then that causes a conflict with the "x1" before the lambda expression. Also the "x1" before the lambda expression muse be "effectively" final. If we modify the "x1" then we get a compiler error. The same rules that apply to inner classes and anonymous classes apply here.
Exceptions
A lambda expression can throw and catch exceptions. The "l7.java" shows an example of that.File: l7.java
//Multiple statemets in a lambda expression interface i1 { void foo() ; } public class l7 { public static void main( String args[] ) { i1 i1Object = () -> { try { System.out.println( "Inside method foo. Step 1" ) ; System.out.println( "Inside method foo. Step 2" ) ; int x1 = 5 /0 ; } catch( Exception excep ) { excep.printStackTrace() ; throw excep ; } } ; i1Object.foo() ; } } C:\WebSite\Learn\2\Java\jdk8>java l7 Inside method foo. Step 1 Inside method foo. Step 2 java.lang.ArithmeticException: / by zero at l7.lambda$main$0(l7.java:26) at l7.main(l7.java:34) Exception in thread "main" java.lang.ArithmeticException: / by zero at l7.lambda$main$0(l7.java:26) at l7.main(l7.java:34)
Intersection of Types
A lambda expression can be cast to multiple interface types provided that there are no conflicts.File: multiple1.java
import java.io.* ; interface i1 { public void foo() ; } interface i2 { public void foo() ; } interface i3 { public void foo() ; } interface i4 { public int foo(int arg1) ; } interface i5 { } public class multiple1 { public static void main( String args[] ) { //Normal Lambda object Runnable runnableObject = () -> System.out.println("Serializable!"); //Allowed Runnable runnableObject1 = (Runnable & Serializable)() -> System.out.println("Serializable!"); if( runnableObject1 instanceof Serializable ) System.out.println( "runnableObject1 is Serializable") ; Serializable runnableObject2 = (Runnable & Serializable)() -> System.out.println("Serializable!"); //Compiler error because Serializable does not have any methods. //runnableObject2.run() ; //Allowed i1 i1Object = (i1 & i2 )() -> System.out.println("2 interfaces with a single method of the same signature."); if( i1Object instanceof i2 ) System.out.println( "i1Object also implements i2.") ; i1Object.foo() ; //Allowed i1 i1Object1 = (i1 & i2 & i3)() -> System.out.println("3 interfaces with same signatures."); //Compiler error i4 has a different method signature //i1 i1Object2 = (i1 & i2 & i4)() -> System.out.println("3 interfaces with different signatures."); //Allowed i1 i1Object3 = (i1 & i2 & i5)() -> System.out.println("3 interfaces with a marker interface."); } } C:\WebSite\Learn\2\Java\jdk8>java multiple1 runnableObject1 is Serializable i1Object also implements i2. 2 interfaces with a single method of the same signature. //Allowed Runnable runnableObject1 = (Runnable & Serializable)() -> System.out.println("Serializable!"); The lambda expression can be cast to both the interfaces "Runnable" and "Serializable" even though "Serializable" doesn't have any methods and we can assign the lambda expression to it but we cannot call it. Serializable runnableObject2 = (Runnable & Serializable)() -> System.out.println("Serializable!"); //Compiler error because Serializable does not have any methods. //runnableObject2.run() ; The rule is that if there is a conflict in the method signatures then we have a problem. //Compiler error i4 has a different method signature //i1 i1Object2 = (i1 & i2 & i4)() -> System.out.println("3 interfaces with different signatures."); The i1 and i2 have a single method with the signature: public void foo() ; However "i4" has a single method. public int foo(int arg1) ; Due to the signature difference we cannnot use the intersection of types.
Functional Interfaces
A new package "java.util.function" has been introduced with JDK 8. This basically has interfaces with a single function so that the programmer does not have to define their own interfaces in most cases. Assume there is a Person class and we want to print out all the Persons in a list that satisfy a particular condition. We can create our own interfaceFile: func1.java
import java.util.* ; import java.util.function.* ; //---------------------------------------------------------------------------------------------------------- /* Illustrating the functional interfaces */ class Person { public enum Sex { MALE, FEMALE } String name; //LocalDate birthday; Sex gender; String emailAddress; int age ; public int getAge() { return age ; } public Sex getGender() { return gender ; } public void printPerson() { System.out.println( "Printing Person:" + "Name = " + name ) ; } } //---------------------------------------------------------------------------------------------------------- interface CheckPerson { boolean test(Person p); } //---------------------------------------------------------------------------------------------------------- public class func1 { //---------------------------------------------------------------------------------------------------------- public static void printPersons(List<Person> roster, CheckPerson tester) { for (Person p : roster) { if (tester.test(p)) { p.printPerson(); } } //for } //---------------------------------------------------------------------------------------------------------- public static void main( String args[] ) { System.out.println( "Inside main" ) ; List<Person> roster1 = new Vector<Person>() ; Person p1= new Person() ; p1.name = "Ajay" ; p1.age = 20 ; p1.gender = Person.Sex.MALE ; roster1.add( p1 ) ; p1= new Person() ; p1.name = "Timothy" ; p1.age = 24 ; p1.gender = Person.Sex.MALE ; roster1.add( p1 ) ; // roster1.forEach(System.out::println); printPersons( roster1, (personObj) -> personObj.getAge()> 20 ) ; } //---------------------------------------------------------------------------------------------------------- } C:\WebSite\Learn\2\Java\jdk8>java func1 Inside main Printing Person:Name = Timothy We created an interface interface CheckPerson { boolean test(Person p); } The lambda expression is assigned to this interface. (personObj) -> personObj.getAge()> 20 In the example above "printPersons" is a method that takes a list and also an object that implements the interface "CheckPerson". This interface takes a single method that takes an argument of type "Person" and returns a boolean. There is no need to define our own interface. We can use one from the package "java.util.function" . The following example shows how this can be done.
File: func2.java
import java.util.* ; import java.util.function.* ; //---------------------------------------------------------------------------------------------------------- /* Illustrating the functional interfaces */ class Person { public enum Sex { MALE, FEMALE } String name; //LocalDate birthday; Sex gender; String emailAddress; int age ; public int getAge() { return age ; } public Sex getGender() { return gender ; } public void printPerson() { System.out.println( "Printing Person:" + "Name = " + name ) ; } } //---------------------------------------------------------------------------------------------------------- interface CheckPerson { boolean test(Person p); } //---------------------------------------------------------------------------------------------------------- public class func2 { //---------------------------------------------------------------------------------------------------------- public static void printPersons(List<Person> roster, Predicate<Person> tester ) { for (Person p : roster) { if (tester.test(p)) { p.printPerson(); } } //for } //---------------------------------------------------------------------------------------------------------- public static void main( String args[] ) { System.out.println( "Inside main" ) ; List<Person> roster1 = new Vector<Person>() ; Person p1= new Person() ; p1.name = "Ajay" ; p1.age = 20 ; p1.gender = Person.Sex.MALE ; roster1.add( p1 ) ; p1= new Person() ; p1.name = "Timothy" ; p1.age = 24 ; p1.gender = Person.Sex.MALE ; roster1.add( p1 ) ; // roster1.forEach(System.out::println); printPersons( roster1, (personObj) -> personObj.getAge()> 20 ) ; } //---------------------------------------------------------------------------------------------------------- } C:\WebSite\Learn\2\Java\jdk8>java func2 Inside main Printing Person:Name = Timothy The interface "Predicate" is defined to use generics. Interface PredicateType Parameters: T - the type of the input to the predicate boolean test(T t) Evaluates this predicate on the given argument. Writing Predicate means the method is defined as "boolean test(Person t)" . It is not important what the name of the method is; the only thing that matters the method takes a Person parameter and returns a boolean. The interface Predicate has other methods also. But a lambda expression can only have an interface with a single method so how is this possible. This is where default methods come in ( discussed in this tutorial ) . One of the default methods in class Predicate is "negate" . This method also returns a Predicate but represents a "logical negation". Let's see how this can be used in our example. Suppose instead of a person age greater than 20 we want persons whose age is less than 20 . This can be done as follows:
File: func3.java
import java.util.* ; import java.util.function.* ; //---------------------------------------------------------------------------------------------------------- /* Illustrating the functional interfaces */ class Person { public enum Sex { MALE, FEMALE } String name; //LocalDate birthday; Sex gender; String emailAddress; int age ; public int getAge() { return age ; } public Sex getGender() { return gender ; } public void printPerson() { System.out.println( "Printing Person:" + "Name = " + name ) ; } } //---------------------------------------------------------------------------------------------------------- interface CheckPerson { boolean test(Person p); } //---------------------------------------------------------------------------------------------------------- public class func3 { //---------------------------------------------------------------------------------------------------------- public static void printPersons(List<Person> roster, Predicate<Person> tester ) { tester = tester.negate() ; for (Person p : roster) { if (tester.test(p)) { p.printPerson(); } } //for } //---------------------------------------------------------------------------------------------------------- public static void main( String args[] ) { System.out.println( "Inside main" ) ; List<Person> roster1 = new Vector<Person>() ; Person p1= new Person() ; p1.name = "Ajay" ; p1.age = 20 ; p1.gender = Person.Sex.MALE ; roster1.add( p1 ) ; p1= new Person() ; p1.name = "Timothy" ; p1.age = 24 ; p1.gender = Person.Sex.MALE ; roster1.add( p1 ) ; // roster1.forEach(System.out::println); printPersons( roster1, (personObj) -> personObj.getAge()> 20 ) ; } //---------------------------------------------------------------------------------------------------------- } C:\WebSite\Learn\2\Java\jdk8>java func3 Inside main Printing Person:Name = AjayBy using the statement "tester = tester.negate() ;" we are filtering by the negation of the filter condition that is in the lambda expression. Now all the persons whose age are less than or equal to 20 are selected. Let us see how negate in the interface Predicate is implemented.
/**
* Returns a predicate that represents the logical negation of this
* predicate.
*
* @return a predicate that represents the logical negation of this
* predicate
*/
default Predicate negate() {
return (t) -> !test(t);
}
Interestingly this uses a Lambda expression . So an object of Predicate
is created using the expression of "(personObj) -> personObj.getAge()> 20".
And the function "negate" uses the function "test" of the first
Predicate object to construct another Predicate object.
Similarly the function "or" of the Predicate interface is defined as:
default Predicate or(Predicate super T> other)
This declaration uses the word "super" to make the Predicate less
restrictive. Why "super" and not "extends". Firstly it's always more
useful to define useful function at the top of the hierarchy that
can be used by the child classes. And secondly if we have a "Person"
type for the first "Predicate" then
we know that a Predicate object of a super class can be safely applied
since a Person class will inherit all the attributes of it's base
classes. As an example
suppose we tried to use the "extends" keyword in the Predicate definition
instead of extends.
default MyPredicate or(MyPredicate extends T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
The above will not compile because T in our case is "Person" .
And when we call "other.test(t)" we get a compiler error.
error: incompatible types: T cannot be converted to CAP#1
other.test( t) ;
We are violating the "PECS" rule of
PECS
Producer extends, Consumer super
In this case we can read a value from the "other" in the
form of the type "T" but we can't let it consume some
value of "T". The
reason is that the
constructor takes a "T" type but the "t" could be a
subtype of type "T" . Similarly the "other" object
could be a subtype of "T"
and first "t" may not
be compatible with that object. As an example the
following program shows how this can happen: