DEV Community

Cover image for Exploring Functional Interfaces in Java
Mukund Madhav
Mukund Madhav

Posted on • Originally published at mukundmadhav.com

Exploring Functional Interfaces in Java

This is second part of the series in Functional interfaces. Check the first one here: Guide to Func interfaces

Java Function Interface

What is a Java Function interface?

It represents a functional interface that accepts one argument and produces a result of any class type.

We know that functional interfaces can have a single abstract method (SAM). So the SAM here is called apply:

  • apply Applies this function to the given argument.

Let’s look at an example:

The following code, performs cube root of 27.

Function<Integer, Double> cubeRoot = num -> Math.pow(num , 0.334);
System.out.println(cubeRoot.apply(27)); // Output: 3
Enter fullscreen mode Exit fullscreen mode

represents parameter and output type respectively.

The next obvious question should be:

Why use Java Function interface instead of a normal method?

People, Special, Different, Employment, Hiring, Job

  1. Streams: If you have made any list manipulation in Java recently, you would have used streams.

Let’s look at the code below that extracts name from Person class (defined above)

Stream.of(listOfPersons)
   .map(p -> p.getName())
   .collect(Collectors.toList());
Enter fullscreen mode Exit fullscreen mode

This code extracts list of names from list of Persons.

Now, notice the map () method.

It is receiving p → p.getName() as a parameter…Isn’t that weird?

This is made possible with Java Function. map () receives Function (the functional interface) as a parameter and since p→ p.getName() is a function so that is how it is possible.

  1. Chained Function

Similar to Predicate chaining, you can chain two functions also. By this, result of one function is then passed to another. You can have two independent functions and can reuse them to create a new use-case.

Let’s explore this with an example.

Function<Double, Double> getCubeRoot = num -> Math.pow(num, 0.334);
Function<Double, Double> getSqaureRoot = num -> Math.pow(num, 0.5);

System.out.println(getCubeRoot.apply(getSqaureRoot.apply(64d))); //Output: 2 
Enter fullscreen mode Exit fullscreen mode

As we can see, getCubeRoot and getSqaureRoot has individual use cases to get the cube and square root respectively, but then they can be used together to get the sixth root. (1/2 x 1/3)

Why use predicate when we have Function?

The next obvious question, would be why not use Function everywhere? Isn’t Predicate just a special case of Function, where it returns Boolean value?

Well, no.

Let me explain.

A function can return a “Boolean” (wrapper class) value, while Predicate will always return a “boolean” (primitive type) value.

So, there is a lack of autoboxing in Predicate and a lack of unboxing in Function.

Box, Package

This is intentionally done because Predicate, in most cases, is used for validations. Therefore if Predicate returns null instead of true/false, we won’t know what should be the course of action. In Streams, there are filter functions that can only accept Predicates whereas a map will only accept a Function.

This can be easily demonstrated. The following code will return in complication error:

 Function<Person, Boolean> isAdult = user -> user.getAge() > 18;
Stream<Person> beerPartyList  = stringList().stream().filter(isAdult); // point 1

Predicate<Person> isAwesome = user -> user.following().equals("mukundmadhav02");
Stream<Person> awesomePeopleList = stringList().stream().map(isAwesome); // point 2 
Enter fullscreen mode Exit fullscreen mode

At point 1 and 2, we are passing Function, where a predicate is needed and vice versa.

What is a BiFunction?

Like Predicate, there are BiFunctions also. As the name implies, they accept two arguments of any class type and return a single result.

Java Supplier and Consumer

The Java util Supplier and Consumer functional interfaces are very much complementary to each other so it’s better if we look at them together.

First, let’s get their definitions out of the way.

What is Java Consumer interface?

Shopping, Cart, Man, Woman, Running, Run, Buy, Store

It is :

  • a Functional interface 💁🏻
  • Accepts one argument and does not return anything
  • Single Abstract Method: accept – It simply performs the operation

Hm, let’s look at an example:

In the following code, we are accepting a String in our Consumer. And, we are then returning the greeting message.

Consumer<String> generateGreeting = name -> System.out.println("Hello " + name);
generateGreeting.accept("Mukund"); // Output: Hello Mukund 
Enter fullscreen mode Exit fullscreen mode

name is passed as an argument to generateGreeting consumer, and output is printed.

Pretty simple, eh?

Where would you use the Consumer interface?

The one area Consumer are ubiquitously used is in forEach function.

List<String> coolWebsites = List.of("colorhexpicker", "google");
coolWebsites.forEach(site -> System.out.println("https://" + site +  ".com"));
/*
Output:
<https://colorhexpicker.com>
<https://google.com>
*/
Enter fullscreen mode Exit fullscreen mode

In the above example, we are iterating through list of cool websites and appending the HTTPS protocol and .com TLD before printing.

Iterating through each element via traditional for-loop involves too much boiler plating.

List’s forEach method accepts a Consumer and that makes iteration so simple and intuitive 💫

Side Note:

If you look at the official Consumer documentation it says,

Unlike most other functional interfaces, Consumer is expected to operate via side effects.

What does this mean?

If we look at what consumer is doing, it is accepting something and not returning anything. Of the most useful things we can do with it, Consumer is typically most used in a for-each loop. So if we say that, for Each cannot mutate the list it is being passed to then, looping through each element in the list would be useless.

So that is why the consumer (unlike other functional interfaces) fails to satisfy the criteria for pure functions (which states that function should not mutate state).

Not important, but good to know.

Okay, let’s dive head-first Supplier.

What is Java Supplier interface?

Team, Greeting, Suppliers, Force, Collaboration

Just contrary to Consumers, Suppliers return a single output without any input.

It is:

  • a Functional interface
  • Does not accept anything, returns a single parameter
  • SAM: get: Gets a result

Hm, let’s look at an example

Supplier<String> getFavString = () -> "02";
System.out.println(getFavString.get()); // Output: 02
Enter fullscreen mode Exit fullscreen mode

In the above example, a supplier getFavString does not accept anything but simply returns a string (02 in this case).

Where would you use the Supplier interface?

Now, if you are seeing a Supplier for the first time then like me, You might be tempted to think, why would anyone use it?

It does not accept anything and returns something…? Why would you use it?

Supplier becomes immensely useful when you tie it with another Java 8’s feature – Optional class. To briefly sum up what is Optional, it is a new class defined to Java 8 that helps in better handling of Null values.

  • Optional.orElseGet

Here, if the value is null, orElseGet invokes another function that can get the value or initialize with some default value.

String lastUser = Optional.of(getLatestNameFromChildDb())
                                                        .orElseGet(() -> getLatestNameFromMasterDb());
System.out.println(lastUser);
Enter fullscreen mode Exit fullscreen mode

In the above example, we first try to get value from Child DB and if that does not return anything then we get value from master DB.

() → getLatestNameFromMasterDb() is the supplier and its lazily executed. Meaning, it won’t get executed if we get value from get getLatestNameFromChildDb();

There is also Double Supplier and Double Supplier and So on. They are simply Suppliers/Consumers who return/accept Double valued results. The same is there for other types like IntSupplier, LongSupplier, etc.

Callbacks in Java

Icons, Phone, Round, Connect, Service, Sign, Support

With the introduction of functional interfaces in Java, another interesting programming practise is now possible in Java – callbacks.

Before diving into how callbacks are implemented in Java, let’s look at what they are and why are they useful?

Callback are function passed to another function. This allows for them to be excuted from the function they are passed to depending on certain criteria.

You’ll already know that it’s not possible to pass function to function in Java. This is where functional interfaces come in. Since they can be assigned agaisnt a variable, you can pass them to other functions and hence achieve calbacks.

public void init(Consumer<Void> callback) {
    callback.accept(null);
}

init((Void) -> done());
Enter fullscreen mode Exit fullscreen mode

Let’s connect

Since you have read this far, let’s do a shameless plug.

Let’s discuss more on Java on Twitter 🎃

Connect on Twiiter

TLDR Infographics

If you skipped the article or read it and want a summed up version, the following infographic summarises the entire article – with functional interfaces and Predicate, function, Consumer and Supplier.

Summary of Functional Interfaces in Java

Summing it up

To sum it up, functional interfaces enabled unparalleled functional programming in Java. Thanks to functional interfaces, Java programs are no longer declarative just for it.

Functional interfaces with lambdas, help to quickly spin up code execution This reduces the effort that would have otherwise been used to write long boilerplate code to more intuitive approaches.

All functional interfaces in Java compared

Hope this was helpful. Have an amazing day/night ahead 😎

Categories Java

Post navigationEverything you need to know about log4j vulnerability & how to fix it

Top comments (0)