Java Functional Interface
What is a Functional interface?
The interface which contains single abstract method is called Functional interface.
Examples
Runnable interface - run()
Callable interface - call()
Comparable interface - compareTo()
Comparator interface - compare()
Functional interface could have default & static methods introduced in java 8
@FunctionalInterface
public interface ValidFunctionalInterf {
void test();
default void test1(){
System.out.println("default method introduced in java 8");
}
public static void test2(){
System.out.println("static method introduced in java 8");
}
}
Identify valid/invalid Functional Interface
@FunctionalInterface
interface A{
public void m1();
}
@FunctionalInterface
interface B extends A{
}
Above one is valid, as both the interface are marked as FunctionalInterface and has only one abstract method.
@FunctionalInterface
interface A{
public void m1();
}
@FunctionalInterface
interface B extends A{
public void m1();
}
valid - because both interface has only one abstract method
@FunctionalInterface
interface A{
public void m1();
}
@FunctionalInterface
interface B extends A{
public void m2();
}
Invalid - because interface B has two abstract methods, m1() is inherited from interface A in interface B.
@FunctionalInterface
interface A{
public void m1();
}
interface B extends A{
public void m2();
}
valid - interface A contains only one abstract method, and interface B is not marked as functional interface(it could have any number of abstract methods).
Built-in Functional Interfaces
The functional interfaces in the java.util.function package are introduced to enable functional-style programming in java by serving as the target types for lambda expressions and method references.
| Interface | Purpose | Functional method |
|---|---|---|
| Predicate | Evaluate a condition | boolean test(T t) |
| Function<T,R> | Transforms an input | R apply(T t) |
| Consumer | Performs an action on an input | void accept(T t) |
| Supplier | Provides a result without taking an input | R get() |
Predicate
Interface and method signature
public interface java.util.function.Predicate<T> {
public abstract boolean test(T);
public default java.util.function.Predicate<T> and(java.util.function.Predicate<? super T>);
public default java.util.function.Predicate<T> negate();
public default java.util.function.Predicate<T> or(java.util.function.Predicate<? super T>);
public static <T> java.util.function.Predicate<T> isEqual(java.lang.Object);
public static <T> java.util.function.Predicate<T> not(java.util.function.Predicate<? super T>);
}
Demo code
public class PredicateDemo {
public static void main(String[] args) {
Predicate<Integer> oddOrEven = num -> num % 2 == 0;
System.out.println(oddOrEven.test(11)); //false
System.out.println(oddOrEven.test(24)); //true
}
}
Predicate Joining
consider below scenario
Predicate1 - returns the number which are even
Predicate2 - returns the number which are greater than 15
public class PredicateDemo {
public static void main(String[] args) {
Predicate<Integer> predicate1 = num -> num % 2 == 0;
Predicate<Integer> predicate2 = num -> num > 15;
}
}
Return the numbers which are even and greater than 15 -> p1.and(p2)
import java.util.Arrays;
import java.util.function.Predicate;
import java.util.List;
import java.util.stream.Collectors;
public class Scenario1 {
public static void main(String[] args) {
Predicate<Integer> predicate1 = num -> num % 2 == 0;
Predicate<Integer> predicate2 = num -> num > 15;
List<Integer> list = Arrays.asList(1,12,16,18,19,22,23);
List<Integer> result = list.stream()
.filter(predicate1.and(predicate2))
.collect(Collectors.toList());
System.out.println(result);
}
}
output
[16, 18, 22]
return the numbers which are even or greater than 10 -> p1.or(p2)
import java.util.Arrays;
import java.util.function.Predicate;
import java.util.List;
import java.util.stream.Collectors;
public class Scenario2 {
public static void main(String[] args) {
Predicate<Integer> predicate1 = num -> num % 2 == 0;
Predicate<Integer> predicate2 = num -> num > 15;
List<Integer> list = Arrays.asList(1,12,16,18,19,22,23);
List<Integer> result = list.stream()
.filter(predicate1.or(predicate2))
.collect(Collectors.toList());
System.out.println(result);
}
}
output
[12, 16, 18, 19, 22, 23]
return the number which is not even(odd number)-> p1.negate()
import java.util.Arrays;
import java.util.function.Predicate;
import java.util.List;
import java.util.stream.Collectors;
public class Scenario3 {
public static void main(String[] args) {
Predicate<Integer> predicate1 = num -> num % 2 == 0;
List<Integer> list = Arrays.asList(1,12,16,18,19,22,23);
List<Integer> result = list.stream()
.filter(predicate1.negate())
.collect(Collectors.toList());
System.out.println(result);
}
}
output
[1, 19, 23]
BiPredicate
Two input arguments, returns boolean
Interface and method signature
public interface java.util.function.BiPredicate<T, U> {
public abstract boolean test(T, U);
public default java.util.function.BiPredicate<T, U> and(java.util.function.BiPredicate<? super T, ? super U>);
public default java.util.function.BiPredicate<T, U> negate();
public default java.util.function.BiPredicate<T, U> or(java.util.function.BiPredicate<? super T, ? super U>);
}
demo code
import java.util.function.BiPredicate;
public class BiPredicateDemo {
public static void main(String[] args) {
BiPredicate<Integer,Integer> checkSumEven = (a,b)-> (a+b)%2==0;
System.out.println(checkSumEven.test(10,20)); //true
System.out.println(checkSumEven.test(11,2)); //false
}
}
Note - and(), or() & negate() works same as Predicate in BiPredicate as demoed above in Predicate section
Primitive Predicate Types
Normal predicate type accept object only, it converts int to Integer(autoboxing) and back to int(autounboxing) while evaluating expressions. This impacts the performance, hence type based Predicates are available those we can use. Below are the types -
IntPredicate
LongPredicate
DoublePredicate
Demo code
import java.util.function.DoublePredicate;
import java.util.function.IntPredicate;
import java.util.function.LongPredicate;
public class PrimitivePredicateTypes {
public static void main(String[] args) {
IntPredicate p1 = num -> num > 10 ;
System.out.println(p1.test(12));
System.out.println(p1.test(9));
LongPredicate p2 = num -> num % 2 ==0;
System.out.println(p2.test(20L));
System.out.println(p2.test(3L));
DoublePredicate p3 = num -> num / 3 == 2;
System.out.println(p3.test(6.0d));
System.out.println(p3.test(1.0d));
}
}
Function
Used to perform some operation and produce results.
Interface and method signature
public interface java.util.function.Function<T, R> {
public abstract R apply(T);
public default <V> java.util.function.Function<V, R> compose(java.util.function.Function<? super V, ? extends T>);
public default <V> java.util.function.Function<T, V> andThen(java.util.function.Function<? super R, ? extends V>);
public static <T> java.util.function.Function<T, T> identity();
}
Example1
import java.util.function.Function;
public class FunctionDemo {
public static void main(String[] args) {
Function<Integer,Integer> square = x -> x * x;
System.out.println(square.apply(4)); //output - 16
}
}
Example2
import java.util.function.Function;
public class FunctionDemo {
public static void main(String[] args) {
Function<String,Integer> len = String::length;
/*
Used method reference above, alternative is: s-> s.length()
*/
System.out.println(len.apply("amol"));//output - 4
}
}
Function Chaining
double the number then calculate the square of a number -> f1.andThen(f2)
import java.util.function.Function;
public class FunctionDemo {
public static void main(String[] args) {
Function<Integer,Integer> f1 = x -> x * 2; // double
Function<Integer,Integer> f2 = x -> x * x; //square
Function<Integer,Integer> doubleThenSquare = f1.andThen(f2);
System.out.println(doubleThenSquare.apply(5));//output - 100
}
}
calculate the square of a number then double it -> f1.compose(f2)
import java.util.function.Function;
public class FunctionDemo {
public static void main(String[] args) {
Function<Integer,Integer> f1 = x -> x * 2; // double
Function<Integer,Integer> f2 = x -> x * x; //square
Function<Integer,Integer> squareThenDouble = f1.compose(f2);
System.out.println(squareThenDouble.apply(5));//output - 50
}
}
BiFunction
Takes two input arguments and returns value of given type
Demo code
import java.util.function.BiFunction;
public class BiFunctionDemo {
public static void main(String[] args) {
BiFunction<Integer,Integer,Integer> squareOfSum = (a,b)-> {
int c = a + b;
return c * c;
};
System.out.println(squareOfSum.apply(1,1)); //4
}
}
Primitive Function Type
Single argument with fixed input type and returns any type
IntFunction
LongFunction
DoubleFunction
Demo code
import java.util.function.IntFunction;
public class PrimitiveFunctionTypes {
public static void main(String[] args) {
IntFunction<String> cases = n -> {
switch (n){
case 1: return "one";
case 2: return "two";
case 3: return "three";
default: return "match not found";
}
};
System.out.println(cases.apply(2));
System.out.println(cases.apply(5));
}
}
Note - Similar way we can use DoubleFunction & LongFunction which has fixed input type
single argument with fixed input and fixed output
IntToDoubleFunction - double applyAsDouble(int value)
DoubleToIntFunction - int applyAsInt(double value)
IntToLongFunction - long applyAsLong(int value)
DoubleToLongFunction - long applyAsLong(double value)
Demo Code
import java.util.function.IntToDoubleFunction;
public class IntToDoubleDemo {
public static void main(String[] args) {
IntToDoubleFunction ans = n -> n / 3.3333d;
System.out.println(ans.applyAsDouble(10)); //3.000030000300003
}
}
Single argument fixed return type
ToIntFunctionDemo
ToLongFunction
ToDoubleFunction
Demo code
import java.util.function.ToIntFunction;
public class ToIntFunctionDemo {
public static void main(String[] args) {
ToIntFunction<String> length = s -> s.length();
System.out.println(length.applyAsInt("calculate length of string"));
}
}
Two input args and fixed return type
ToIntBiFunction
ToDoubleBiFunction
ToLongBiFunction
Demo code
import java.util.function.ToIntBiFunction;
public class ToIntBiFunctionDemo {
public static void main(String[] args) {
ToIntBiFunction<Integer,Integer> ans = (a,b) -> a + b;
System.out.println(ans.applyAsInt(10,30)); //40
}
}
Consumer
consumer won't return anything, it just accept the data and consume it.
consumer interface and method signature
public interface java.util.function.Consumer<T> {
public abstract void accept(T);
public default java.util.function.Consumer<T> andThen(java.util.function.Consumer<? super T>);
}
Demo code
import java.util.function.Consumer;
public class ConsumerDemo {
public static void main(String[] args) {
Consumer<String> printName = System.out::println;
printName.accept("Amol");// output - Amol
}
}
Consumer Chaining
import java.util.function.Consumer;
public class ConsumerDemo1 {
public static void main(String[] args) {
Consumer<String> message1 = s -> System.out.print("lets learn "+s);
Consumer<String> message2 = s -> System.out.println(" In as simple way");
Consumer<String> c = message1.andThen(message2);
c.accept("consumer");//output - lets learn consumer In as simple way
}
}
BiConsumer
Demo code - create arraylist using the BiConsumer concept
import java.util.List;
import java.util.Arrays;
import java.util.function.BiConsumer;
public class BiConsumerDemo {
public static void main(String[] args) {
BiConsumer<Integer,Integer> list = (a,b) -> {
List<Integer> l = Arrays.asList(a,b);
System.out.println(l);
};
list.accept(10,20); //[10, 20]
}
}
Primitive types Consumer
IntConsumer
LongConsumer
DoubleConsumer
ObjIntConsumer
ObjLongConsumer
ObjDoubleConsumer
Supplier
Supplier won't take any input, it supplies only required object.
Interface and method signature
public interface java.util.function.Supplier<T> {
public abstract T get();
}
Example1
import java.util.function.Supplier;
public class SupplierDemo {
public static void main(String[] args) {
Supplier<Date> d = ()-> new Date();
System.out.println(d.get()); //output - Sat Apr 11 11:25:48 IST 2026
}
}
Example2 - Supplier to get 6 digit random OTP
import java.util.function.Supplier;
public class SupplierDemo1 {
public static void main(String[] args) {
Supplier<String> randomOtp = ()-> {
String otp = "";
for (int i = 0; i < 6; i++) {
otp += (int)(Math.random()*10);
}
return otp;
};
System.out.println(randomOtp.get());
System.out.println(randomOtp.get());
System.out.println(randomOtp.get());
System.out.println(randomOtp.get());
}
}
sample output - It is not necessary you will also get the same output
302627
225098
355752
986310

