Java 8 provides several ready-to-use functional interfaces in the java.util.function package. These interfaces make it easy to write cleaner and reusable code. Let’s start with one of the most commonly used:
Predicate<T>
A Predicate is a functional interface that represents a single argument function that returns a boolean result. It is commonly used for evaluating conditions and making decisions in your program. It has one abstract method:
boolean test(T t);
This method takes an input of type T and returns true or false depending on whether the input satisfies the condition defined in the test.
Example:
Predicate isLessThan5 = str -> str.length() < 5; System.out.println(isLessThan5.test("Hi")); //true System.out.println(isShort.test("Hello")); //false
Using Predicate with Collections
Predicates are extremely useful when working with Java Streams to filter data.
Example:
import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; public class FilterExample { public static void main(String[] args) { List names = Arrays.asList("John", "Anna", "Chris", "Bob"); Predicate startsWithA = name -> name.startsWith("A"); List filtered = names.stream() .filter(startsWithA) .collect(Collectors.toList()); System.out.println(filtered); // Output: [Anna] } }
Predicate Methods
Java provides additional methods that help combine multiple conditions:
- and() – Combine two predicates with AND logic.
- or() – Combine with OR logic.
- negate() – Reverse the result.
Example:
Predicate longerThan3 = s -> s.length() > 3; Predicate startsWithJ = s -> s.startsWith("J"); Predicate combined = longerThan3.and(startsWithJ); System.out.println(combined.test("John")); // true System.out.println(combined.test("Jo")); // false
When to Use Predicate?
- Filtering a list
- Checking conditions
- Validation logic
Function<T, R>
A Function is a functional interface that takes an input of type T and returns a result of type R. It has one abstract method:
R apply(T t);
This means you give it something, and it gives you something back — often after performing a transformation or calculation.
Example:
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
Function<String, Integer> stringLength = str -> str.length();
System.out.println(stringLength.apply("Java")); // Output: 4
System.out.println(stringLength.apply("Function")); //Output: 8
}
}
Common Use Cases
- Converting one type to another (e.g., String to Integer)
- Performing calculations (e.g., square a number)
- Extracting or formatting data
Function Chaining with andThen() and compose():
You can chain functions together to perform multiple operations.
Function<Integer, Integer> multiplyBy2 = n -> n * 2; Function<Integer, Integer> add3 = n -> n + 3; Function<Integer, Integer> combined = multiplyBy2.andThen(add3); System.out.println(combined.apply(5)); // (5 * 2) + 3 = 13 Function<Integer, Integer> reverseCombined = multiplyBy2.compose(add3); System.out.println(reverseCombined.apply(5)); // (5 + 3) * 2 = 16
Consumer
A Consumer is a functional interface that takes an input and performs some action on it, but does not return any result. It’s often used when you just want to process or consume the input (like printing, logging, or storing). It has one abstract method:
void accept(T t);
Example:
import java.util.function.Consumer; public class Main { public static void main(String[] args) { Consumer printUpperCase = str -> System.out.println(str.toUpperCase()); printUpperCase.accept("hello"); // Output: HELLO } }
Use Cases:
- Printing to the console
- Storing data
- Logging
- Modifying objects
Chaining Consumers with andThen()
You can chain multiple consumers to perform a sequence of actions.
Consumer greet = str -> System.out.print("Hello, " + str); Consumer exclaim = str -> System.out.println("!"); Consumer combined = greet.andThen(exclaim); combined.accept("Alice"); // Output: Hello, Alice!
Supplier
A Supplier is a functional interface that does not take any input but supplies (or returns) a value. It is used when you want to generate or provide values without needing any parameters. It has one abstract method:
T get();
Example:
import java.util.function.Supplier; public class Main { public static void main(String[] args) { Supplier greetSupplier = () -> "Hello, World!"; System.out.println(greetSupplier.get()); // Output: Hello, World! } }
Use Cases:
- Lazy value generation (e.g., generate a random number only when needed)
- Supplying default values
- Creating objects
Another Example – Random Number:
import java.util.function.Supplier; import java.util.Random; public class Main { public static void main(String[] args) { Supplier randomSupplier = () -> new Random().nextInt(100); System.out.println("Random Number: " + randomSupplier.get()); } }
By mastering these interfaces, you’ll have a strong grip on functional programming in Java. These interfaces are not just theory—they are used widely in real-world applications, especially when working with collections and streams.