Lambda and Functional Interfaces
Programming languages like C++ , javascript etc. are called functional programing languages because we can write functions and use them whenever needed. Being Object oriented is not bad but it adds verbosity. Functional Interfaces and Lambda Expression introduced in Java 8 can be used to write clean and crisp code, which is devoid of boiler-plate codes pieces.
Brief Table of Content
- Introduction to Functional Interfaces
- Valid Functional Interfaces
- InValid Functional Interfaces
- Functinal Interfaces with Lambda
- Invalid Lambdas
- Variables in Lambda
- Lambda as Method Refrences
- Types of Method refrences
- Build-in Functional Interfaces
- Checking Functional Interface types
Introduction to Functional Interfaces
Functional Interfaces are interfaces with exactly one abstract method.They are basis for lambda expressions. Lambda expressions are piece of code that get pass around.
Interface Mammals is a functional interface as it has single abstract method run().
Annotation @FunctionalInterface is just for the sake of clarity, though this is not required.
Compiler will implicitly assume any interface with single abstract method as a functional interface.Marking an interface as a @FunctionalInterface will throw an error at compile time if someone adds more than one abstract method or no method at all.
Further, this annotation will warn developer not to add any other abstract method as this might break code because it will not remain as a functional interface.
Valid Functional Interfaces
Assuming Mammals interface as previously defined , which of the following are valid functional interfaces ?
Answer ? All three of them are valid functional interfaces.
1. First interface Tiger is valid as it adds no new abstract methods.Since it extends interface mammals which in turn has a single abstract method, this interface inherits it, hence a functional interface.
2. Second interface Lion overrides run() method of mammals, therefore this too has single abstract run() method.Hence, Lion is a valid functional interface.
3.Third interface Elephant override run() method of parent Mammals() interface, and has two other methods. One default method and another static method.Java 8, introduced concept of default and static methods neither of which are abstract methods.Hence this too is a valid functional interface.
Invalid Functional Interfaces
All of above interfaces will compile, and they are valid interfaces, but they are invalid functional interfaces.
1. Interface Animals has no abstract method.
2. Interface Human has two abstract methods. One is inherited from it's parent Mammals.
3. Interface monkey has two abstract methods , hence not a valid functional interface.
Functional Interfaces With Lambda
Lambda expression are piece of code that get pass around, just like anonymous methods.Let's
start with a simple Comparator functional interface. This interface has an abstract method compare which takes two objects as input parameters and returns an int.
We have a class Max which has a method findMax which finds maximum from a list of intergers.It also accepts instance of Compator as a parameter.
Lambda expression chosen for illustration for this code is quite simple:
(a , b) -> { return a-b ;}
This expression implies Java called method which takes two parameters and returns an int value.
Now you must be having following queries in your mind ?
1. What's the type of variables 'a' and 'b' ? Integer, String , Object or what ?
2. Which method is actually called ?
3. How did we infer return type of method ?
4. How does this lambda expression a valid one ? What's the syntax of lambda expression ?
Let's answer all above questions.
Understating Lambda Syntax
In above example we used following lambda expression:
( a, b) -> { return a-b ; }
Syntax of above lambda expression is like this.
- We specified two parameters 'a' and 'b' enclosed in parentheses()
- Arrow parameter separating parameters from body
- Body which has one line of code(can have more than one) , including braces {}, a semicolon ; and a return statement.
- Java infers parameter type from method, which has types defined.
- Which method to call is again known to java from context as functional interfaces only carries single abstract methods, there is no way by which conflict might arise.
- Return type is again inferred from return type of method.
E.g. Comparator interface used above takes input as Integer(Inferred from Generic type)
Comparator has only one method, compare that java already knows from the definition of functional interface.
Return type is int, which is return type of method compare.
Following expressions are also valid lambda expressions.
() -> new String("hello,World")
a -> {return "someString" ;}
(Integer a) -> String.valueOf(a)
(Integer a, String b) -> a + b ;
- First one is valid lambda expression for a method that takes no parameter and returns a String
- Second and third are valid lambda expression which takes Integer as an input parameter and returns a String
- Last one can be used for a functional interface that has an abstract method accepting an Integer and String as input parameters and returns a String
Few other valid lambda expressions:
() -> true // 0 parameter
a -> {return a. startsWith("test");} // 1 parameter
(String a) -> a. startsWith("test") // 1 parameter
int x -> {} // 1 parameter
int y -> {return ;} // 1 parameter
(a , b ) -> b.startswith("test") // 2 parameters
(String a, String b) -> a.startsWith("test") // 2 parameters
a -> {return a. startsWith("test");} // 1 parameter
(String a) -> a. startsWith("test") // 1 parameter
int x -> {} // 1 parameter
int y -> {return ;} // 1 parameter
(a , b ) -> b.startswith("test") // 2 parameters
(String a, String b) -> a.startsWith("test") // 2 parameters
Invalid Lambdas
Integer a -> String.valueOf(a) //DOES NOT COMPILEa, b -> a+b + ""; //DOES NOT COMPILE
Integer a, String b -> b.substring(0,1) //DOES NOT COMPILE
All of them requires Parentheses () in parameter ! Parentheses can be skipped only if there is exactly one parameter and it's data type is not specified.
a, b -> a.startsWith("test") //DOES NOT COMPILE
c -> return 10; //DOES NOT COMPILE
a -> {return a.startsWith("test")} //DOES NOT COMPILE
All above expression are invalid lambda expression. Correct version of all of them are following:
( a, b ) -> a.startsWith("test") // Parentheses () needed for more than two parameters
c -> { return 10;} // {} curly braces needed with return keyword
a -> {return a.startsWith("test") ;} //semicolon ; is required after return statement
Data type of parameters are optional. But when data type of one parameter is listed, data type of all parameters must be provided.
(int y, z) -> {int x=1; return y+10; } //DOES NOT COMPILE
(String s, z) -> { return s.length()+z; } //DOES NOT COMPILE
(a, Animal b, c) -> a.getName() //DOES NOT COMPILE
Re-declaration of a variable is not allowed with lambda expression.We here try to re-declare variable 'a' , which is not allowed. E.g.
(a, b) -> { int a = 0; return 5;} //DOES NOT COMPILE
Declaring another variable is perfectly alright.Here new variable 'c' is declared in body.
(a, b) -> { int c = 0; return 5;} //COMPILES AND A VALID LAMBDA EXPRESSION
Variables in Lambda
Lambda expression can access static variables, instance variables, effectively final method parameters, and effectively final local variables. How many are there in following example ?
1. public interface Human {
2. String move();
3. }
4. class HomoSapiens{
5. String talk ="talk";
6. void actions(boolean adult) {
7. String takeDecisions= "MyDecision";
8. perform(()-> talk);
9. perform(()-> adult ? "ride car" : "ride bicycle");
10. perform(()-> takeDecisions); // takeDecisions = "MyNewDecision"
11. }
12. void perform(Human h) {
13. System.out.println(h.move());
14. }
15. }
Line 8 uses instance variable.
Line 9 uses method parameters.
variable "adult" is effectively final as it is not reassigned anywhere and can be used in lambda expression.
Line 10 uses an effectively final local variable (takeDecisions). If we uncomment assigning it to some other value, reassignment will convert it to non effectively final variable.Compiler will cry that when you use this variable in lambda expression.
Other normal rules of access control remains same. E.g. , lambda can't access private variables in another class. Lambdas can access a subset of variables that are accessible, but never more than that.
Lambdas as Method References
Method reference makes code look crisp by just mentioning the name of method.Let's understand how to use them with examples.
class HumanHelper{
public static int compareByHeight(Human one, Human two) {
return one.getHeight() - two.getHeight();
}
public static int compareByWeight(Human one, Human two) {
return one.getWeight() - two.getWeight();
}
}
We can write following implementation for Comparator interface for sorting two Humans by their height.
Comparator<Human> byHeight= (h1, h2) -> HumanHelper.compareByHeight(h1,h2);
That's Good. But it's size can be reduced further. There is some redundancy that can be removed.
Comparator<Human> byHeight= HumanHelper :: compareByHeight;
The :: operator tells java to pass parameters automatically to method compareByHeight.
Java takes out all other necessary information from context.Compiler knows that this method accepts two parameters of type Human and return an int. Hence, passing all those information are superficial.
HumanHelper :: compareByHeight returns a functional interface, but not int. Operator :: , is used for deferred execution.
Types of Method References
- Static methods
- Instance method on a particular interface
- Instance method on an instance to be determined at runtime
- Constructors
Consumer<List<Integer>> methodReference = Collections::sort;
We called a method with one parameter , and Java knows, how to create a lambda with one parameter and pass it to method. Since static sort method has many overloaded methods, Java infers information about correct sort method from context. Here we have declared a Consumer which takes just one parameter of type List<Integer>. Java calls method that matches such description.
Instance Method on of an Instance
String myStr = "myString";
UnaryOperator<String> instanceRef1= myStr :: concat;
UnaryOperator<String> instanceRef2= s-> s.concat("hello");
We called an instance method , concat on instance of Class String. Concat method takes string as input and concatenates to original string.
Instance method on instance determined at runtime
Predicate
Constructor Reference
Supplier
Build in Functional Interfaces
---------------------------------------------------------------------------------------------------------------------
Supplier
Function
BiFunction
UnaryOperator <T> 1(T) T apply
UnaryOperator <T> 2(T,T) T apply
Several other functional interfaces are defined in the java.util.function package.They are for working with primitives.
Supplier Implementation
The Supplier interface is defined as :
@FunctionalInterface
public interface Supplier<T>{
public T get();
}
Supplier<StringBuilder> sb2 = () -> new StringBuilder();
Consumer and BiConsumer Implementation
@FunctionalInterface
public interface Consumer<T>{
void accept(T t);
}
@FunctionalInterface
public interface BiConsumer<T,U>{
void T accept(T t, U u);
}
Predicate and BiPredicate Implementation
Predicate is used usually when filtering or matching.BiPredicate is just like a Predicate except that it takes two parameters instead of one.
@FunctionalInterface
public interface Predicate<T>{
boolean test(T t);
}
@FunctionalInterface
public interface BiPredicate<T,U>{
boolean test(T t, U u);
}
Predicate is used to test a condition.
Predicate<String> isEmptyString1= String :: isEmpty;
Predicate<String> isEmptyString2= x ->x.isEmpty() ;
System.out.println(isEmptyString1.test(""));
System.out.println(isEmptyString2.test(""));
This prints true twice.
BiPredicate<String, String> b1 = String :: startsWith;
BiPredicate<String, String> b2 = (str, prefix) -> str.startsWith(prefix);
System.out.println(b1.test("Indian", "India"));
This prints true twice.BiPredicate takes two parameters.Above BiPredicate uses method reference and called an instance method.
Function and BiFunction Implementation
Function converts one parameter into another parameter of same or different type. BiFunction , takes two parameters of same or different type and turn it into a type similar to either of input parameters or a completly different one.
@FunctionalInterface
public interface Function<T, R>{
R apply(T t);
}
@FunctionalInterface
public interface BiFunction<T,U,R>{
R apply(T t, U u);
}
E.g. This function converts a String to length of String.
Function<String, Integer> f1= String :: length;
Function<String, Integer> f2= x -> x.length();
System.out.println(f1.apply("India"));
System.out.println(f2.apply("India"));
Both will return 5.
Following will combilne two String objects and produces another String.
BiFunction<String,String, String> b1 = String ::concat;
BiFunction<String,String, String> b1 = (string, toAdd) -> string.concat(toAdd);
public interface Function<T, R>{
R apply(T t);
}
@FunctionalInterface
public interface BiFunction<T,U,R>{
R apply(T t, U u);
}
E.g. This function converts a String to length of String.
Function<String, Integer> f1= String :: length;
Function<String, Integer> f2= x -> x.length();
System.out.println(f1.apply("India"));
System.out.println(f2.apply("India"));
Both will return 5.
Following will combilne two String objects and produces another String.
BiFunction<String,String, String> b1 = String ::concat;
BiFunction<String,String, String> b1 = (string, toAdd) -> string.concat(toAdd);
System.out.println(b1.apply("Hello," "World"));
System.out.println(b2.apply("Hello," "World"));
System.out.println(b2.apply("Hello," "World"));
Both will produce "Hello,World".
UnaryOperator and BinaryOperator Implementation
Unary and Binary represents special case of Function.They need to all parameters of same type. Unary operator transform its value into one of same type. UnaryOperarator extends Function.Similarly, BinaryOperator extends BiFunction.
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T,T>{
T apply(T t);
}
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T>{
T apply(T t1, T t2);
}
E.g. This function converts a String to length of String.
UnaryOperator<String> u1= String :: toUpperCase;
UnaryOperator<String> u2= x -> x.toUpperCase();
System.out.println(u1.apply("India"));
System.out.println(u2.apply("India"));
Both will return print "INDIA" twice.
Following will combilne two String objects and produces another String. No need to specify return type because UnaryOperaor requires it to be of same type.
BinaryOperator<String> b1 = String ::concat;
BinaryOperator<String> b1 = (string, toAdd) -> string.concat(toAdd);
public interface UnaryOperator<T> extends Function<T,T>{
T apply(T t);
}
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T>{
T apply(T t1, T t2);
}
E.g. This function converts a String to length of String.
UnaryOperator<String> u1= String :: toUpperCase;
UnaryOperator<String> u2= x -> x.toUpperCase();
System.out.println(u1.apply("India"));
System.out.println(u2.apply("India"));
Both will return print "INDIA" twice.
Following will combilne two String objects and produces another String. No need to specify return type because UnaryOperaor requires it to be of same type.
BinaryOperator<String> b1 = String ::concat;
BinaryOperator<String> b1 = (string, toAdd) -> string.concat(toAdd);
System.out.println(b1.apply("Hello," "World"));
System.out.println(b2.apply("Hello," "World"));
System.out.println(b2.apply("Hello," "World"));
Both will produce "Hello,World".
Checking Functional Interface Types
Let's check our knowledge in finding correct functional interface with few exercises.
________________List<String> ex1= x -> "".equals(x.get(0));
________________<Long> ex2 =(Long l) -> System.out.println(l);
________________<String ,String> ex3 = (s1, s2) -> false;
Example one passes one parameter and returns a boolean. This hint us that it could be either Function or Predicate. Since it takes only one parameter, it's a Predicate.
Example two takes one Long parameter and doesn't return anything. This tell us that it's a Consumer.
Example three takes two parameters of type String and return a boolean;Here boolean is returned with two input parameters, it's a BiPredicate.
Note, if return type would have been Boolean Object , instead of boolean primitive, that this interface would have been a BiFunction as BiFunction has return type Object.
That's all about lambdas and functional interfaces. Functional interface for primitives isn't covered as a part of this post but it's quite similar to Functional interfaces that we have covered here.There will be subtle difference but with knowledge gained in this post, you can quickly learn them.
Happy Learning!!




Comments
Post a Comment