Java Functional Programming : 3. The java.util.function Library

This is the multi part series on Java Functional Programming


3. The java.util.function Library


In most cases you might not need to declare your own functional interface as Java gives you a standard set of interfaces to save your time. In case you’d like to work with a function of two parameters of the same type, you could probably use the interface java.util.function.BinaryOperator<T> for the same activity. (More on the package java.util.function is to follow). As a shortcut, you’d probably prefer to keep things simple without the need of an extra interface declaration. Lets look at the constructs below:



       BinaryOperator<Integer> binAdd = (a, b) -> a + b;
       System.out.println("Bin Add 2, 3 = " + binAdd.apply(2, 3));


The lambda expressions can be passed as a parameter into the methods. Let us define a method which doubles the value given by the lambda expression.


       private static int doubleVal(int a, int b, IntMathOperation op){
              return op.operation(a, b) * 2;
       }


You could pass the expression directly in the parameter and they would give back the output based upon the operation you passed. For example the code below


System.out.println("Double Val Add (3, 2) = " + doubleVal(3, 2, (a, b) -> a + b));
System.out.println("Double Val Sub (3, 2) = " + doubleVal(3, 2, (a, b) -> a - b));
System.out.println("Double Val Mul (3, 2) = " + doubleVal(3, 2, (a, b) -> a * b));


would give the output as:
Double Val Add (3, 2) = 10
Double Val Sub (3, 2) = 2
Double Val Mul (3, 2) = 12

Not only the input parameters, the lambda expressions can also be returned back as the output of a method in the form of a functional interface. Have a look at the code below. The method doubleOp (the method is made static so that we don’t have to initialize the class) returns a functional interface as an output. The method takes in a functional interface as a parameter and doubles the value and passes back as a functional interface.


       @FunctionalInterface
       public interface MyOperation {         
              public int op(int i, int j);           
       }
      
       public static MyOperation doubleOp(MyOperation mo){
              return (a, b) -> mo.op(a, b) * 2;
       }
      
       public static void main(String... args) {
              System.out.println("------ Printing Double Operation  -----");
              MyOperation mo = doubleOp((a, b) -> a + b);
              System.out.println(mo.op(6, 7));
              System.out.println(doubleOp((a, b) -> a + b).op(6, 7));          
       }


Running the above program yields the output
------ Printing Double Operation  -----
26
26

We can take the output of this method and treat is exactly like a functional interface and pass in the values to apply to the method. The last line of the program shows that we can also pass in the lambda function and the values directly into the parameter. A little more complex program is mentioned below. The code is self explanatory. This way you can chain multiple operations (or expressions).


       @FunctionalInterface
       public interface MyOperation {         
              public int op(int i, int j);           
       }
             
       public static MyOperation multOps(MyOperation mo1, MyOperation mo2){          
              return (a, b) -> mo1.op(a, b) * mo2.op(a, b);
       }
      
       public static void main(String... args) {
             
              System.out.println("----- Printing Multiple of Operations ------");
              MyOperation op1 = (a, b) -> a - b;
              MyOperation op2 = (a, b) -> a * b;
              MyOperation mulOp = multOps(op1, op2);
             
              System.out.println(mulOp.op(6, 4));
              System.out.println(multOps((a, b) -> a - b, (a, b) -> a * b).op(6, 4));
       }
      

The output of this program would be
----- Printing Multiple of Operations ------
48
48


Now that we have covered the basics let us now try to understand the many functional interfaces in the package java.util.function. We have seen the interface java.util.function.BinaryOperator<T> earlier and let us introduce the family here. As we have seen, since the body is not defined, a lot of functional interfaces could be reused. This package defines those standard set of interfaces which can be reused without worrying to create your own. These are the ones which are used by the standard java library set, so it makes sense to get acquainted with the package. This package has four categories of functional interfaces and many variants of each of them. We will study the group first and try to understand them. This will make it easy to remember and task simpler, going forward. There are four groups of functional interfaces in the package – the predicates, the consumers, the suppliers and the functions. It’s easy to remember. Memorize the following phrase – “Suppliers get something which Consumers accept, test it by Predicates and apply Function to it”. That’s it. Apart from the four categories the phrase also contains the functionality each group is expected to perform. Suppliers are supposed to get us something, Consumers accept, Predicates must be used to test it and Functions should be applied.  




These functional interfaces are typed, so that they can be used with any class type. The supplier, consumer and predicate are defined with only one type of class, but the function needs two of them. The Suppliers has the method get which takes in no parameters but returns the object of the type, Consumers have a method accept which takes in the object of the type but do not return anything. The Predicates have a method test which returns a boolean value and take in the object of the type as input, and the Functions have the method apply which take in one type and return another (remember the above phrase). If did not get it now don’t worry. We will go through the four major functional interfaces and see their usage. Then you can come back and recite the phrase again. Let us have class “Chair”, which has come attributes (Described below). We will use the same class to describe each category of the functional interface.


public class Chair {
      
       private String brand;
       private int price;
       private boolean withCushion;
      
       private Chair(String brand, int price, boolean withCushion){
              super();
              this.brand = brand;
              this.price = price;
              this.withCushion = withCushion;
              System.out.println("Chair Created ... !!");
       }
      
       /**
        * Returns an immutable object chair.
        */
       public static Chair createChair(String brand, int price, boolean withCushion){
              return new Chair(brand, price, withCushion);
       }
      
       public final String getBrand(){
              return brand;
       }
      
       public final int getPrice() {
              return price;
       }

       public final boolean isWithCushion() {
              return withCushion;
       }
      
       @Override
       public final String toString(){
              return "brand=" + brand + ", price=$" + price + ".00, With Cushion?=" +
                 withCushion;
       }

       @Override
       public boolean equals(Object chair2){
              if (!(chair2 instanceof Chair))
                     return false;
             
              Chair ch2 = (Chair) chair2;            
              return this.price == ch2.price && this.withCushion == ch2.withCushion;
       }
}


Remember what each functional interface does and refer to the code below

             
// supplier creates a new chair 
Supplier<Chair> createChair  = ( ) -> Chair.createChair("Java Chair", 200, true);
// consumer does an operation on the chair
Consumer<Chair> printDetails = (c) -> System.out.println("Printing Details => " + c);
// checks some parameter of the chair
Predicate<Chair> isCushioned = (c) -> c.isWithCushion();
// gets the wholesale price of a chair
// wholesale price is 80% of the price rounded to an integer
Function<Chair, Integer> wholeSalePrcie = (c) -> Math.round(0.8f * c.getPrice());
             
             
// using supplier
Chair c = createChair.get();
// using consumer
printDetails.accept(c);
// using predicate
boolean isWithCushion = isCushioned.test(c);
System.out.println("Is With Cushion=" + isWithCushion);
// using function
int wcPrice = wholeSalePrcie.apply(c);
System.out.println("Whole Sale Price=$" + wcPrice + ".00");


  1. The ‘Supplier’ functional interface has a method (get) which returns a type Chair, but does not take in any parameter. Usually this type of functional interface would be used for creation of an object. In most applications this would be the object initialized from values taken from a data repository (such as XML, file system, database, etc)
  2.  The ‘Consumer’ functional interface has a method (accept) which takes in a Chair object, and performs some operation on it. This method does not return any value, so it can be used for data persistence or an external system call or any other side-effect. In this case it simply prints the object Chair.
  3. The ‘Predicate’ functional interface has a method (test) which takes in the chair object and tests something chair. It can be used to filter objects as well. In this example it will simply check a property of Chair.
  4.  The ‘Function’ functional interface has a method (apply) which takes in the object Chair and applies some operation to it and returns the object of a different type. In the example it returns an integer, which is the wholesale price.  
The above code execution prints the following, on the console:

Chair Created ... !!
Printing Details => brand=Java Chair, price=$200.00, With Cushion?=true
Is With Cushion=true
Whole Sale Price=$160.00

Now that we have a fairly good idea of the types and what each one does, let us have a look at each of these four functional interfaces in detail. Yes, there are more to it than just the abstract methods in the interface. Those interfaces come with default and static methods as well. The Supplier interface has just the abstract method so it warrants no other discussion. Let’s discuss others in the coming topics.


<< Prev (Lambda Expressions & Java Functional Interfaces) | Next (Consumer) >>

Comments

Popular posts from this blog

Java Generics - 7. Upper and Lower Bounds

Java Generics - 3. Multiple bounding