Q17 of 40 · Core Java

What are functional interfaces? Provide 3 examples from the standard library.

Core JavaMidfunctional-interfaceslambdajava-streamsjava-functional-programming

Short answer

Short answer: A functional interface has exactly one abstract method and can be the target of a lambda or method reference. The @FunctionalInterface annotation is optional but enforces the contract at compile time. Key standard examples: Predicate<T> (test), Function<T,R> (apply), Consumer<T> (accept), Supplier<T> (get), Runnable (run).

Detail

A functional interface is any interface with a single abstract method (SAM). It can have any number of default or static methods. The @FunctionalInterface annotation is optional — Java will treat any SAM interface as functional — but adding it causes the compiler to verify that the interface still has exactly one abstract method, guarding against accidental addition of a second one.

Lambdas are syntactic sugar for anonymous implementations of a functional interface. When you write list.removeIf(s -> s.isEmpty()), the lambda s -> s.isEmpty() is treated as an instance of Predicate<String>.

Three key standard-library functional interfaces (from java.util.function):

  1. Predicate<T> — takes a T, returns boolean. Abstract method: test(T t). Used in Stream.filter(), List.removeIf(), Optional.filter(). Useful for reusable filter conditions in test data selection.

  2. Function<T, R> — takes a T, returns an R. Abstract method: apply(T t). Used in Stream.map(). Compose with andThen() and compose() for transformation pipelines.

  3. Consumer<T> — takes a T, returns nothing. Abstract method: accept(T t). Used in Stream.forEach(), Optional.ifPresent(). Appropriate for side effects: logging, assertions, sending data.

Other commonly cited: Supplier<T> (takes nothing, returns T — used in Optional.orElseGet(), lazy initialisation), BiFunction<T,U,R>, UnaryOperator<T>.

// EXAMPLE

FunctionalInterfaces.java

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

// Predicate<T> — returns boolean, used for filtering
Predicate<String> isSmoke = tag -> "smoke".equals(tag);
Predicate<String> isShort = tag -> tag.length() <= 4;
Predicate<String> isSmokeOrShort = isSmoke.or(isShort); // composable

List<String> smokeTags = tags.stream()
    .filter(isSmoke)
    .toList();

// Function<T,R> — transforms T to R
Function<String, Integer> wordCount = s -> s.split("\s+").length;
Function<String, String> trim = String::trim; // method reference
Function<String, Integer> trimThenCount = trim.andThen(wordCount);

// Consumer<T> — side effect, no return value
Consumer<TestResult> logger = r ->
    System.out.printf("[%s] %s%n", r.passed() ? "PASS" : "FAIL", r.id());

results.forEach(logger);
result.ifPresent(logger); // Optional.ifPresent() takes a Consumer

// Supplier<T> — produces a value, takes nothing
Supplier<List<String>> emptyTagList = ArrayList::new;
Map<String, List<String>> grouped = new HashMap<>();
grouped.computeIfAbsent("smoke", k -> emptyTagList.get());

// Custom functional interface
@FunctionalInterface
interface TestDataBuilder<T> {
    T build(long seed);
}
TestDataBuilder<User> userFactory = seed -> new User("user" + seed);

// WHAT INTERVIEWERS LOOK FOR

Correct SAM definition, the @FunctionalInterface annotation's compile-time purpose, and fluency with at least three standard interfaces. Strong answers show composition (Predicate.and/or, Function.andThen) and method references alongside lambdas.

// COMMON PITFALL

Listing only Runnable and Callable and missing java.util.function entirely. The java.util.function package has 43 functional interfaces — knowing Predicate, Function, Consumer, Supplier, and BiFunction by name and signature is the expected baseline.