Java Recent History — Java 8 [Part 2] -Functional Interface, Predefined Functional Interface…

Julio Falbo8 min read
Listen to it, read by Julio Falbo (10 min)
10:00

Java 8 official logo

Heeeey people!

I was looking forward to posting this second part because here we have important features of Java 8 that are not so famous!

So, I’ll not prolong too much, let’s understand Functional Interfaces!!!

Functional Interface

I’ll start with a really simple explanation!

Functional Interface is an interface that only has one abstract method!

Hmm, but what is an abstract method?

An abstract method is a method that doesn’t have a body, which means that this method only has a signature!

Before Java 8 we called this type of Interface as “Single Abstract Method” (SAM) and we can use Runnable and Callable as SAM examples (also called to Legacy Functional Interfaces).

Now, I have a simple question. What is the use of a Functional Interface?

And I have a simple answer: Invoke Lambda Expression!

Interesting, but how can I guarantee that no one can add a method to my functional interface?

Amazing question!

Java 8 provides an annotation called @FunctionalInterface to check whether the interface is a Functional Interface or not!

So, when you have one Interface annotated with @FunctionalInterface and someone tries to add another abstract method, the compiler will throw this message:

Text
Unexpected @FunctionalInterface annotation. Multiple non-overriding abstract methods found in interface <YOUR-INTERFACE>

One important note here: The annotation is optional, that is, it is an informative annotation used to guarantee that your Interface will never have another abstract method. So, if you have an interface with one abstract method but without FunctionalInterface annotation, it still is a functional interface!

But if we have an interface annotated with FunctionalInterface annotation without methods, we will receive a compile error:

Text
Unexpected @FunctionalInterface annotation. No abstract method found in interface <YOUR-INTERFACE>

It means that an empty interface is not a valid Functional Interface.

Another question Julio, why did you highlight the word abstract when you talked about interface methods? Every method in a interface should be abstract correct?

Really, REALLY, great question!

Now in Java 8, we have 2 new concepts about interface methods: Default and Static methods!

I’ll not explain here what is each one because we have an article(first part of this series) explaining each one in detail.

The only thing that you need to know is: Functional Interfaces can have 0 or more default and static methods. The only thing that must have only 1 is the abstract method!

Another very important rule is: Functional Interfaces can have abstract methods of java.lang.Object. For example, I can add an abstract method called equals returning a boolean and receiving one parameter with the type Object. Here you can find the complete list of methods that can be in our FunctionalInterface along with our main abstract method.

Custom Functional Interface

To finish the explanation about Functional Interfaces, let’s talk about how it works in the Inheritance context.

So, let’s imagine that we have 2 interfaces, ParentInterface, and ChildInterface (both annotated with @FunctionalInterface), where ChildInterface is extending ParentInterface.

The scenarios are (let’s assume that ParentInterface contains a method called parentAbstractMethod):

  1. ChildInterface can be empty because it is inheriting the abstract method of the ParentInterface.
  2. ChildInterface can define the same method (with the same signature) of ParentInterface because the method declared in ChildInterface will override the method declared in ParentInterface.
  3. ChildInterface can not declare an abstract method (except java.lang.Object methods) since it already is inheriting the abstract method of the ParentInterface.
  4. ChildInterface can have default methods and static methods

Now let’s imagine that we have another interface that is extending ChildInterface, but it is not a Functional Interface, can we have another abstract method?

And the answer is: Yes. Since your interface is not annotated with FunctionalInterface annotation, it is a simple Interface, it does not matter if it is extending a Functional Interface if we do not say to the compiler (using the annotation) that it is a Functional Interface, the compiler will treat it as a normal interface.

Predefined Functional Interfaces

Now that we already know what is a Functional Interface, let’s understand all predefined functional interfaces that Java 8 brought to us.

Function

Actually, the idea is the same that we saw in the Functional Interface section.

The most important thing that we need to have in mind when we are using the Function Functional Interface is the parameter type and the return type.

The Functional Interface expects 2 types parameters (Function<T, R>) where T is the input parameter type and R is the return type. So the left type is the input parameter type and the right type is the return type. (Pay attention here, this is very important)

To call our function, we have a method called apply (remember here that we only can pass 1 input parameter), it means,Function takes a single input and returns a single output.

We can confirm that T is the input parameter type when we look at the apply method signature.

Java
R apply(T t);

This Functional Interface is very important to help us to apply the Functional Programming concept, so, now we can send this function as a parameter to another method!

As we saw in the Functional Interface section, a Functional Interface can have default methods and static methods, and Function has 2 defaults and 1 static method and I’ll explain the goal of each one!

Defaults Methods of Function

Method: andThen

Before to start the explanation about the andThen method, I would like to show the implementation of this method.

Java
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
   Objects.requireNonNull(after);
   return (T t) -> after.apply(apply(t));
}

As we can see, this method is receiving another Function as a parameter and returns a composed function that first applies this function to its input, and then applies the after function to the result. We can call this Function Chaining.

If the evaluation of either function throws an exception, it is relayed to the caller of the composed function.

So, in other words, Java will execute the function that called .andThen first and after this, it will execute the function that we passed as parameter. Like a method that is expecting a result of another method.

WAIT!

Here there is a really important point!

I don’t know if you paid attention to the parameter Function (I think you did because you are amazing), but we have an important point there!

Look at the Function type:

Java
Function<? super R, ? extends V>

And?!

Did you remember that T is the input parameter type and R is the return type? (I said it was very important)

So, when we say “? super R” means that this type should be or should extend the R type (return type). And do you remember what the left type is?

Yeeees! The left type is the input parameter type!

So, the parameter type of the second Function should be the return type (or extend this) of the first function, exactly when you have 2 methods that the second one is expecting the return of the first one!

Method:compose

I’ll define the compose method very quickly and you will understand!

Implementation:

Java
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
   Objects.requireNonNull(before);
   return (V v) -> apply(before.apply(v));
}

The compose method is exactly the opposite of the andThen method, it is, the composing method will execute first the parameter Function and after the function that called the composite method!

SIMPLY LIKE THAT!

Note: Remember that the parameter type of the first Function should be or extend the return type of the second Function.

Now let’s talk about the static method of the Function!

Static method of Function

Method: identity

We only have 1 static method that is the identity method, but again, before the explanation, I would like to show the implementation of this method.

Java
static <T> Function<T, T> identity() {
 return t -> t;
}

As you can see, this method is really simple, it only returns a function that always returns its input argument.

Note: As you can see the returned Function has the type T and is expecting T as a parameter and return types, it means that you can only use Function.identity() when the parameter has the same type of return!

Predicate

Now that we already know like a Functional Interface Function works it is easy to understand the other Predefined Functional Interfaces.

Let’s talk about Predicate!

To make it easier, imagine a Predicate like a Functional that always returns a boolean type in the abstract method called test. Knowing this it is pretty easy to understand the goal of a predicate: Takes an object as input and returns either true or false based on the condition (implementation of test method).

Method Signature:

Java
boolean test(T t);

Like a Function, Predicates already have default methods and one static method.

Default Methods

Static Method

Default Method: and

Implementation:

Java
default Predicate<T> and(Predicate<? super T> other) {
   Objects.requireNonNull(other);
   return (t) -> test(t) && other.test(t);
}

This method has the same goal of the Function method `andThen`: Create a Function Chaining (in this case Predicate Chaining).

So, as we can see in the implementation of this method:
It will get the result of the first Predicate and create another condition using the result of the second Predicate (input parameter Predicate of and method) using the conditional operator &&, it means that the result of the method and only will be true if both results are true.

Note: You can add as many conditions as you want in your chain!

Example:

Java
Predicate<String> firstPredicate = s -> s.startsWith("Julio");
Predicate<String> secondPredicate = s -> s.endsWith("Falbo");
Predicate<String> thirdPredicate = s -> s.length() == 11;

// true case
firstPredicate.and(secondPredicate).and(thirdPredicate).test("Julio Falbo");

// false case
firstPredicate.and(secondPredicate).and(thirdPredicate).test("Julio Falbo Error");

Default Method: or

Implementation:

Java
default Predicate<T> or(Predicate<? super T> other) {
   Objects.requireNonNull(other);
   return (t) -> test(t) || other.test(t);
}

The method or does the same thing of method and but instead use conditional operator &&, it uses the conditional operator ||, it means that the result will be true if at least 1 condition is true.

Example:

Java
Predicate<String> firstPredicate = s -> s.startsWith("Julio");
Predicate<String> secondPredicate = s -> s.endsWith("Falbo");
Predicate<String> thirdPredicate = s -> s.length() == 11;

// true case -> Because all the Predicates are returning true
firstPredicate.or(secondPredicate).or(thirdPredicate).test("Julio Falbo");

// true case -> Because firstPredicate is returning true
firstPredicate.or(secondPredicate).or(thirdPredicate).test("Julio Falbo Still True");

// true false -> Because all the Predicates are returning false
firstPredicate.or(secondPredicate).or(thirdPredicate).test("False Julio Falbo False");

Default method: negate

Java
default Predicate<T> negate() {
   return (t) -> !test(t);
}

This is the simplest method that we have in Predicate. This method is only responsible to return the opposite result of a Predicate, that is if my Predicate returns true the result of the negating method will be false.

Example:

Java
Predicate<String> firstPredicate = s -> s.startsWith("Julio");
// The result will be false because the result of the predicate is true
firstPredicate.negate().test("Julio");

Static method: isEqual

Implementation:

Java
static <T> Predicate<T> isEqual(Object targetRef) {
   return (null == targetRef)
           ? Objects::isNull
           : object -> targetRef.equals(object);
}

This is another simple method that exists to facilitate comparing 2 objects using the method .equals of the object that we are sending as a parameter to the isEqual method. So, this method returns a predicate that tests if two arguments are equal.

Note: As we can see in the implementation, the isEqual is null safe, so, we can use it without concern about NPEs (NullPointerException).

Example:

Java
// Will return false because we are comparing 2 objects with different types
Predicate<Integer> testDifferentTypes = Predicate.isEqual("2");
testDifferentTypes.test(2);

// Will return true because we are comparing 2 equal objects
Predicate<Integer> testSameTypes = Predicate.isEqual(2);
testSameTypes.test(2);

Consumer

Consumer follows the same idea of other predefined functional interfaces, but your abstract method (accept) returns nothing!

Method signature:

Java
void accept(T t);

Ok, it is useless, right?

It depends, Consumer is expected to operate via side-effects.

Consumer only has one default method called andThen, and this method has the same goal of the andThen of the Functional Interface Function, create a chain (in this case a Consumer Chaining).

Example:

Java
AtomicReference<String> atomic = new AtomicReference("Julio");
Consumer<AtomicReference> firstConsumer = a -> System.out.println(a.get());
Consumer<AtomicReference> secondConsumer = a -> a.set("Falbo");
// The result will be:
// Julio
// Falbo
// Because firstConsumer is printing the current value of the AtomicReference object, which is Julio (initial value).
// The secondConsumer is changing the value of the AtomicReference to Falbo
// So when we called firstConsumer after the secondConsumer, the current value of the AtomicReference now is Falbo.
firstConsumer.andThen(secondConsumer).andThen(firstConsumer).accept(atomic);

Supplier

Unlike other predefined functional interfaces, Supplier takes no input and returns an object, also Supplier has no default or static methods. What makes Supplier the simplest predefined functional interface that we have!

The abstract method of Supplier is called get!

Method signature:

Java
T get();

Example:

Java
Supplier<String> mySupplier = () -> String.valueOf(Math.random());
// Will return a random object as String
mySupplier.get();

UnaryOperator

UnaryOperator is a Functional Interface witch extends Function and represents an operation that receives a single input parameter and returns a result of the same type.

So, let’s imagine that we want to create a Function that is expecting an Integer as an input parameter and returns an Integer as a result. The implementation is:

Java
Function<Integer, Integer> integerFunction = i -> i + 2;

As you can see, we need to declare both Type Parameter as Integer. So, in this case, we can use UnaryOperator to simplify the implementation:

Java
UnaryOperator<Integer> unaryOperator = i -> i + 2;

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

Thank you very much and I hope you enjoyed it!

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

See you in the third part!

References

What’s New in JDK 8

Share on LinkedIn

Originally published on Medium on Nov 4, 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