Java Recent History — Java 8 [Part 3] -Two-Argument (Bi) Functional Interfaces, Primitive Type…

Julio Falbo6 min read
Listen to it, read by Julio Falbo (8 min)
8:00

Java 8 official logo

Heeeey people!

One more article about the Java 8 features! Oh yeah!

Now we’ll talk about Two-Argument (Bi) Functional Interfaces and Primitive Type Functional Interfaces and understand what the benefits are when working with Primitive Types combined with Functional Interfaces.

If you did not read the second part, I strongly suggest you read it before start here!

Two-Argument (Bi) Functional Interfaces

Ok, now we already saw all the predefined functional interfaces, and I’m sure that you realized something really interesting.

The number of input parameter?

You are simply AMAZING!
Yes, you are right! Function, Predicate, and Supplier can only receive 1 input parameter, and in some cases, we need another one, how can we handle this case?

Simple!

Java 8 provided us with BiFunctional Interfaces from each Predefined Functional Interface (excluding Supplier because the abstract method of Supplier does not receive any parameter).

It means that we have BiFunction, BiPredicate, and BiConsumer that receive 2 input parameters!

Wow, nice! But are they exactly the same as the Single input versions but with 2 input parameters?

Unfortunately no :(

Let’s talk about BiFunction first!

BiFunction

Like the single parameter version, BiFunction has an abstract method called apply but now receiving 2 parameters.

Method signature:

Java
R apply(T t, U u);

As you can see, the first type of BiFunction<T, U, R> is T, the middle one is U and the last one is R, where T is the type of the first parameter, U is the type of the second parameter and R is the of the return.

So, what is the difference?

Do you remember the default and static methods of the Function?

Yes, now we only have 1 method called andThen, it means that we don’t have a default method compose and the static method identity.

And the curious fact here is: The default method andThen of a BiFunction interface receives a Function as a parameter, not a BiFunction. So, it is not possible to create a chain of BiFunctions.

Example:

Java
BiFunction<Integer, Integer, String> calcAndToString = (first, second) -> String.valueOf(first + second);

Function<String, Integer> add2 = first -> Integer.valueOf(first) + 2;

calcAndToString.andThen(add2).apply(1, 10);

BiPredicate

Different from BiFunction, the BiPredicate<T, U> interface has almost the same methods as the Predicate interface, it is only missing the static method isEquals, and the default methods and andor are receiving a BiPredicate as a parameter, so, here it’s possible to create a BiPredicate chain.

Example:

Java
BiPredicate<String, String> contains = (first, second) -> first.contains(second);

BiPredicate<String, String> startsWith = (first, second) -> first.startsWith(second);

// Will return true because all conditions are true
System.out.println(contains.and(startsWith).test("Julio Falbo", "Julio"));

// Will return true because contains BiPredicate is true
System.out.println(contains.or(startsWith).test("Julio Falbo", "Falbo"));

// Will return false because contains BiPredicate is true and we called the method negate
System.out.println(contains.negate().test("Julio Falbo", "Falbo"));

BiConsumer

BiConsumer is the same as Consumer but receiving 2 input parameters. It means that BiConsumer also has an andThen and unlike the BiFunction, it is receiving a BiConsumer as a parameter!

Example:

Java
BiConsumer<String, String> printLove = (first, second) -> System.out.println("I (" + first + ") love " + second);

BiConsumer<String, String> printFamily = (first, second) ->
       System.out.println("I also love my wife and my daughter (and " + second + ")");

printLove.andThen(printAnotherPhrase).accept("Julio Falbo", "Java");

BinaryOperator

Like a UnaryOperator, BinaryOperator brings the concept of a single type, but now extending a BiFunction.

It means that BinaryOperator is a BiFunction but with both input parameters and returns with the same type!

Example:

Java
BiFunction<Integer, Integer, Integer> addBi = (a, b) -> a + b;
// Result will be 4
System.out.println(addBi.apply(2,2));

BinaryOperator<Integer> addBinary = (a, b) -> a + b;
// Result will be 4
System.out.println(addBinary.apply(2,2));

Ok Julio, but do we have something more?

Unlike UnaryOperator, BinaryOperator has 2 extras static methods: minBy and maxBy.

The idea here is to give to us the flexibility to implement our custom logic to compare 2 objects easily.

Note before starting: Comparatoris a Legacy Functional Interface added in Java 1.2 and Java 8 introduced several enhancements to this interface. I’ll not explain those enhancements but if you want to go deep I can suggest this amazing article.

Method: minBy

Implementation:

Java
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
   Objects.requireNonNull(comparator);
   return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}

This method returns a BinaryOperator which returns the lesser of the two elements according to the specified Comparator.

Example:

Java
BinaryOperator<Integer> minBinaryOperator = BinaryOperator.minBy((a, b) -> (a > b) ? 1 : ((a == b) ? 0 : -1));

System.out.println(minBinaryOperator.apply(2, 4));

The code above will print 2 because in the custom logic I’m saying to return

And I’m calling the method minBy, so if the result of the comparison is lesser than/equal 0 it will return the first parameter (because it already knows which is the lesser value based on the custom compare logic)

Method: maxBy

Implementation:

Java
public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
   Objects.requireNonNull(comparator);
   return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}

This method returns a BinaryOperator which returns the greater of the two elements according to the specified Comparator.

Example:

Java
BinaryOperator<Integer> maxBinaryOperator = BinaryOperator.maxBy((a, b) -> (a > b) ? 1 : ((a == b) ? 0 : -1));

System.out.println(maxBinaryOperator.apply(2, 4));

The code above will print 4 because in the custom logic I’m saying to return

And I’m calling the method maxBy, so if the result of the comparison is bigger than/equal 0 it will return the first parameter (because it already knows which is the bigger value based on the custom compare logic)

Primitive Type Functional Interfaces

Before I start to show the Primitive Type Functional Interfaces I would like to explain some concepts. It will be very important for you to understand why we need them.

First of all, I will explain what the Wrapper Classes are.

Wrapper Classes

Wrapper classes are objects responsible for encapsulating primitive types. Below I’ll show the primitive type and the corresponding wrapper.

  1. boolean -> Boolean
  2. byte -> Byte
  3. short -> Short
  4. char -> Character
  5. int -> Integer
  6. long -> Long
  7. float -> Float
  8. double -> Double

Hmm, looks nice, but what is the goal of a wrapper class?

Really important question and the answer is simpler than you imagine!

Generic classes don’t support primitive types. It means that we can not use a primitive type as a Type Parameter.

Example:

Java
// Wrong
Predicate<int> wrongPredicate = i -> i < 0;

// Correct
Predicate<Integer> correctPrecidate = i -> i < 0;

Now that we know what is a Wrapper Class I’ll explain the Autoboxing and Autounboxing concepts.

Autoboxing

It is a concept introduced in Java 5 (2004) and consists of the compiler’s capability to automatically convert from a primitive type to a Wrapper Class.

Java
// Using autoboxing
Integer autoboxed = 10;

// Not using autoboxing
Integer withoutAutoboxing = Integer.valueOf(10);

Auto-unboxing

It is the capability of a compiler to reverse the Autoboxing, which means, convert from Wrapper Class to a primitive type variable.

Java
Integer object = new Integer(10);

// Auto-unboxing
int autounboxing = object;

Now that we already understood these 3 important concepts, let’s start to talk about the PTFI (Primitive Type Functional Interface).

So, to understand the usage of a Primitive Type Functional Interface I would like to quote an amazing author, Joshua Bloch.

In Item 5, of Effective Java, Joshua Bloch says:

Prefer primitives to boxed primitives, and watch out for unintentional autoboxing.

But, why?

And the answer is simple: Memory Impact!

Let’s see the memory impact of primitive types:

boolean -> 1 bit

byte -> 8-bit

short and char-> 16-bit

int and float-> 32-bit

long and double -> 64-bit

Now let’s see the memory impact of wrapper classes:

Boolean, Byte, Short, Character, Integer, Float -> 128 bits

Long and Double -> 192 bits

Note: To measure the Objects size I used a tool called Java Object Layout(or just JOL) and you can see the code in my GitHub repo.

Another thing that is important to know is: Primitive data types live in the stack memory and can be accessed fast (If you don’t know what is a stack memory click here) and Wrapper Classes are objects, they live on the heap and are relatively slow to access.

So, I think you already got the point. Operations for primitive types are faster than those for wrapper classes!

Now I’ll show a simple example, but if you want to know more I suggest you read this amazing article.

Performance Benchmark

Primitive Type Benchmark

And the output is:

Text
ElapsedTime operationWithWrapperClass: 1544

ElapsedTime operationWithPrimitiveTypeFunctionInterface: 1064

So, as you could see in the operationWithWrapperClass method we are doing 200000000 unintended conversions (100000000 autoboxing and 100000000 auto-unboxing) since the type parameter of Predicate is Integer.

Now that you understood and saw the benefits of Primitive Type Functional Interfaces I’ll show all the options that we have available in Java 8.

Primitive Functions

We have 15 different types of Primitive Functions present in Java 8.

Primitive Type as input parameter

Java
R apply(int value);
Java
R apply(long value);
Java
R apply(double value);

Primitive Type as return

Java
int applyAsInt(T value);
Java
int applyAsInt(T t, U u);
Java
long applyAsLong(T value);
Java
long applyAsLong(T t, U u);
Java
double applyAsDouble(T value);
Java
double applyAsDouble(T t, U u);

Primitive Type as input parameter and return

Java
long applyAsLong(int value);
Java
double applyAsDouble(int value);
Java
int applyAsInt(long value);
Java
double applyAsDouble(long value);
Java
int applyAsInt(double value);
Java
long applyAsLong(double value);

Primitive Predicates

Java
boolean test(double value);
Java
boolean test(int value);
Java
boolean test(long value);

Primitive Consumers

Java
void accept(double value);
Java
void accept(int value);
Java
void accept(long value);
Java
void accept(T t, double value);
Java
void accept(T t, int value);
Java
void accept(T t, long value);

Primitive Suppliers

Java
boolean getAsBoolean();
Java
double getAsDouble();
Java
int getAsInt();
Java
long getAsLong();

Primitive UnaryOperators

Java
double applyAsDouble(double operand);
Java
int applyAsInt(int operand);
Java
long applyAsLong(long operand);

Primitive BinaryOperators

Java
double applyAsDouble(double left, double right);
Java
int applyAsInt(int left, int right);
Java
long applyAsLong(long left, long right);

If you are reading this line I would like to say something:

Thank you very much and I hope you enjoyed it!

Every code that I mentioned here (and examples that I did not put here) is present in the GitHub!

See you in the fourth part!

References

java.util.function (Java Platform SE 8 )

What’s New in JDK 8

Share on LinkedIn

Originally published on Medium on Nov 5, 2019. Claps and comments live there.

Comments

No comments yet. Be the first.

We email you a 6-digit code to publish it. Your email is never shown.

← More articles · follow by RSS