Google

May 26, 2014

Learning to write functional programming with Java 8 with examples -- part 1

In the earlier post entitled  Understanding Java 8 Lambda expressions with working examples -- part 1, we looked at Summable example to illustrate functional programming. In this tutorial, I will go in more detail with similar examples with scenarios. The example is a a simple arithmetic operation.

Scenario 1:  The Operate interface with the annotation @FunctionalInterface. This annotation ensures that you can only have a single abstract method. You can have additional default and static method implementations.

Step 1: Define the interface. It is an abstract method by default, and  @FunctionalInterface ensures that you can only define a Single Abstract Method (aka SAM).


package com.java8.examples;

@FunctionalInterface
public interface Operation {
    int operate(int operand1, int operand2); 
}

Step 2: The OperationTest class that uses the functional interface with lambda expressions like (x, y) -> (x + y); and  (x, y) -> (x * y) to add and multiply respectively. The method name operate(int operand1, int operand2) is not explictly called as the compiler knows to work it out from the parameters passed int operand1, int operand2 which method to invoke.

package com.java8.examples;

public class OperationTest {

 public static void main(String[] args) {
   OperationTest test = new OperationTest();
   System.out.println("add result = " + test.add(2, 3));
   System.out.println("multiply result = " + test.multiply(2, 3));
 }

 public int add( int input1,  int input2) {
   Operation adder = (x, y) -> (x + y);
   return adder.operate(input1, input2);
 }  
 
 public int multiply( int input1,  int input2) {
   Operation multiplier = (x, y) -> (x * y);
   return multiplier.operate(input1, input2);
 }  
}

Output:

add result = 5
multiply result = 6

The above example will give the same output even without the @FunctionalInterface annotation. You could even have "abstract" in front of  int operate(int operand1, int operand2);

package com.java8.examples;

public interface Operation {
 abstract int operate(int operand1, int operand2);
}



Scenario 2: Have another Operation2 interface that takes 3 integer parameters.


package com.java8.examples;

public interface Operation2 {
 abstract int operate(int operand1, int operand2, int operand3); 
}


The OperationTest class that uses both interfaces. The Operation that take 2 integer parameters and Operation2 that takes 3 integer parameters.

package com.java8.examples;

public class OperationTest {

 public static void main(String[] args) {
   OperationTest test = new OperationTest();
   System.out.println("add result = " + test.add(2, 3));
   System.out.println("multiply result = " + test.multiply(2, 3));
   System.out.println("add 3 numbers result = " + test.add2(2, 3, 4));
 }

 public int add( int input1,  int input2) {
   Operation adder = (x, y)  -> (x + y);
   return adder.operate(input1, input2);
 }  
 
 
 public int multiply( int input1,  int input2) {
   Operation subtracter =  (x, y) -> (x * y);
   return subtracter.operate(input1, input2);
 } 
 
 public int add2( int input1,  int input2, int input3) {
   Operation2 adder = (x, y, z)  -> (x + y + z);
   return adder.operate(input1, input2, input3);
 }  
 
}


Output:

add result = 5
multiply result = 6
add 3 numbers result = 9



Scenario 3: Why do you have default methods in Java 8 interfaces?

This example demonstrates that default methods provide default behaviors, and closures are used to have  special behaviors like add, multiply, divide, subtract, print, etc.

Step 1: Here is the revised Operation interface with default methods.

package com.java8.examples;

import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.Objects;

@FunctionalInterface
public interface Operation<Intetger>  {
 
    //SAM -- Single Abstract Method.
    //identifier abstract is optional
    Integer operate(Integer operand);
 
    default Operation<Integer> add(Integer o){
       return (o1) -> operate(o1) +   o;
    }
 
    default Operation<Integer> multiply(Integer o){
      return (o1) -> operate(o1) * o;
    }
 
    //define other default methods for divide, subtract, etc
 
    default Integer getResult() {
    return operate(0);
    }
    
 default void print(){
  System.out.println("result is = " + getResult());
 }
   
}





Step 2: The revised OperationTest class.

package com.java8.examples;

import static java.lang.System.out;

public class OperationTest {
 
 public static void main(String[] args) {
 
  Operation<Integer> calc = (x) -> (2);
  
  Operation complexOp = calc.add(3)
         .multiply(4)
         .multiply(2)
         .multiply(2)
         .add(4);
  
  complexOp.print();
 
  int result = complexOp.getResult();
 } 
 
}




Output:

result is = 84


We know that Java does not support multiple implementation inheritance to solve the diamond problem (till Java 8). Java did only support multiple interface inheritance. That is, a class can implement multiple interfaces. By having default method implementations in interfaces, you can now have multiple behavioral inheritance in Java 8.  Partially solving the diamond problem.


Scenario 4: Why do you have static methods in Java 8 interfaces?

The static methods are helper methods. Prior to Java 8, the Java APIs used to have separate interfaces and utility methods. For example, Collection interface and Collections utility class, Path interface and Paths utility class, etc.

So, instead of doing Collections.sort(list, ordering), in Java 8 APIs you could do list.sort(ordering);

Secondly, static helper methods are more expressive with meaningful names like plus5, plus10, etc as shown below.

package com.java8.examples;

import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.Objects;

@FunctionalInterface
public interface Operation<Intetger>  {
 
 //SAM -- Single Abstract Method.
 //identifier abstract is optional
 Integer operate(Integer operand);
 
    default Operation<Integer> add(Integer o){
       return (o1) -> operate(o1) +   o;
    }
 
    default Operation<Integer> multiply(Integer o){
      return (o1) -> operate(o1) * o;
    }
 
    //define other default methods for divide, subtract, etc
 
    default Integer getResult() {
    return operate(0);
    }
    
 default void print(){
  System.out.println("result is = " + getResult());
 }
   
 
 //ads 5 to a given number
 static Integer plus5(Integer input) {
  return input + 5 ;
 }
}


Now, how to invoke the static method via Lambda expressions:

package com.java8.examples;

import static java.lang.System.out;

public class OperationTest {
 
 public static void main(String[] args) {
 
  //plus5 is an expressive static helper method that adds 5 to a given number
  Operation<Integer> calc = (x) -> Operation.plus5(2);
  
  Operation complexOp = calc.add(3)
         .multiply(4)
         .multiply(2)
         .multiply(2)
         .add(4);
  
  complexOp.print();
 
  int result = complexOp.getResult();
  
 } 
}


Output:

result is = 164


Learning to write functional programming with Java 8 with examples -- part 2

Labels: ,

0 Comments:

Post a Comment

Subscribe to Post Comments [Atom]

<< Home