Essential Functional Interfaces in Java 8

Pritam Panhale
5 min readSep 24, 2020

There are lots of functional interfaces that are provided by Java. You can check all the functional interfaces provided by Java in a package — java.util.function.

As there are lots of functional interfaces available in the above package we can easily get confused that from where should I start learning about these interfaces. When I started learning these interfaces I started with four basic interfaces which I think if anyone learns how to use then they can be better with lambda expression in their java code. Those four interfaces are -

  1. Supplier
  2. Function
  3. Predicate
  4. Consumer

As these interfaces are functional interfaces, it only has one method, and that one method is going to be called in some inbuilt functions which are used by streams. Let us re-iterate the above functional interfaces with their method.

  1. Supplier — T get()
  2. Function — R apply(T t)
  3. Predicate — boolean test(T t)
  4. Consumer — void accept(T t)

The ‘T’ and ‘R’ are the generic type variables in the above methods. If you have a close look at the names of the methods, you will understand the pattern in the name and it will make you memorize and make sense when to use which functional interface. lets again revisit each functional interface in detail now.

  1. Supplier -

By the name itself, one can expect that the object of type supplier will give you something. Authors of Java are very precise while putting their thoughts on naming any package, class, method, or variable. so whenever we want to get something from the object(as the business logic needs) we can use the supplier interface.

Supplier<String> supplier = ()-> "Hello";
System.out.println(supplier.get());

The above code will simply print “Hello”. So whenever you encounter a case where you just need to get something then the supplier is a good option. In the above code, the supplier is of type String and returns the String.

Please refer to the sample code below where we get the regular expression from supplier objects and use them to validate the strings.

public static void supplierDemo() {
checkPattern(() -> "[0-9]", "1");
checkPattern(() -> "[a-z]", "a");
}

public static void checkPattern(Supplier<String> supplier, String expression) {
Matcher matcher = Pattern.compile(supplier.get()).matcher(expression);
System.out.println(matcher.matches());
}
OUTPUT - true, true

Remember ‘Supplier supplies, so we get’

2. Function —

The function is the most used and popular functional interface. Most of the stream methods are taking a function as a parameter.

Remember ‘Function always returns something’

The functional interface Function has ‘R apply(T y)’ method. So whenever we write a lambda expression for the function we are writing an implementation of apply method. Whatever we pass to apply method, it processes it and always returns something. There are lots of use cases in business processing where we encounter this scenario where we provide some input and wanted something to be returned. As we discussed may of the stream methods are also designed in the same way so that we can use Functions in it.

public static void functionDemo() {
Function<String, Boolean> checkStartsWithP = checker -> checker.startsWith("P");
System.out.println(checkStartsWithP.apply("Program"));
}
OUTPUT - true

The above code will simply print “true” because the condition is getting satisfied. The function checkStartsWithP has two generics parameter ‘String’ and ‘Boolean’. The first parameter specifies what type of argument the method will take and the second parameter specifies what type of result the method will return. Hence in the above code, the apply method will take the argument as String and returns a Boolean result. Let see an example with stream objects which are more popular and will help you reduce the number of lines of code in your program.

public static void functionDemoWithStreams() {
Integer[] salaries = {100, 200, 300, 400};
List<Integer> salariesList = Arrays.asList(salaries);
List<Double> appraisedSalaries = salariesList.stream().map(salary -> salary + salary * 0.1).collect(Collectors.toList());
System.out.println(appraisedSalaries);
}
OUTPUT - [110.0, 220.0, 330.0, 440.0]

In the above example, we see, streams provide a map function which takes Function as an argument where we have written a lambda expression which takes an argument ‘salary’(of type Integer) and returns appraised salary (of type Double). For me, the Function is the most useful functional interface when I write code in my project.

3. Predicate

Again one of the most commonly used functional interfaces. Let's see an example first and then we will go towards the explanation.

public static void predicateDemo() {
Predicate<String> animalPredicate = (val) -> val.equals("Lion");
System.out.println(animalPredicate.test("Lion"));
}
OUTPUT - true

The above function will just check whether the given string is ‘Lion’ or not and it will print ‘true’. The predicate has a method test(T t) which always returns a boolean. The argument we pass to the test method will be evaluated with some business logic and we can return true or false. This functional interface is also getting used in the codes most of the time. Let's take one more example with streams that we can relate to more.

public static void predicateDemo() {
Integer[] salaries = {100, 200, 300, 400};
List<Integer> salariesList = Arrays.asList(salaries);
boolean salaryBelow200 = salariesList.stream().anyMatch(sal -> sal < 200);
System.out.println(salaryBelow200);
boolean salaryBelow50 = salariesList.stream().anyMatch(sal -> sal < 50);
System.out.println(salaryBelow50);
}
OUTPUT - true , false

The above will print ‘true’ and then ‘false’ sequentially. The method anyMatch takes an argument of type Predicate which is of type Integer which checks the salary. Both the lambda expression have different checks on salary. If any of the elements in the list fulfill the criteria it will return the appropriate value. We are just passing the object of Predicate to anyMatch rest will be taken care of by anyMatch function itself!

RememberPredicate always tests’ !

4. Consumer.

The name itself gives us the fair idea that it is going to consume something which we provide and when someone consumes we never know what happens to it! Hence, the essence is, consume’s method never returns anything, it just accepts whatever is given to it. Let’s see an example.

public static void consumerDemo() {
String animal = "Lion";
Consumer<String> uppercase = c -> System.out.println("Uppercase is : " + c.toUpperCase());
Consumer<String> lowercase = c -> System.out.println("Lowercase is : " + c.toLowerCase());
uppercase.accept(animal);
lowercase.accept(animal);
}
OUTPUT - LION, lion

The consumer has a method called ‘accept(T a)’ which does not return anything, it just accepts. We can write the rest of the business logic in here. In the above code, we just had written the SOP statement as a lambda statement which will convert a normal string into uppercase and lowercase respectively. So the output of the above code will be ‘LION’ and ‘lion’. There is a good use of this interface in streams. Most of the time we need iterations in our code, then what's better than the consumer! Let's see an example.

public static void consumerDemo() {
String[] animals = {"Lion", "Tiger", "Puma"};
List<String> animalList = Arrays.asList(animals);
animalList.stream().forEach(animal -> {
System.out.println(animal);
});
}
OUTPUT :
Lion
Tiger
Puma

In the above example, the method ‘forEach’ will take an argument of type Consumer. The consumer is of type String and its role is to print the content on the console, and that's how the for loop will print all the animals. The method forEach just takes an argument of type Consumer, it will call consumer’s accept() method internally!

Remember, ‘Consumer accepts whatever comes and never return!’

This is it! these are the four functional interfaces which I think are the basic interfaces to learn first. There are others too but if you learn four of these then it will be easier to learn others. For example — DoubleConsumer it's the same as Consumer but with generic type as Double, IntSupplier — same like Supplier but comes directly with an Integer type. There are many more, you can just google it and you can get the whole list.

Happy Learning !!!

--

--