Interfaces

  • Interface contains empty methods. 
  • Any class based on that interface will have certain behaviors and will have to implement all the methods in that interface.
Interface
 package javaimplant.interfaces;  
 public interface Animal {  
      void makeSound();  
 }  
Dog Class Extends Interface
 package javaimplant.interfaces;  
 public class Dog implements Animal {  
      public void makeSound() {  
           System.out.println("Woof Woof");  
      }  
 }  
Cat class extends interface
 package javaimplant.interfaces;  
 public class Cat implements Animal {  
      public void makeSound() {  
           System.out.println("Meaw");  
      }  
 }  
Main Class
 package javaimplant.interfaces;  
 public class InterfaceExample   
 {  
      public static void main(String args[])  
      {  
           Animal a1=new Dog();  
           Animal a2=new Cat();  
           a1.makeSound();  
           a2.makeSound();  
      }  
 }  
    Functional Interfaces
    • Java Functional Interfaces are present in java.util.function package.
    • These out of the box interfaces address some of the common scenarios such as adding conditions.
    • For example instead of creating a Boolean Conditional Interface we can use a Predicate Interface which comes out of box and provides functionality of handling Boolean conditions.
      • The Predicate Interface provides an abstract method called as test(T t) which does the same work as our defined method i.e. returns Boolean.
    • Let us try using the Predicate Interface instead of Condition Interface.
      • Since predicate uses a generic type we have to define the type and we do it as follows.
        • private static void printConditionally(List<Employee> employees,Predicate<Employee> condition)
        • So we do not have to define an interface for our conditions separately.
      • Another Interface is Supplier which does not take any input but returns an object of Generic type.
      • A function Interface which has 2 generic parameters takes a generic input and returns another generic output using apply(T t) method.
    Functional Repository.java
     import java.util.Arrays;  
     import java.util.List;  
     import java.util.function.Predicate;  
     /**  
      *  
      * @author Gaurav Matta  
      */  
     public class FunctionalRepository {  
       public static void main(String args[])  
       {  
         List<Employee> employees=Arrays.asList(  
             new Employee("Gaurav","Matta",31),  
             new Employee("Vivek","Sharma",28),  
             new Employee("Michel","Lee",27),  
             new Employee("Steve","Warne",45),  
             new Employee("Ricky","Gichrist",40),   
             new Employee("Greg","Right",35)   
         );  
         System.out.println("Printing All Employees:");  
         printConditionally(employees,e->true);  
         System.out.println("Printing first name with G using Lambda:");  
         printConditionally(employees,e->e.getFirst_name().startsWith("G"));  
         System.out.println("Printing first name with M using Lambda:");  
         printConditionally(employees,e->e.getFirst_name().startsWith("M"));  
       }  
       private static void printConditionally(List<Employee> employees, Predicate<Employee> condition) {  
         for(Employee e : employees)  
         {  
           if(condition.test(e))  
           {    
             System.out.println(e);  
           }  
         }  
       }  
     }  
    
    Employee.java
     public class Employee {  
       private String first_name;  
       private String last_name;  
       private int age;  
       public Employee(String first_name, String last_name, int age) {  
         super();  
         this.first_name = first_name;  
         this.last_name = last_name;  
         this.age = age;  
       }  
       public String getFirst_name() {  
         return first_name;  
       }  
       public void setFirst_name(String first_name) {  
         this.first_name = first_name;  
       }  
       public String getLast_name() {  
         return last_name;  
       }  
       public void setLast_name(String last_name) {  
         this.last_name = last_name;  
       }  
       public int getAge() {  
         return age;  
       }  
       public void setAge(int age) {  
         this.age = age;  
       }  
       @Override  
       public String toString() {  
         return "Employee{" + "first_name=" + first_name + ", last_name=" + last_name + ", age=" + age + '}';  
       }  
     }  
    

    Using Functional Interface as Arguments
    • Now since we have been directly using functional interfaces let us explore another way of using them this time as arguments.
    • We will check the "filter()" method of the Stream Interface here.
      • Now since Stream has more than one abstract methods we cannot use it as functional interface.
      • Instead we can define a method in it which can take a Functional Interface as an argument.This method will serve us in exactly same manner.
      • Let's look at the filter() function of Stream which has following declaration
        • Stream<T> filter(Predicate<? Super T > predicate);
      • This function takes Predicate interface as argument which is a functional interface.
        • So we can also filter our employees as
          •  List<Employee> el = employees.stream().filter(e-> e.getFirst_name().startsWith("S")).collect(Collectors.toList());  
            
          • This will save our filtered result in new List.
    Update in Main Method(FunctionalRepository.java)
       System.out.println("Printing all have first name with S using Stream Interface:");  
       List<Employee> el = employees.stream().filter(e-> e.getFirst_name().startsWith("S")).collect(Collectors.toList());  
           printConditionally(el,e->true);    
      

      Selection of Function in Package Java.util.function
      • Let's try an operation of Division on array of Integers using Lambda Functions
      • Now we have two ways of performing this operation
        • First way is to loop through an array of Integers in the main method and call lambda expression to perform operation on each integer which will return an int.
          • For this we can use the "BiFunction" interface which will accept 3 parameter Types perform operation on first two return result as third type.
          • We can use following method which takes Interface as argument
            •    private static Integer operateReturnInt(int number,int key,BiFunction<Integer,Integer,Integer> function)  
                 {  
                   return function.apply(number, key);  
                 }  
              
          • We will use following code to call method in main method
            •      int[] arrayNumbers={1,2,3,4,0,5,6};  
                   int key=2;  
                   for(int a : arrayNumbers)  
                   {  
                     System.out.print(a+"==>");  
                     Integer i = operateReturnInt(a,key,(a1,a2)->(a1/a2));  
                     System.out.println(i);  
                   }  
              
        • Second way in which we can perform this is to use pass entire array and perform operation in Class Method Print the result. In this case we will return void.
          • For this we can use the "BiConsumer" Interface since it accepts 2 parameters and returns a Void which suits our requirement.
          • We will define our class method as follows
            •    private static void operateNoReturn(int[] array,int key,BiConsumer<Integer,Integer> consumer)  
                 {  
                   for(int a:array)  
                   {  
                     consumer.accept(a,key);  
                   }  
                 }  
              
          • We will call this method as follows using lambda expression
            •      System.out.println("Division using Using BiConsumer");  
                   operateNoReturn(arrayNumbers,key,(a1,a2)->{  
                     System.out.print(a1+"=>");  
                     System.out.println(a1/a2);  
                   });  
              
      • So we see how we can select the Function Interface in concern depending upon our needs.
      Exception Handling In Lambdas
      • Let us see a few ways in which we can handle exceptions.
      • Taking our example of Integer Division from previous section we will test using a very common exception i.e. Division by zero.
      • Let us take method "operateNoReturn" in this case.
      • Let us also change key to zero so that we can force divide by zero exception
      • Now a big question arises which is where we should handle this exception.
      • We have two areas here
        • First in class Method where the method of the functional interface is called.
          • We will handle exception where we are calling Interface method.
            • In this case we will be using try catch block on the "consumer.accept()" method which is failing.
            • One issue in this is that inside the class method i.e. "operateNoReturn()" we don't know what operation is going to be performed.
            • The method "accept()" is still abstract so we will have to catch each and every Exception which may or may not even occur.
            • This method will have a long list of catch blocks.
            • This looks very inappropriate.
          • Example of this method is
            •    private static void operateNoReturn(int[] array,int key,BiConsumer<Integer,Integer> consumer)  
                 {  
                   for(int a:array)  
                   {  
                     try  
                     {  
                       consumer.accept(a,key);  
                     }  
                     catch(ArithmeticException e)  
                     {  
                       System.out.println("Arithmetic Exception cought in Class Method");  
                     }  
                   }  
                 }  
              
        • Second in main method where the Functional interface method is Implemented.
          • We will handle the exception where our division expression in lambda function is written.
          • We can see this when we are handling exception inside implementation of "operateReturnInt()".
          • This has messed up our lambda expression which was a single line of code earlier.
          • Example of this is
            •        int i = operateReturnInt(a,key,(a1,a2)->{  
                             try  
                             {  
                               return (a1/a2);  
                             }  
                             catch(ArithmeticException e)  
                             {  
                               System.out.println("Arithmetic Exception cought in Main Method");  
                               return 0;  
                             }  
              
              });
        • There is another cleaner way which is using Wrapper Lambda Function
          • Let's remove the try catch block from the implementation of apply function so that it looks clean as it was original.
            •  int i = operateReturnInt(a, key,(a1,a2)->(a1/a2));  
              
          • Now we will Externalize this try catch operation in a separate wrapper function.
          • Let's create an external function called as wrapperLambda which returns a type of BiFunction Interface and takes in the same as arguments as well.
            •    private static BiFunction<Integer,Integer,Integer> wrapperLambda(BiFunction<Integer,Integer,Integer> function)  
                 {  
                   return function;   
                 }  
              
          • Now we can pass the "wrapperLambda" as a last argument in our function "operateReturnInt" which will work perfectly as it is also returning a type of BiFunction Interface.
            •  int i = operateReturnInt(a, key, wrapperLambda((a1,a2)->(a1/a2)));  
              
          • Let's make further modification to this function and return another Lambda expression from this.
            •    private static BiFunction<Integer,Integer,Integer> wrapperLambda(BiFunction<Integer,Integer,Integer> function)  
                 {   
                   return (a1,a2)->(a1+a2);  
                 }  
              
          • What this will do is it will override our previously passed lambda expression.
          • And since this lambda expression is adding 2 numbers when we execute we will see that this adds two numbers.
          • If we can return a lambda expression with operation we can change the operation with function.apply(a1,a2) since it also performs the same task.
          • Since function.apply() also returns an Integer Expression as required by BiFunction().
          • So this wrapper function will return a lambda expression which will override our previous expression.
          • We can perform various tasks in this function which we added as a wrapper.
          • We can also implement a try catch statement in this function on original expression which will ensure that our implementation remains clean.
          • This is a kind of a generic wrapper for all operations.Since it performs the same operation as specified in implementation with some additional functionality.
          • This does not disturbs our functional implementation of Lambda function.
          •    private static BiFunction<Integer,Integer,Integer> wrapperLambda(BiFunction<Integer,Integer,Integer> function)  
               {   
                 try  
                 {  
                   return (a1,a2)->function.apply(a1,a2);  
                 }  
                 catch(ArithmeticException e)  
                 {  
                   System.out.println("Exception caught in Wrapper Lambda Function");  
                 }  
                 return null;  
               }  
            
      Closures in Lambda Expressions
      • Closures help in passing functions from one place to another in code as values.
      • Let us create a new code class called as "ClosuresExample".
      • Create an interface "Process" which has a method "process()" which returns void and takes int as parameter.
      • Next we create a method "doProcess()" inside our class which takes in an integer and instance of Process interface as arguments and calls process method on integer.
      • Next we call "doProcess()" method in main method of this class where we pass parameters as random integer and implementation of Process interface "process()" method.
      • We initialize another variable in main method and use it in implementations of process method.
        •      doProcess(a, new Process(){  
                 @Override  
                 public void process(int i) {  
                   System.out.println(i+b);  
                 }  
               });  
          
        • When we execute the program runs successfully.
          • But "b" is defined and initialized in main method and is being used in "doProcess()" method which is defined in an anonymous class.How is this possible that we are able to use "b" out of scope of main?
          • To answer this  we can say Compiler is treating "b" by default as final variable and is trusting us that we won't change it's value.In other words compiler is keeping track of the value of "b".
          • Now if we try to override b inside "process()" implementation we get an error as follows.
            •  "Local variable defined in an enclosing scope must be final or effectively final".
            • What this means is Java compiler is treating our local variable as effectively final which means even if we don't specify final keyword it assumes it as final.
      • Now let us try this scenario on a lambda expression.
        • In a lambda expression we have a functional expression implemented as a function.
        • This expression uses a value from its closure i.e. it's initialization. This value "b" here is freezed to the expression.
        • So the last value of "b" is freezed to the expression by Java Compiler at runtime where "b" is a local variable of main scope.
        • Whenever we execute this lambda expression it will take "b" on its last value before implementation.
        • So whenever we will execute this function even after changing value of "b" it will take "b" on it's last value before implementation.
      ClosuresExample.java
         public class ClosuresExample {  
           public static void main(String args[])  
           {  
             int a=10;  
             int b=20;  
             doProcess(a, new Process(){  
               @Override  
               public void process(int i) {  
                 System.out.println(i+b);  
               }  
             });  
             doProcess(a,i->System.out.println(i+b));  
           }  
           public static void doProcess(int i,Process p)  
           {  
             p.process(i);  
           }  
         }  
        Process.java
         public interface Process {  
           void process(int i);  
         }  
        

        This reference in Lambda Functions
        • "This" keyword refers to the instance of the object that is being currently used.
        • This reference behaves differently in an inner class implementation and in a lambda implementation.
        • Let us create a new class called as ThisReferenceExample.
          • Let us create a method in this class called as doProcess().
          • This method will take 2 parameters Integer and a Type of Process interface.
          • This method will execute process() method of interface.
        • We will be implementing the Process Interface in 2 ways via lambda expression and via anonymous class implementation of Process Interface.
        • We will try to print this keyword in both these ways i.e. using Functional implementation and Class Implementation.
        • Now when we print this from the implementation of anonymous inner class we see that it prints the object of implemented anonymous inner class.
          • We can also override "toString()" method in this class and check the result.
        • Let us try using the Lambda Expression now
          • Let us now create a lambda expression for the implementation of Process interface
          • When we try to use this reference inside this lambda expression we get an error.
            • non-static variable this cannot be referenced from a static context
          • This is because instance of lambda expression does not change the value of this reference and since we cannot call non static members in static methods.It is not allowing us to use this reference.
          • Let us try printing this using lambda expression in a non static function called as "execute()".
            • We see that we are able to use "this" here.
            • But it prints the object of container class of execute() method.
              • We can check this using toString() method in ThisReferenceExample class.
          • So a lambda function does not change the value of this reference.
          • The value of this reference remains the same as it was outside the expression.
        ThisReferenceExample.java
           public class ThisReferenceExample {  
             public static void main(String args[])  
             {  
               ThisReferenceExample thisReferenceExample = new ThisReferenceExample();  
               thisReferenceExample.doProcess(10, new Process(){  
                 @Override  
                 public void process(int i) {  
                   System.out.println(i);  
                   System.out.println(this);  
                 }  
                 @Override  
                 public String toString() {  
                   return "This is an anonymous inner class";  
                 }  
               });  
               thisReferenceExample.doProcess(10,i -> {  
                 System.out.println("Value of i from lambda is "+i);  
           //      System.out.println(this); //Lambda Expressions do not alter this reference so this is invalid  
               });  
               thisReferenceExample.execute();  
             }  
             @Override  
             public String toString() {  
               return "ThisReferenceExample{" + '}';  
             }  
             public void execute()  
             {  
               System.out.println(this);  
               doProcess(10,i->{  
                 System.out.println("Value of i from lambda inside execute is "+i);  
                 System.out.println(this);  
               });  
             }  
             public void doProcess(int i,Process p)  
             {  
               p.process(i);  
             }  
           }  
          

          Method References
          • Method Reference is an alternate way for writing lambdas.
          • Let's create a new class class called as "MethodReferenceExample"  and create a static method which prints string as output.
          • Let's now create a thread using Lambda which will run this method.
          • One way of executing Lambda Expression is 
            •  Thread t1= new Thread(()->printMessage());  
              
          • We can replace this Lambda function directly with a static Method as follows
            •  Thread t2= new Thread(MethodReferenceExample::printMessage);  
              
          • We can use a method directly if its parameters match with lambda function implementing functional interface.This is called as method reference.
          • Here we have a Runnable interface which is implemented by a function
          • A Runnable interface accepts zero arguments.Let's try with "Consumer Interface" which accepts one argument.
          • We take example of the Employee<List> used before and try printing it.
          • We will add another static function to class which will take three parameters checks for condition using Predicate method and if true then will call accept method of Consumer.The three parameters will be
            • List of Employees
            • Type of Predicate Interface with Employee Type arguments.
            • Type of Consumer Interface with Employee Type arguments.
          • Now one way we used earlier to print was
            •  printConditionally(employees,e->true,e->System.out.println(e));  
              
          • If we have to pass a Method reference we will use
            •  printConditionally(employees,e->true,System.out::println);  
              
            • Here println is a static method which matches exact parameters as accept function of Consumer.
          • In this way we can further reduce our code by passing method reference.
          MethodReferenceExample.java
           public class MethodReferenceExample {  
             public static void main(String args[])  
             {  
               List<Employee> employees=Arrays.asList(  
                   new Employee("Gaurav","Matta",31),  
                   new Employee("Vivek","Sharma",28),  
                   new Employee("Michel","Lee",27),  
                   new Employee("Steve","Warne",45),  
                   new Employee("Ricky","Gichrist",40),   
                   new Employee("Greg","Right",35),  
                   new Employee("Sachin","Kumble",32)  
               );  
               System.out.println("Printing All Employees:");  
               printConditionally(employees,e->true,e->System.out.println(e));  
               printConditionally(employees,e->true,System.out::println);  
               printMessage();  
               Thread t1= new Thread(()->printMessage());  
               Thread t2= new Thread(MethodReferenceExample::printMessage);  
               t1.start();  
               t2.start();  
             }  
             private static void printConditionally(List<Employee> employees, Predicate<Employee> condition,Consumer<Employee> consumer) {  
               for(Employee e : employees)  
               {  
                 if(condition.test(e))  
                 {    
                   consumer.accept(e);  
                 }  
               }  
             }  
             public static void printMessage()  
             {  
               System.out.println("Hello");  
             }  
           }  
          

          Streams
          • Streams in java 8 re used to loop over collections using lambda expressions.
          • Let us create a class called as "StreamClass" with main method.
          • We will again use the "EmployeeList" here which we used earlier.
          • Now let us see different ways in which we can loop through list.
            • In Java 7 we used to loop using for loop.
              • Iterate over complete list using an index and print the element which pointer points too.
              •      for(int i=0;i<employees.size();i++)  
                     {  
                       System.out.println(employees.get(i));  
                     }  
                
            • Another way was using For Each/For In loop.
              •      for(Employee e:employees)  
                     {  
                       System.out.println(e);  
                     }  
                
          • Both of the above loops are called as external iterators since we are controlling the iteration.
          • Now let us look at some internal iterators defined in Java 8.
            • In Java 8 Every Collection has a new method called as for each
              • For Each takes in a "Consumer Interface" as parameter.
              • This means that we can pass a lambda expression using each element in for each which returns a void.
              •  employees.forEach(e->System.out.println(e));  
                
            • We can further simplify this using method reference as follows.
              •  employees.forEach(System.out::println);  
                
            • Advantage of Internal iterations is that it makes it very easy for the processor to run in multiple threads.
            • Secondly External iterators are sequential but Internal iterators do not care about the order they just run expression for each and every element of the collection.
              • This helps in running iteration of a loop parallel in multi-threaded environment.
              • Another operation can run parallel with the loop.
          StreamClass.java
           public class StreamClass {  
             public static void main(String args[])  
             {  
               List<Employee> employees=Arrays.asList(  
                   new Employee("Gaurav","Matta",31),  
                   new Employee("Vivek","Sharma",28),  
                   new Employee("Michel","Lee",27),  
                   new Employee("Steve","Warne",45),  
                   new Employee("Ricky","Gichrist",40),   
                   new Employee("Greg","Right",35),  
                   new Employee("Sachin","Kumble",32)  
               );  
               System.out.println("Using For Loop");  
               for(int i=0;i<employees.size();i++)  
               {  
                 System.out.println(employees.get(i));  
               }  
               System.out.println("Using For In/FOr Each Loop");  
               for(Employee e:employees)  
               {  
                 System.out.println(e);  
               }  
               System.out.println("Using Lambda For Each Loop");  
               employees.forEach(p->System.out.println(p));  
               System.out.println("Using Lambda For Each Loop using Method Reference");  
               employees.forEach(System.out::println);  
             }  
           }  
          

          Streams
          • A stream is a sequence of elements supporting sequential and parallel aggregate operations.
          • A stream helps in multi-threading as threads  can work simultaneously over different elements.
          • We do not need to iterate over the sequence again and again for every operation this speeds up the operations on the stream.
          • We will proceed further on our "forEach" example.
            • We will get a list of employees whose first name starts with "G".
            • Every collection has a stream method.
            • The stream object which is returned from above method has many functionalities that can be used with lambda expression.
            • We will use "forEach()" method here to loop through the objects and print firstName.
            • Next we will perform another operation to print only those names which start with "G".
              • We will use filter method for this.
              • Filter method takes in a predicate which is a conditional functional Interface.
            • So when we add filter before forEach we find that only those elements which match the condition will be processed under forEach.
          • We find that Stream is like a Conveyor Belt on which multiple operations can be performed simultaneously on each object.
          • Each operation can filter out an object before proceeding for next element.
          Addition to main method of StreamClass.java
               employees.stream()  
                   .filter(e->{  
                     System.out.print(e.getLast_name()+" ");  
                     return e.getFirst_name().startsWith("G");  
                       })  
                   .forEach(e->System.out.println(e.getFirst_name()));  
          • Stream is like a new view of the collection.
          • A Stream always has a source.
          • A Stream contains the operations that need to be performed on elements.
          • A Stream has a terminal operation that performs an action.
          • In the above case we have a terminal operation that prints the first name.
          • A stream does not starts if the terminal operation is not present.
            •  Stream<Employee> emp=employees.stream();  
              
          • Even if we add a non terminal operation like filter stream will not work.
            •  Stream<Employee> emp=employees.stream().filter(e->e.getFirst_name().startsWith("G"));  
              
          • If we add a terminal operation like "forEach()" or "Count()" it will run
            • Let's use count this time which provides a count of elements in the Stream.
            •  long empCount=employees.stream().filter(e->e.getFirst_name().startsWith("G")).count();  
              
            • This will return a count of filtered elements.
          • We will also find that we cannot add any further operation after a terminal operation.
          • Using Streams Lambda Expressions enable parallel processing.
            • Stream is an internal iteration which means it is being performed by the Runtime Environment.
            • Different portions of the collection can be handled by different processors or different cores of the same processor.
            • We can have different assembly lines for different set of elements.
            • For parallel processing we can use a method called as "parallelStream()".
            • This method returns parallel stream if JRE feels that multiple cores can make it run faster.
            •  long empCount=employees.parallelStream().filter(e->e.getFirst_name().startsWith("G")).count();  
              
          • s

            No comments:

            Post a Comment