Lambda ( λ ) Expression -Java




Lambda ( λ ) Expression

☀ Lambda calculus is a big change in mathematical world which has been introduced in 1930.
Because of benefits of Lambda calculus slowly this concepts started using in programming world.
“LISP” is the first programming which uses Lambda Expression.
☀ The other languages which uses lambda expressions are:
 C#.Net
 C Objective
 C
 C++
 Python
 Ruby etc.
and finally in Java also.
☀ The Main Objective of Lambda Expression is to bring benefits of functional programming into
Java.

What is Lambda Expression (λ):
Lambda Expression is just an anonymous (nameless) function. That means the function which
doesn’t have the name, return type and access modifiers.
Lambda Expression also known as anonymous functions or closures.
Ex: 1


Normal
Lambda Expression (λ)
public void m1() {
sop(“hello”);
}
() -> {
sop(“hello”);
}


() ->{ sop(“hello”); }


() -> sop(“hello”);
public void add(inta, int b) {
sop(a+b);
}




(inta, int b) ->sop(a+b);
public String str(String str) {
return str;
}




(String str) -> return str;


(str) ->str;



If the type of the parameter can be decided by compiler automatically based on the context then
we can remove types also.
The above Lambda expression we can rewrite as (a,b) ->sop (a+b);



Conclusions:
1) A lambda expression can have zero or more number of parameters (arguments).
Ex:
() ->sop(“hello”);
(int a ) ->sop(a);
(inta, int b) -> return a+b;

2) Usually we can specify type of parameter. If the compiler expects the type based on the context
then we can remove type. i.e., programmer is not required.
Ex:
(inta, int b) -> sop(a+b);
(a,b) -> sop(a+b);

3) If multiple parameters present then these parameters should be separated with comma (,).

4) If zero number of parameters available then we have to use empty parameter [ like ()].
Ex: () -> sop(“hello”);

5) If only one parameter is available and if the compiler can expect the type then we can remove the
type and parenthesis also.
Ex:
(int a) -> sop(a);
(a)-> sop(a);
A -> sop(a);


6) Similar to method body lambda expression body also can contain multiple statements. If more
than one statements present then we have to enclose inside within curly braces. If one statement
present then curly braces are optional.

7) Once we write lambda expression we can call that expression just like a method, for this
functional interfaces are required.


Functional Interfaces

If an interface contain only one abstract method, such type of interfaces are called functional
interfaces and the method is called functional method or single abstract method (SAM).
Ex:

Runnable -- It contains only run() method
Comparable -- It contains only compareTo() method
ActionListener -- It contains only actionPerformed()
Callable -- It contains only call() method

Inside functional interface in addition to single Abstract method (SAM) we write any number of
default and static methods.

Ex:
interface Interf {

public abstract void m1();

default void m2() {

System.out.println (“hello”);

}
}

In Java 8, Sun Micro System introduced @Functional Interface annotation to specify that the interface is Functional Interface.

Ex:
@Functional Interface
Interface Interf {
public void m1();
}

This code compiles without any compilation errors.
Inside Functional Interface we can take only one abstract method, if we take more than one abstract
method then compiler raise an error message that is called we will get compilation error.
Ex:
@Functional Interface {
public void m1();
public void m2();
}

Inside Functional Interface we have to take exactly only one abstract method.If we are not declaring
that abstract method then compiler gives an error message.
Ex:
@Functional Interface {
interface Interface {
}
compilation error

Functional Interface with respect to Inheritance:

If an interface extends Functional Interface and child interface doesn’t contain any abstract method
then child interface is also Functional Interface
Ex:
@Functional Interface
interface A {
public void methodOne();
}
@Functional Interface
Interface B extends A {
}

In the child interface we can define exactly same parent interface abstract method.

Ex:

@Functional Interface
interface A {
public void methodOne();
}
@Functional Interface
interface B extends A {
public void methodOne();
}
No Compile Time Error

In the child interface we can’t define any new abstract methods otherwise child interface won’t be
Functional Interface and if we are trying to use @Functional Interface annotation then compiler gives an error message.

@Functional Interface {
interface A {
public void methodOne();
}
@Functional Interface
interface B extends A {
public void methodTwo();
}
Compiletime Error




Ex:
@Functional Interface
interface A {
public void methodOne();
}

interface B extends A {
public void methodTwo();
}

No compile time error

This’s Normal interface so that code compiles without error
In the above example in both parent & child interface we can write any number of default methods
and there are no restrictions. Restrictions are applicable only for abstract methods.

Functional Interface Vs Lambda Expressions:

Once we write Lambda expressions to invoke it’s functionality, then Functional Interface is required.We can use Functional Interface reference to refer Lambda Expression.
Where ever Functional Interface concept is applicable there we can use Lambda Expressions

Ex:1
Without Lambda Expression

interface Interf {

public void methodOne();

}

public class Demo implements Interface {

public void methodOne() {

System.out.println(“method one execution”);

}

public class Test {

public static void main(String[] args) {

Interf i = new Demo();

i.methodOne();
}
}





Above code With Lambda expression

interface Interf {

public void methodOne();

}

class Test {

public static void main(String[] args) {

Interf i = () -> System.out.println(“MethodOne Execution”);

i.methodOne();
}
}


Without Lambda Expression

interface Interf {
public void sum(inta,int b);
}

class Demo implements Interf {
public void sum(inta,int b) {
System.out.println(“The sum:”+(a+b));
}
}

public class Test {
public static void main(String[] args) {
Interfi = new Demo();
i.sum(20,5);
}
}

Above code With Lambda Expression

interface Interf {
public void sum(inta, int b);
}
class Test {
public static void main(String[] args) {
Interf i = (a,b) ->System.out.println(“The Sum:” +(a+b));
i.sum(5,10);
}
}


Without Lambda Expressions

interface Interf {
publicint square(int x);
}

class Demo implements Interf {
public int square(int x) {
return x*x;
}
}

class Test {
public static void main(String[] args) {
Interf i = new Demo();
System.out.println(“The Square of 7 is: “ +i.square(7));
}
}

Above code with Lambda Expression

interface Interf {
public int square(int x);
}

class Test {
public static void main(String[] args) {
Interf i = x -> x*x;
System.out.println(“The Square of 5 is:”+i.square(5));
}
}





















Without Lambda expression

class MyRunnable implements Runnable {
public void run() {
for(int i=0; i<10; i++) {
System.out.println(“Child Thread”);
}
}
}

class ThreadDemo {
public static void main(String[] args) {
Runnable r = new myRunnable();
Thread t = new Thread(r);
t.start();
for(int i=0; i<10; i++) {
System.out.println(“Main Thread”)
}
}
}







With Lambda expression

class ThreadDemo {
public static void main(String[] args) {
Runnable r = () ->{
for(int i=0; i<10; i++) {
System.out.println(“Child Thread”);
}
};
Thread t = new Thread(r);
t.start();
for(i=0; i<10; i++) {
System.out.println(“Main Thread”);
}
}
}
















Anonymous inner classes vs Lambda Expressions

Wherever we are using anonymous inner classes there may be a chance of using Lambda expression
to reduce length of the code and to resolve complexity.

Ex: With anonymous inner class
class Test {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
public void run() {
for(int i=0; i<10; i++) {
System.out.println("Child Thread");
}
}
});
t.start();
for(int i=0; i<10; i++)
System.out.println("Main thread");
}
}


With Lambda expression
class Test {
public static void main(String[] args) {
Thread t = new Thread(()->{
for(int i=0; i<10; i++) {
System.out.println("Child Thread");
}
});
t.start();
for(int i=0; i<10; i++) {
System.out.println("Main Thread");
}
}
}

What are the advantages of Lambda expression?

We can reduce length of the code so that readability of the code will be improved.
We can resolve complexity of anonymous inner classes.
We can provide Lambda expression in the place of object.
We can pass lambda expression as argument to methods.








☀ Hence if anonymous inner class implements Functional Interface in that particular case only we
can replace with lambda expressions. Hence wherever anonymous inner class concept is there, it
may not possible to replace with Lambda expressions.

Anonymous inner class can extend concrete class, can extend abstract class, can implement
interface with any number of methods

Lambda expression can implement an interface with only single abstract method (Functional
Interface).

Inside anonymous inner class we can declare instance variables.


☀ Anonymous inner class! = Lambda Expression

☀ Inside anonymous inner class “this” always refers current inner class object(anonymous inner
class) but not related outer class object
Ex:
☀ Inside lambda expression we can’t declare instance variables.
☀ Whatever the variables declare inside lambda expression are simply acts as local variables
☀ Within lambda expression ‘this” keyword represents current outer class object reference (that is
current enclosing class reference in which we declare lambda expression)
Ex:

interface Interf {
public void m1();
}

class Test {
int x = 777;
public void m2() {
Interfi = ()->{
int x = 888;
System.out.println(x); //888
System.out.println(this.x); //777
};
i.m1();
}
public static void main(String[] args) {
Test t = new Test();
t.m2();
}
}

☀ From lambda expression we can access enclosing class variables and enclosing method variables
directly.
☀ The local variables referenced from lambda expression are implicitly final and hence we can’t
perform re-assignment for those local variables otherwise we get compile time error





Ex:
interface Interf {
public void m1();
}

class Test {
int x = 10;
public void m2() {
int y = 20;
Interfi = ()->{
System.out.println(x); //10
System.out.println(y); //20
x = 888;
y = 999; //CE
};
i.m1();
y = 777;
}
public static void main(String[] args) {
Test t = new Test();
t.m2();
}
}































Anonymous Inner Class
Lambda Expression
Anonymous Inner class
It’s a class without name
Lambda Expression
It’s a method without name (anonymous
function)
Anonymous inner class can extend
Abstract and concrete classes
Anonymous inner class can implement
An interface that contains any number of
Abstract methods
lambda expression can’t extend
Abstract and concrete classes
lambda expression can implement an
Interface which contains single abstract method
(Functional Interface)
Inside anonymous inner class we can
Declare instance variables.
Inside lambda expression we can’t
Declare instance variables, whatever the
variables declared are simply acts as local
variables.
Anonymous inner classes can be
Instantiated
Inside anonymous inner class “this”
Always refers current anonymous
Inner class object but not outer class
Object.
lambda expressions can’t be instantiated
Inside lambda expression “this”
Always refers current outer class object. That is
enclosing class object.
Anonymous inner class is the best choice
If we want to handle multiple methods.
Lambda expression is the best Choice if we want
to handle interface
With single abstract method (Functional
Interface).
In the case of anonymous inner class
At the time of compilation a separate
Dot class file will be generated
(outerclass$1.class)
At the time of compilation no dot Class file will be
generated for Lambda expression. It simply
converts in to private method outer class.
Memory allocated on demand
Whenever we are creating an object
Reside in permanent memory of JVM


Lambda Expressions with Collections

Collection is nothing but a group of objects represented as a single entity.
The important Collection types are:
1. List(I)
2. Set(I)
3. Map(I)

1. List(I):
If we want to represent a group of objects as a single entity where duplicate objects are allowed
and insertion order is preserved then we shoud go for List.
1. Insertion order is preserved
2. Duplicate objects are allowed

The main implementation classes of List interface are:
1. ArrayList 2. LinkedList 3. Vector 4. Stack
Demo Program to describe List Properties:

import java.util.ArrayList;
class Test
{
public static void main(String[] args)
{
ArrayList<String> l = new ArrayList<String>();
l.add("Sunny");
l.add("Bunny");
l.add("Chinny");
l.add("Sunny");
System.out.println(l);
}}
Output: [Sunny, Bunny, Chinny, Sunny]
Note: List(may be ArrayList,LinkedList,Vector or Stack) never talks about sorting order. If we want
sorting for the list then we should use Collections class sort() method.
Collecttions.sort(list)==>meant for Default Natural Sorting Order
Collections.sort(list,Comparator)==>meant for Customized Sorting Order

2. Set(I):
If we want to represent a group of individual objects as a single entity where duplicate objects are
not allowed and insertion order is not preserved then we should go for Set.
1. Insertion order is not preserved
2. Duplicates are not allowed.If we are trying to add duplicates then we won't get any error, just
add() method returns false.

The following are important Set implementation classes
1. HashSet 2.TreeSet etc

Demo Program for Set:
import java.util.HashSet;
class Test
{
public static void main(String[] args)
{
HashSet<String> l = new HashSet<String>();
l.add("Sunny");
l.add("Bunny");
l.add("Chinny");
l.add("Sunny");
System.out.println(l);
}
}
Output: [Chinny, Bunny, Sunny]
Note: In the case of Set, if we want sorting order then we should go for: TreeSet

3. Map(I):
If we want to represent objects as key-value pairs then we should go for
Map
Eg:
Rollno-->Name
mobilenumber-->address
The important implementation classes of Map are:
1. HashMap 2. TreeMap etc

Demo Program for Map:
import java.util.HashMap;
class Test
{
public static void main(String[] args)
{
HashMap<String,String> m= new HashMap<String,String>();
m.put("A","Apple");
m.put("Z","Zebra");
m.put("Durga","Java");
m.put("B","Boy");
m.put("T","Tiger");
System.out.println(m);
}
}
Output: {A=Apple, B=Boy, T=Tiger, Z=Zebra, Durga=Java}
Sorted Collections:
1. Sorted List
2. Sorted Set
3. Sorted Map
1. Sorted List:
List(may be ArrayList,LinkedList,Vector or Stack) never talks about sorting order. If we want
sorting for the list then we should use Collections class sort() method.
Collecttions.sort(list)==>meant for Default Natural Sorting Order
For String objects: Alphabetical Order
For Numbers : Ascending order

Instead of Default natural sorting order if we want customized sorting order then we should go for
Comparator interface.
Comparator interface contains only one abstract method: compare()
Hence it is Functional interface.

public int compare(obj1,obj2)
returns -ve iff obj1 has to come before obj2
returns +ve iff obj1 has to come after obj2
returns 0 iff obj1 and obj2 are equal
Collections.sort(list,Comparator)==>meant for Customized Sorting Order

Demo Program to Sort elements of ArrayList according to Defaut Natural Sorting
Order(Ascending Order):

import java.util.ArrayList;
import java.util.Collections;
class Test
{
public static void main(String[] args)
{
ArrayList<Integer> l = new ArrayList<Integer>();
l.add(10);
l.add(0);
l.add(15);
l.add(5);
l.add(20);
System.out.println("Before Sorting:"+l);
Collections.sort(l);
System.out.println("After Sorting:"+l);
}
}
Output:
Before Sorting:[10, 0, 15, 5, 20]
After Sorting:[0, 5, 10, 15, 20]
Demo Program to Sort elements of ArrayList according to Customized Sorting
Order(Descending Order):




import java.util.TreeSet;
import java.util.Comparator;
class MyComparator implements Comparator<Integer>
{
public int compare(Integer I1,Integer I2)
{
if(I1<I2)
{
return +1;
}
else if(I1>I2)
{
return -1;
}
else
{
return 0;
}
}
}
class Test
{
public static void main(String[] args)
{
TreeSet<Integer> l = new TreeSet<Integer>(new MyComparator());
l.add(10);
l.add(0);
l.add(15);
l.add(5);
l.add(20);
System.out.println(l);
}
}
//Descending order Comparator
Output: [20, 15, 10, 5, 0]

Shortcut way:

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Collections;
class MyComparator implements Comparator<Integer>
{
public int compare(Integer I1,Integer I2)
{
return (I1>I2)?-1:(I1<I2)?1:0;
}
}
class Test
{
public static void main(String[] args)
{
ArrayList<Integer> l = new ArrayList<Integer>();
l.add(10);
l.add(0);
l.add(15);
l.add(5);
l.add(20);
System.out.println("Before Sorting:"+l);
Collections.sort(l,new MyComparator());
System.out.println("After Sorting:"+l);
}
}

Sorting with Lambda Expressions:
As Comparator is Functional interface, we can replace its implementation with Lambda Expression
Collections.sort(l,(I1,I2)->(I1<I2)?1:(I1>I2)?-1:0);
Demo Program to Sort elements of ArrayList according to Customized Sorting Order By
using Lambda Expressions(Descending Order):

import java.util.ArrayList;
import java.util.Collections;
class Test
{
public static void main(String[] args)
{
ArrayList<Integer> l= new ArrayList<Integer>();
l.add(10);
l.add(0);
l.add(15);
l.add(5);
l.add(20);
System.out.println("Before Sorting:"+l);
Collections.sort(l,(I1,I2)->(I1<I2)?1:(I1>I2)?-1:0);
System.out.println("After Sorting:"+l);
}
}
Output:
Before Sorting:[10, 0, 15, 5, 20]
After Sorting:[20, 15, 10, 5, 0]
2. Sorted Set
In the case of Set, if we want Sorting order then we should go for TreeSet
1. TreeSet t = new TreeSet();
This TreeSet object meant for default natural sorting order
2. TreeSet t = new TreeSet(Comparator c);
This TreeSet object meant for Customized Sorting Order







Demo Program for Default Natural Sorting Order(Ascending Order):
import java.util.TreeSet;
class Test
{
public static void main(String[] args)
{
TreeSet<Integer> t = new TreeSet<Integer>();
t.add(10);
t.add(0);
t.add(15);
t.add(5);
t.add(20);
System.out.println(t);
}
}
Output: [0, 5, 10, 15, 20]

Demo Program for Customized Sorting Order(Descending Order):
import java.util.TreeSet;
class Test
{
public static void main(String[] args)
{
TreeSet<Integer> t = new TreeSet<Integer>((I1,I2)->(I1>I2)?-1:(I1<I2)?1:0);
t.add(10);
t.add(0);
t.add(15);
t.add(25);
t.add(5);
t.add(20);
System.out.println(t);
}
}
Output: [25, 20, 15, 10, 5, 0]

Sorted Map:
In the case of Map, if we want default natural sorting order of keys then we should go for
TreeMap.
TreeMap m = new TreeMap();
This TreeMap object meant for default natural sorting order of keys
TreeMap t = new TreeMap(Comparator c);
This TreeMap object meant for Customized Sorting Order of keys











Demo Program for Default Natural Sorting Order(Ascending Order):

import java.util.TreeMap;
class Test
{
public static void main(String[] args)
{
TreeMap<Integer,String> m = new TreeMap<Integer,String>();
m.put(100,"Durga");
m.put(600,"Sunny");
m.put(300,"Bunny");
m.put(200,"Chinny");
m.put(700,"Vinny");
m.put(400,"Pinny");
System.out.println(m);
}
}
Output: {100=Durga, 200=Chinny, 300=Bunny, 400=Pinny, 600=Sunny, 700=Vinny}

Demo Program for Customized Sorting Order(Descending Order):
import java.util.TreeMap;
class Test
{
public static void main(String[] args)
{
TreeMap<Integer,String> m = new TreeMap<Integer,String>((I1,I2)->(I1<I2)?1:(I1>I2)?-
1:0);
m.put(100,"Durga");
m.put(600,"Sunny");
m.put(300,"Bunny");
m.put(200,"Chinny");
m.put(700,"Vinny");
m.put(400,"Pinny");
System.out.println(m);
}
}
Output: {700=Vinny, 600=Sunny, 400=Pinny, 300=Bunny, 200=Chinny, 100=Durga}

Sorting for Customized class objects by using Lambda Expressions:
import java.util.ArrayList;
import java.util.Collections;
class Employee
{
int eno;
String ename;
Employee(int eno,String ename)
{
this.eno=eno;
this.ename=ename;
}
public String toString()
{
return eno+":"+ename;
}
}
class Test
{
public static void main(String[] args)
{
ArrayList<Employee> l= new ArrayList<Employee>();
l.add(new Employee(100,"Katrina"));
l.add(new Employee(600,"Kareena"));
l.add(new Employee(200,"Deepika"));
l.add(new Employee(400,"Sunny"));
l.add(new Employee(500,"Alia"));
l.add(new Employee(300,"Mallika"));
System.out.println("Before Sorting:");
System.out.println(l);
Collections.sort(l,(e1,e2)->(e1.eno<e2.eno)?-1:(e1.eno>e2.eno)?1:0);
System.out.println("After Sorting:");
System.out.println(l);
}
}
Output:
Before Sorting:
[100:Katrina, 600:Kareena, 200:Deepika, 400:Sunny, 500:Alia, 300:Mallika]
After Sorting:
[100:Katrina, 200:Deepika, 300:Mallika, 400:Sunny, 500:Alia, 600:Kareena]

Comments

Popular posts from this blog

SafeVarargs Annotation - Java

Diamond Operator - Java

Predefined Functional Interface - Function-Java