Java Functional Interfaces Cheat Sheet + Examples
Java Functional Interfaces
PREDICATE | takes one (or two) argument(s) and returns a boolean (5 variants) |
UNARY OPERATOR | result and the single argument types are the same (4 variants) |
BINARY OPERATOR | result and both argument types are the same (4 variants) |
FUNCTION | result and one (or two) argument(s) types are different (17 variants) |
SUPPLIER | takes no arguments, returns a value ( 5 variants ) |
CONSUMER | takes one (or two) arguments and returns no value (8 variants) |
One Simple Example
// Predicate Predicate<Integer> isPositive = num -> num > 0; System.out.println(isPositive.test(5)); // Output: true // Unary Operator UnaryOperator<Integer> square = num -> num * num; System.out.println(square.apply(5)); // Output: 25 // Binary Operator BinaryOperator<Integer> sum = (num1, num2) -> num1 + num2; System.out.println(sum.apply(5, 3)); // Output: 8 // Function Function<String, Integer> strLength = str -> str.length(); System.out.println(strLength.apply("Hello")); // Output: 5 // Supplier Supplier<Double> randomDouble = () -> Math.random(); System.out.println(randomDouble.get()); // Output: Random double value // Consumer Consumer<String> printUpperCase = str -> System.out.println(str.toUpperCase()); printUpperCase.accept("hello"); // Output: HELLO
Supplier Interface
The Supplier Interface does not allow input, it returns a value based on a defined type. It is like a function without parameters, with a return type. Now, let’s see this behavior with examples.
public class SupplierInterface { //Supplier function declarations. Supplier textSupplier = () -> "Hello SW Test Academy!"; Supplier numberSupplier = () -> 1234; Supplier randomSupplier = () -> Math.random(); Supplier randomSupplierMR = Math::random; //With Method Reference (MR) @Test public void supplierTest() { //Calling Supplier functions. System.out.println(textSupplier.get()); System.out.println(numberSupplier.get()); System.out.println(randomSupplier.get()); System.out.println(randomSupplierMR.get()); } }
Consumer Interface
The Consumer Interface takes an input, it does not return a value. It is like a function with a parameter, without a return type. BiConsumer Interface takes two inputs and does not return anything. That’s why it is called “Bi” Consumer. If we chain multiple consumers with andThen method, first the first consumer will be executed and the second one executed. It is working like left to right flow.
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class ConsumerInterface { //Consumer function declarations. Consumer upperCaseConsumer = (text) -> System.out.println(text.toUpperCase()); Consumer lowerCaseConsumer = (text) -> System.out.println(text.toLowerCase()); Consumer logOfTenConsumer = (number) -> System.out.println(Math.log10(number)); //BiConsumer takes two parameters and does not return anything! BiConsumer<Integer, Integer> powConsumer = (base, power) -> System.out.println(Math.pow(base, power)); @BeforeEach public void setup(TestInfo testInfo) { System.out.println("Test name: " + testInfo.getDisplayName()); } @AfterEach public void tearDown(){ System.out.println(); } @Order(1) @Test public void consumerTest() { //Calling Consumer functions. upperCaseConsumer.accept("Hello SW Test Academy!"); lowerCaseConsumer.accept("Hello SW Test Academy!"); logOfTenConsumer.accept(1000.00); } @Order(2) @Test public void biConsumerTest() { //Calling BiConsumer function. powConsumer.accept(3,2); } @Order(3) @Test public void consumerChainTest() { //Consumer chaining with andThen method. upperCaseConsumer .andThen(lowerCaseConsumer) .accept("Hello SW Test Academy!"); } }
Function Interface
The Function Interface takes an input, it returns a defined type. It is like a function with a parameter, with a return type. The first declaration is input, the second is the return type. The BiFunction Interface takes two inputs rather than one input. That’s the only difference between Function and BiFunction interfaces. Also, UnaryOperator interface takes and returns the same type.
If we chain the functions with andThen method, the execution order will be like the left to right flow. First, the first function will be run, then the others will be run. If we want to run these functions right to left we can use compose method rather than the andThen method.
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class FunctionInterface { //FunctionInterface function declarations. (Input Type, Return Type) Function toUpperCase = (text) -> text.toUpperCase(); Function toLowerCase = (text) -> text.toLowerCase(); Function log10 = (number) -> Math.log10(number); //Method Reference Declarations (Input Type, Return Type) Function<String, String> toUpperCaseMR = String::toUpperCase; Function<String, String> toLowerCaseMR = String::toLowerCase; Function<Integer, Double> log10MR = Math::log10; //BiFunction Example (Input Type, Input Type, Return Type) BiFunction<Integer, Integer, Integer> powerOf = (base, power) -> (int) Math.pow(base, power); //UnaryOperator Example (Input and Return type are same.) UnaryOperator<String> appendText = (text) -> "I am appending: " + text; @BeforeEach public void setup(TestInfo testInfo) { System.out.println("Test name: " + testInfo.getDisplayName()); } @AfterEach public void tearDown(){ System.out.println(); } @Order(1) @Test public void functionTest() { //Calling functions. String upperCaseResult = toUpperCase.apply("hello sw test academy!"); Double log10Result = log10.apply(10000); System.out.println(upperCaseResult); System.out.println(log10Result); } @Order(2) @Test public void functionChainWithAndThen() { //Function chaining. First do the first function then do the second one. String chainResult1 = toUpperCase.andThen(toLowerCase).apply("heLLo sW teSt ACadEmy!"); String chainResult2 = toLowerCase.andThen(toUpperCase).apply("heLLo sW teSt ACadEmy!"); System.out.println(chainResult1); System.out.println(chainResult2); } @Order(3) @Test public void functionChainWithCompose() { //Function chaining. First do the second function then do the first one. Vise versa of andThen. String chainResult1 = toUpperCase.compose(toLowerCase).apply("heLLo sW teSt ACadEmy!"); String chainResult2 = toLowerCase.compose(toUpperCase).apply("heLLo sW teSt ACadEmy!"); System.out.println(chainResult1); System.out.println(chainResult2); } @Order(4) @Test public void biFunctionTest() { //Calling functions. int result = powerOf.apply(3, 2); System.out.println("Power of 3 over 2 is: " + result); } @Order(5) @Test public void unaryOperatorTest(){ //Calling UnaryOperator System.out.println(appendText.apply("Hello SW Test Academy!")); } }
Predicate Interface
The predicate takes an input, it returns a boolean value as true or false. It is like a function with a parameter, with the boolean return type. BiPredicate Interface takes two inputs and returns a boolean value.
Negate method does the inversion for the value.
And method is working as logical AND operation.
Or method is working as a logical OR operation.
Let’s see all of these with examples.
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class PredicateInterface { //Predicate function declaration. String sampleText = "Hello SW Test Academy"; Predicate containsPredicate = (text) -> sampleText.contains(text); //BiPredicate function declaration. BiPredicate<String, String> containsBiPredicate = (text, pattern) -> text.contains(pattern); BiPredicate<String, String> containsBiPredicateMR = String::contains; //Method reference version. @BeforeEach public void setup(TestInfo testInfo) { System.out.println("Test name: " + testInfo.getDisplayName()); } @AfterEach public void tearDown(){ System.out.println(); } @Order(1) @Test public void predicateTest() { //Calling Predicate functions. boolean result = containsPredicate.test("SW"); boolean resultOfNegate = containsPredicate.negate().test("SW"); //negate is inverse operation like "does not contain". boolean andResult = containsBiPredicate.and(containsBiPredicate.negate()).test("SW", "SW"); //Logical AND operation. boolean orResult = containsBiPredicate.or(containsBiPredicate.negate()).test("SW", "SW"); //Logical OR operation. System.out.println(result); System.out.println(resultOfNegate); System.out.println(andResult); System.out.println(orResult); } @Order(2) @Test public void predicateListTest() { List<Predicate<String>> predicateList = new ArrayList<>(); predicateList.add(containsPredicate); predicateList.add(containsPredicate.negate()); predicateList .forEach(predicate -> System.out.println(predicate.test("SW"))); } @Order(3) @Test public void biPredicateTest() { //Calling BiPredicate functions. boolean result = containsBiPredicate.test("Hello SW Test Academy", "SW"); System.out.println(result); } }
Runnable Interface
The Runnable does not allow input, it does not return value. It is like a function without parameters, without return type. Let’s make it tangible with examples.
public class RunnableInterface { //Runnable function declarations. Runnable runFunction = () -> System.out.println("I am running!"); @Test public void runnableTest() { //Calling Runnable functions. runFunction.run(); } //Running Asynchronously public static void main(String[] args) { Runnable runFunction = () -> System.out.println("I am running!"); Runnable runWithDelay = () -> { Uninterruptibles.sleepUninterruptibly(3000, TimeUnit.MILLISECONDS); System.out.println("I am running Asynchronously!"); }; new Thread(runWithDelay).start(); runFunction.run(); } }
Callable Interface
Callable does not allow input, it returns a value. It is like a function without parameters, with the return type. An example is as follows.
public class CallableInterface { //Callable function declarations. Callable callFunction = () -> Math.random() * 100; Supplier supplierFunction = () -> Math.random() * 100; @SneakyThrows @Test public void callableTest() { //Calling functions. int callResult = callFunction.call().intValue(); int getResult = supplierFunction.get().intValue(); System.out.println(callResult); System.out.println(getResult); } }