Java

Be more Functional: Java's Functional Interfaces

 0

 12

By Imran.shaikh

On Mar 21, 2020 at 08:48 am

  

Introduction

Hey! tea lovers. This post is about the functional interfaces provided by Java. We will talk about the basic ones briefly. These functional interfaces are used by Stream API heavily, so knowing them will make your life easier. Not just streams you can use it anywhere actually, unless you want to.

 

Necessities before diving in

Not much, Just make sure the concept of functional interfaces and lambdas are clear. You can find the code on github here or the full project here. And yes fill your cup of tea, start sipping and learn in your tea break.

 

java.util.function

The functional interfaces we will be discussing in your tea break will be java.util.function. This package contains Java’s general-purpose functional interface to be used by coders like us. This helps us to save time by not writing it repeatedly. It has many functional interfaces (you probably won’t be needing that many in your code) but we are going to see the most common and useful functional interfaces. We will be comparing it to the normal method or function for better comprehension

 

Consumer < T >

As the name implies Consumer consumes the inputs. It doesn’t return anything when called. It is similar to function with the void return type. This funtional interface has 2 methods one being the void accept(T t) to trigger it and other is default method andThen(Consumer<T>) which let you chain multiple Consumers. You can call multiple Consumers with andThen(consumer1).andThen(consumer2)..... and call accept(t) function to terminate the chain. All Consumers in this chaining will use the same input given via accept().

/*define the consumer which consumes the age to print it*/
Consumer<Integer> printAgeConsumer = new Consumer<Integer>() {
@Override
public void accept(Integer age) { System.out.println("Age is " + age); }
};
/*call the method*/
printAgeConsumer.accept(23);/* Age is 23 */
/*using lambda */
Consumer<Integer> printAgeWithLamda = (age) -> System.out.println("Lamdda : age is " + age);
/*will work similar as printAgeConsumer */
printAgeWithLamda.accept(223);/*Lamda : age is 223 */
/*chaining with andThen(Consumer) */
printAgeConsumer /*1st */
.andThen(printAgeWithLamda) /*2nd */
.andThen(age -> System.out.println("How old is he ? " + age)) /*3rd */
.accept(23); /*this value will be given to each consumer */
Predicate < T >

The Predicate is used for boolean values. It tests the given value and returns a boolean by calling test(T t). Then again like Consumer, you can use chaining using and(anotherPredicate). And calling test(T) in the end will complete the chain. Similar to Consumer they use the same input throughout the chain. It returns true only if all the predicates returns true else it returns false.

Predicate<Integer> isEven = new Predicate<Integer>() {
@Override
public boolean test(Integer number) { return number % 2 == 0;}
};
boolean is23Even = isEven.test(23);/*false */
/*using lambda */
Predicate<Integer> isEvenlambda = (number) -> number % 2 == 0;
boolean is46Even = isEvenlambda.test(46); /*true */
/* chaining with and()
returns true only of all the predicated returns true else false */
isEven /* 1st - number should be even */
.and(n -> n < 10)/* 2nd -number should be less than 10 */
.and(n -> n > 5) /* 3rd should be greater than 5 */
.test(6); /* this nu,ber will be used in chain */
/* returns true after completion */

 

Function < T, R >

Yes, that’s the name. Unlike preceding interfaces, Function has two generic parameters. The first parameter is for input type and the second one is for the return type. Essentially Function is used for transforming the given value into the desired one. Functions use apply(T t) for its functions and andThen(anotherFuntion) for chaining. But dissimilar to Consumer and Predicate this chaining does not use the same value to all the Functions instead, they use the result of the preceding Function. But keep in mind that, the subsequent Function has the input type equivalent to the current Function’s return type.

Function<Integer, Integer> doubleTheNumber = new Function<Integer, Integer>() {
@Override
public Integer apply(Integer number) { return number * 2; }
};
int doubleTheNumber2 = doubleTheNumber.apply(2); /*4 */
/*using lambda */
Function<Integer, Integer> subtract2 = (number) -> number - 2;
int subtract2From4 = doubleTheNumber.apply(4); /*2 */
/* chaining with andThen()
Unlike Predicate and Consumer which uses the same value to all the nodes,
Function uses the result of previous Function */
doubleTheNumber /* 1st will double the number */
.andThen(subtract2) /* 2nd will subtract 2 from doubled number */
.andThen(doubleTheNumber) /* 3rd subtracted result will be doubled */
.apply(4); /* (((4 * 2) - 2) * 2) = 12 */

 

BiFunction < T, U, V >

BiFunction is similar to Function except this accepts two input parameters as opposed to Function which handles only one input. It uses apply(T t, U u) for triggering. Chaining in this usage Function instead of BiFuntion in andThen(Function<V v, E e>) ( since methods don’t have two return values).

BiFunction<Integer, Integer, Integer> areaOfRectangle = new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer length, Integer breadth) { return length * breadth; }
};
areaOfRectangle.apply(2, 2);/*4 */
BiFunction<Integer, Integer, String> areaWitMessage = (lengh, breadth) -> "Area is " + lengh * breadth;
areaWitMessage.apply(4, 3);/*Area is 12 */
/*chaining */
areaOfRectangle /* 1st area will be calulated */
.andThen(area -> area * 3) /* 2nd calulated area will be multiplied by 3 as height */
.apply(2, 3); /* 2 * 3 * 3 = 18 */

 

Supplier < T >

Till now we have talked about the interfaces which take input parameters. Supplier, on the other hand, does not demand any arguments, it just produces one by calling get(). Similar to the method which produces values without input, like toString() or hashCode(). We can’t use chaining in this obviously since it does not take input parameters.

Supplier<Double> randomSupplier = new Supplier<Double>() {
@Override
public Double get() { return Math.random(); }
};
double randomNumber = randomSupplier.get();
System.out.println(randomNumber);
/*lambda */
Supplier<String> codersTeaUrl = () -> "https://coderstea.com";
System.out.println(codersTeaUrl.get());

 

There are more of them

There are more Functional interfaces in the package. They are the implementation of these above functional interfaces for specific challenges or usage. We don’t have to understand every functional interface. If you understand the foregoing interfaces you are good to go. Let us look at them crisply.

The following table is obtained from the Java doc. you can find it here

Interface Description
BiConsumer<T,U> Represents an operation that accepts two input arguments and returns no result.
BiFunction<T,U,R> Represents a function that accepts two arguments and produces a result.
BinaryOperator<T> Represents an operation upon two operands of the same type, producing a result of the same type as the operands.
BiPredicate<T,U> Represents a predicate (boolean-valued function) of two arguments.
BooleanSupplier Represents a supplier of boolean-valued results.
Consumer<T> Represents an operation that accepts a single input argument and returns no result.
DoubleBinaryOperator Represents an operation upon two double-valued operands and producing a double-valued result.

The end of this post. You can find the code on github here or the full project here. We will talk about it into more deep in our next post.

Till then Hakuna Matata!!!

   JavaJava8Functional ProgrammingStream API

About Contributer

Questions / Comments


Comments

Be the first to comment on this post.