Java 8 Functional Interfaces
Java 8 introduced functional interfaces, which are interfaces that have exactly one abstract method. Functional interfaces are important…
Java 8 Functional Interfaces
Java 8 introduced functional interfaces, which are interfaces that have exactly one abstract method. Functional interfaces are important because they can be used to implement lambda expressions, which provide a concise way to write code that is more expressive and less verbose.
There are several built-in functional interfaces in Java 8. In this answer, we will provide an in-depth explanation of each type with code examples and demonstrate how to use lambda expressions with each functional interface.
Predicate Interface:
The Predicate interface represents a boolean-valued function that takes one argument. The input type is specified by the type parameter of the interface. It is often used to test whether a value satisfies a certain condition.
Some common methods present in the Predicate interface are:
test(T t): This method takes a value of typeTand returns a Boolean result based on whether the value satisfies the condition specified by the predicate.and(Predicate<? super T> other): This method returns a new predicate that represents the logical AND of the original predicate and another predicate.or(Predicate<? super T> other): This method returns a new predicate that represents the logical OR of the original predicate and another predicate.negate(): This method returns a new predicate that represents the logical negation of the original predicate.
Predicate<Integer> isPositive = n -> n > 0;
System.out.println(isPositive.test(5)); // true System.out.println(isPositive.test(-5)); // false
In this example, we define a Predicate that checks whether an integer is positive. We use a lambda expression to implement the test method of the Predicate.
We can also use the and, or, and negate methods of the Predicate interface to combine or modify predicates. Here's an example:
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> isPositiveAndEven = isPositive.and(isEven); System.out.println(isPositiveAndEven.test(6)); // true System.out.println(isPositiveAndEven.test(-6)); // false System.out.println(isPositiveAndEven.test(5)); // false
In this example, we define a Predicate that checks whether an integer is even. We then use the and method to combine it with the isPositive predicate, creating a new predicate that checks whether an integer is both positive and even.
working code of Predicate where we use the test, negate, and or methods:
import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEven = n -> n % 2 == 0;
// Test method
System.out.println(isPositive.test(5)); // true
System.out.println(isPositive.test(-5)); // false
System.out.println(isEven.test(6)); // true
System.out.println(isEven.test(5)); // false
// Negate method
Predicate<Integer> isNegative = isPositive.negate();
System.out.println(isNegative.test(5)); // false
System.out.println(isNegative.test(-5)); // true
// Or method
Predicate<Integer> isPositiveOrEven = isPositive.or(isEven);
System.out.println(isPositiveOrEven.test(6)); // true
System.out.println(isPositiveOrEven.test(-6)); // false
System.out.println(isPositiveOrEven.test(5)); // true
}
}
In this example, we first define two predicates: isPositive and isEven. The isPositive predicate checks whether an integer is positive, and the isEven predicate checks whether an integer is even.
We then use the test method to test whether certain integers satisfy the conditions specified by the predicates. We also use the negate method to create a new predicate that checks whether an integer is negative, and the or method to create a new predicate that checks whether an integer is either positive or even.
Function Interface:
The Function interface is a functional interface that represents a function that takes one argument and returns a result. The input type is specified by the type parameter of the interface, and the output type is specified by the type parameter of the method. It is often used to transform or map a value from one type to another.
The Function interface has one abstract method, apply(T t), that takes an argument of type T and returns a result of type R. Here's an example of a simple Function implementation that doubles an integer:
import java.util.function.Function;public class FunctionExample {
public static void main(String[] args) {
Function<Integer, Integer> doubler = n -> n * 2;
System.out.println(doubler.apply(5)); // 10
}
}
In this example, we define a Function that doubles an integer. We use a lambda expression to implement the apply method of the Function
Here are some popular methods of the Function interface and their explanations:
andThen(Function<? super R, ? extends V> after): This method returns a newFunctionthat applies the current function and then theafterfunction. Here's an example:
Function<Integer, Integer> doubler = n -> n * 2;
Function<Integer, Integer> addOne = n -> n + 1;
Function<Integer, Integer> doublerAndAddOne = doubler.andThen(addOne);
System.out.println(doublerAndAddOne.apply(5)); // 11
In this example, we define two Function objects: doubler and addOne. We then use the andThen method to chain the two Function objects together, creating a new Function that first doubles an integer and then adds one.
compose(Function<? super V, ? extends T> before): This method returns a newFunctionthat applies thebeforefunction and then the current function. Here's an example:
Function<Integer, Integer> doubler = n -> n * 2;
Function<Integer, Integer> subtractOne = n -> n - 1;
Function<Integer, Integer> doublerAndSubtractOne = doubler.compose(subtractOne)
System.out.println(doublerAndSubtractOne.apply(5)); // 8
In this example, we define two Function objects: doubler and subtractOne. We then use the compose method to chain the two Function objects together, creating a new Function that first subtracts one from an integer and then doubles it.
3 identity(): This method returns a Function that simply returns its input argument. Here's an example:
Function<String, String> identity = Function.identity();
System.out.println(identity.apply("hello")); // "hello"
In this example, we define a Function object using the identity method. This function simply returns its input argument, so when we call it with the string "hello", it returns "hello".
Here’s a full working example code that demonstrates the use of the Function interface and all of its methods:
import java.util.function.Function;public class FunctionExample {
public static void main(String[] args) {
Function<Integer, Integer> doubler = n -> n * 2;
Function<Integer, Integer> addOne = n -> n + 1;
Function<Integer, Integer> subtractOne = n -> n - 1;
Function<Integer, Integer> doublerAndAddOne = doubler.andThen(addOne);
System.out.println(doublerAndAddOne.apply(5)); // 11
Function<Integer, Integer> doublerAndSubtractOne = doubler.compose(subtractOne);
System.out.println(doublerAndSubtractOne.apply(5)); // 8
Function<String, String> identity = Function.identity();
System.out.println(identity.apply("hello")); // "hello"
}
}
Consumer Interface:
The Consumer functional interface is used to represent an operation that accepts a single argument and returns no result. It has one abstract method, accept(T t), which accepts an argument of type T and returns void. The Consumer interface is often used in scenarios where you need to perform some kind of side-effect on an object, such as printing to the console or updating a database.
Here are some of the common methods of the Consumer interface along with examples of each:
andThen(Consumer<? super T> after): This method returns a newConsumerthat performs the current operation and then theafteroperation. Here's an example:
Consumer<String> printUpperCase = str -> System.out.println(str.toUpperCase());
Consumer<String> printLowerCase = str -> System.out.println(str.toLowerCase());
Consumer<String> printBoth = printUpperCase.andThen(printLowerCase);
printBoth.accept("Hello World"); // prints "HELLO WORLD\nhello world"
In this example, we define two Consumer objects: printUpperCase and printLowerCase. We then use the andThen method to chain the two Consumer objects together, creating a new Consumer that first prints the input string in uppercase, and then prints it in lowercase.
accept(T t): This is the main abstract method of theConsumerinterface, which accepts an argument of typeTand returns void. Here's an example:
Consumer<Integer> printNumber = num -> System.out.println(num);
printNumber.accept(42); // prints "42"
In this example, we define a Consumer object called printNumber, which simply prints its input argument to the console.
forEach(Iterable<? extends T> elements): This method takes anIterableof typeTand applies theacceptmethod to each element in the collection. Here's an example:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Consumer<String> printName = name -> System.out.println("Hello, " + name + "!");
names.forEach(printName);
In this example, we define a list of names and a Consumer object called printName, which prints a personalized greeting for each name. We then use the forEach method to apply the printName operation to each element in the names list.
Here’s a full working example that demonstrates the use of the Consumer interface and its common methods:
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class ConsumerExample {
public static void main(String[] args) {
Consumer<String> printUpperCase = str -> System.out.println(str.toUpperCase());
Consumer<String> printLowerCase = str -> System.out.println(str.toLowerCase());
Consumer<String> printBoth = printUpperCase.andThen(printLowerCase);
printBoth.accept("Hello World");
Consumer<Integer> printNumber = num -> System.out.println(num);
printNumber.accept(42);
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Consumer<String> printName = name -> System.out.println("Hello, " + name + "!");
names.forEach(printName);
}
}
Supplier Interface:
The Supplier functional interface is used to represent a supplier of results, which has no input arguments. It has one abstract method, get(), which returns an object of type T. The Supplier interface is often used in scenarios where you need to generate some kind of value dynamically.
Here are some of the common methods of the Supplier interface along with examples of each:
get(): This is the main abstract method of theSupplierinterface, which returns an object of typeT. Here's an example:
Supplier<Integer> randomNumber = () -> (int)(Math.random() * 100);
System.out.println(randomNumber.get());
In this example, we define a Supplier object called randomNumber, which generates a random integer between 0 and 100 (exclusive) every time the get() method is called. We then call the get() method on the randomNumber object to generate and print a random number to the console.
orElse(T other): This method returns the value generated by theget()method, or the specified default value if theget()method returns null. Here's an example:
Supplier<String> helloWorld = () -> "Hello World";
System.out.println(helloWorld.orElse("Default Value"));
In this example, we define a Supplier object called helloWorld, which returns the string "Hello World" every time the get() method is called. We then call the orElse method on the helloWorld object to specify a default value ("Default Value") in case the get() method returns null.
andThen(Function<? super T, ? extends U> after): This method returns a newSupplierthat performs the current operation and then theafteroperation. Here's an example:
Supplier<Integer> randomNumber = () -> (int)(Math.random() * 100);
Function<Integer, Integer> addOne = num -> num + 1;
Supplier<Integer> generateRandomPlusOne = randomNumber.andThen(addOne);
System.out.println(generateRandomPlusOne.get());
In this example, we define a Supplier object called randomNumber, which generates a random integer between 0 and 100 (exclusive) every time the get() method is called. We then define a Function object called addOne, which adds one to its input argument. Finally, we use the andThen method to chain the randomNumber and addOne operations together, creating a new Supplier that first generates a random number and then adds one to it.
Here’s a full working example that demonstrates the use of the Supplier interface and its common methods:
import java.util.function.Supplier;
import java.util.function.Function;
public class SupplierExample {
public static void main(String[] args) {
Supplier<Integer> randomNumber = () -> (int)(Math.random() * 100);
System.out.println(randomNumber.get());
Supplier<String> helloWorld = () -> "Hello World";
System.out.println(helloWorld.orElse("Default Value"));
Function<Integer, Integer> addOne = num -> num + 1;
Supplier<Integer> generateRandomPlusOne = randomNumber.andThen(addOne);
System.out.println(generateRandomPlusOne.get());
}
}
BiFunction Interface:
The BiFunction functional interface is used to represent a function that accepts two arguments of type T and U, and returns an object of type R. It has one abstract method, apply(T t, U u), which takes two arguments of type T and U and returns an object of type R. The BiFunction interface is often used in scenarios where you need to perform some kind of operation that involves two input parameters.
Here are some of the common methods of the BiFunction interface along with examples of each:
apply(T t, U u): This is the main abstract method of theBiFunctioninterface, which takes two arguments of typeTandUand returns an object of typeR. Here's an example:
BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;
System.out.println(add.apply(2, 3));
In this example, we define a BiFunction object called add, which takes two integers as input and returns their sum. We then call the apply() method on the add object to perform the addition operation on the integers 2 and 3.
andThen(Function<? super R, ? extends V> after): This method returns a newBiFunctionthat performs the current operation and then theafteroperation. Here's an example:
BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;
Function<Integer, String> toString = num -> Integer.toString(num);
BiFunction<Integer, Integer, String> addToString = add.andThen(toString);
System.out.println(addToString.apply(2, 3));
In this example, we define a BiFunction object called add, which takes two integers as input and returns their sum. We then define a Function object called toString, which converts an integer to a string. Finally, we use the andThen method to chain the add and toString operations together, creating a new BiFunction that first performs the addition operation and then converts the result to a string.
Here’s a full working example that demonstrates the use of the BiFunction interface and its common methods:
import java.util.function.BiFunction;
import java.util.function.Function;
public class BiFunctionExample {
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;
System.out.println(add.apply(2, 3));
Function<Integer, String> toString = num -> Integer.toString(num);
BiFunction<Integer, Integer, String> addToString = add.andThen(toString);
System.out.println(addToString.apply(2, 3));
}
}
UnaryOperator Interface:
The UnaryOperator functional interface is a specialization of the Function interface, where the input and output types are the same. It represents an operation that takes a single input of type T and returns an output of the same type T. It has one abstract method, apply(T t), which takes an argument of type T and returns a result of the same type.
Here are some of the common methods of the UnaryOperator interface along with examples of each:
apply(T t): This is the main abstract method of theUnaryOperatorinterface, which takes an argument of typeTand returns a result of the same typeT. Here's an example:
UnaryOperator<Integer> square = num -> num * num;
System.out.println(square.apply(5));
In this example, we define a UnaryOperator object called square, which takes an integer as input and returns its square. We then call the apply() method on the square object to calculate the square of the integer 5.
andThen(UnaryOperator<T> after): This method returns a newUnaryOperatorthat performs the current operation and then theafteroperation. Here's an example:
UnaryOperator<Integer> square = num -> num * num;
UnaryOperator<Integer> doubleSquare = square.andThen(num -> num * 2);
System.out.println(doubleSquare.apply(5));
In this example, we define a UnaryOperator object called square, which takes an integer as input and returns its square. We then use the andThen method to chain the square and double operations together, creating a new UnaryOperator that first calculates the square of the input integer and then doubles the result.
Here’s a full working example that demonstrates the use of the UnaryOperator interface and its common methods:
import java.util.function.UnaryOperator;
public class UnaryOperatorExample {
public static void main(String[] args) {
UnaryOperator<Integer> square = num -> num * num;
System.out.println(square.apply(5));
UnaryOperator<Integer> doubleSquare = square.andThen(num -> num * 2);
System.out.println(doubleSquare.apply(5));
}
}
BinaryOperator Interface:
The BinaryOperator functional interface is another specialization of the Function interface, where both the input and output types are the same. It represents an operation that takes two inputs of type T and returns an output of the same type T. It has one abstract method, apply(T t, T u), which takes two arguments of type T and returns a result of the same type.
Here are some common methods of the BinaryOperator interface along with examples of each:
apply(T t, T u): This is the main abstract method of theBinaryOperatorinterface, which takes two arguments of typeTand returns a result of the same typeT. Here's an example:
BinaryOperator<Integer> add = (num1, num2) -> num1 + num2;
System.out.println(add.apply(5, 10));
In this example, we define a BinaryOperator object called add, which takes two integers as input and returns their sum. We then call the apply() method on the add object to calculate the sum of the integers 5 and 10.
minBy(Comparator<T> comparator)andmaxBy(Comparator<T> comparator): These methods return a newBinaryOperatorthat returns the minimum or maximum of two values based on the specifiedComparator. Here's an example:
BinaryOperator<Integer> max = BinaryOperator.maxBy(Integer::compare);
System.out.println(max.apply(5, 10));
BinaryOperator<Integer> min = BinaryOperator.minBy(Integer::compare);
System.out.println(min.apply(5, 10));
In this example, we define a BinaryOperator object called max using the maxBy() method, which takes two integers as input and returns the larger of the two. Similarly, we define a BinaryOperator object called min using the minBy() method, which takes two integers as input and returns the smaller of the two.
Here’s a full working example that demonstrates the use of the BinaryOperator interface and its common methods:
import java.util.function.BinaryOperator;
public class BinaryOperatorExample {
public static void main(String[] args) {
BinaryOperator<Integer> add = (num1, num2) -> num1 + num2;
System.out.println(add.apply(5, 10));
BinaryOperator<Integer> max = BinaryOperator.maxBy(Integer::compare);
System.out.println(max.apply(5, 10));
BinaryOperator<Integer> min = BinaryOperator.minBy(Integer::compare);
System.out.println(min.apply(5, 10));
}
}
Interview Tip: after reading this article you should be able to answer the following interview questions.
- Can you explain the difference between a functional interface and a regular interface in Java?
- Can you give an example of a built-in functional interface in Java?
- How do you use lambda expressions with functional interfaces?
- Can you explain the purpose of the Function interface in Java?
- Can you give an example of using the Predicate interface in Java?
- How do you compose two functions in Java?
- Can you explain the difference between a UnaryOperator and a Function in Java?
- Can you give an example of using the Consumer interface in Java?
- Can you explain the difference between a BiFunction and a BinaryOperator in Java?
- Can you give an example of using the Supplier interface in Java?
메타데이터
- post_id
- d6ade6fefe8b
- slug
- java-8-functional-interfaces-d6ade6fefe8b
- url
- https://medium.com/@ashwth94/java-8-functional-interfaces-d6ade6fefe8b
- canonical_url
- https://medium.com/@ashwth94/java-8-functional-interfaces-d6ade6fefe8b
- author_url
- https://medium.com/@ashwth94
- status
- ok
- fetched_at
- 2026-06-29 01:02:39