Java Notes
Complete guide to Functional Interfaces in Java 8 — @FunctionalInterface annotation, built-in interfaces (Predicate, Function, Consumer, Supplier), composition, and custom interfaces.
What is a Functional Interface?
A Functional Interface is an interface that contains exactly one abstract method. It can have any number of default or static methods. Functional interfaces are the foundation of lambda expressions — every lambda targets a functional interface.
@FunctionalInterface
public interface Calculator {
int compute(int a, int b); // Single abstract method (SAM)
// ✅ Default methods are allowed
default void printResult(int a, int b) {
System.out.println("Result: " + compute(a, b));
}
// ✅ Static methods are allowed
static Calculator add() {
return (a, b) -> a + b;
}
// ✅ Methods from Object class don't count
boolean equals(Object obj);
String toString();
}The @FunctionalInterface Annotation
This annotation is optional but highly recommended:
@FunctionalInterface // Compiler enforces single abstract method
interface Greeting {
void greet(String name);
// void farewell(String name); // ❌ Compile error if uncommented
}Without the annotation, the interface is still functional if it has one abstract method — the annotation just enables compile-time checking.
Primitive Specializations
To avoid autoboxing overhead, Java provides primitive versions:
// Instead of Predicate<Integer> - avoids boxing
IntPredicate isEven = n -> n % 2 == 0;
LongPredicate isPositiveLong = n -> n > 0L;
DoublePredicate isFinite = Double::isFinite;
// Instead of Function<Integer, Integer>
IntUnaryOperator doubler = n -> n * 2;
IntFunction<String> intToString = Integer::toString;
ToIntFunction<String> stringLength = String::length;
// Instead of Consumer<Integer>
IntConsumer printInt = System.out::println;
// Instead of Supplier<Integer>
IntSupplier randomInt = () -> (int)(Math.random() * 100);
// Performance comparison
long start = System.nanoTime();
IntStream.range(0, 1_000_000).filter(n -> n % 2 == 0).sum(); // Fast (no boxing)
long primitiveTime = System.nanoTime() - start;
start = System.nanoTime();
Stream.iterate(0, n -> n + 1).limit(1_000_000)
.filter(n -> n % 2 == 0)
.mapToInt(Integer::intValue).sum(); // Slower (boxing/unboxing)
long boxedTime = System.nanoTime() - start;Composing Functional Interfaces
Building Complex Logic from Simple Pieces
false (too short) true "hello-world-"
Custom Functional Interfaces
Tri-Function (Java doesn't provide this)
@FunctionalInterface
interface TriFunction<A, B, C, R> {
R apply(A a, B b, C c);
default <V> TriFunction<A, B, C, V> andThen(Function<R, V> after) {
return (a, b, c) -> after.apply(this.apply(a, b, c));
}
}
// Usage
TriFunction<Integer, Integer, Integer, Integer> volume = (l, w, h) -> l * w * h;
TriFunction<Integer, Integer, Integer, String> volumeDesc =
volume.andThen(v -> "Volume: " + v + " cubic units");
System.out.println(volumeDesc.apply(3, 4, 5)); // "Volume: 60 cubic units""Volume: 60 cubic units"
Checked Function (handles exceptions)
@FunctionalInterface
interface CheckedFunction<T, R> {
R apply(T t) throws Exception;
static <T, R> Function<T, R> unchecked(CheckedFunction<T, R> f) {
return t -> {
try {
return f.apply(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
}
// Usage - parsing URLs without try-catch clutter
List<URL> urls = paths.stream()
.map(CheckedFunction.unchecked(URL::new))
.collect(Collectors.toList());Real-World Usage Patterns
Specification Pattern
Interview Questions
Q1: What makes an interface "functional"?
Answer: Exactly one abstract method (SAM — Single Abstract Method). Default methods, static methods, and methods inherited from Object don't count.
Q2: Name the four core functional interfaces and their method signatures.
Answer: Predicate<T> → boolean test(T t), Function<T,R> → R apply(T t), Consumer<T> → void accept(T t), Supplier<T> → T get().
Q3: What's the difference between Function.compose() and Function.andThen()?
Answer: f.andThen(g) applies f first, then g: g(f(x)). f.compose(g) applies g first, then f: f(g(x)).
Q4: Why do IntPredicate, LongConsumer, etc. exist?
Answer: To avoid autoboxing/unboxing overhead. Using IntPredicate instead of Predicate<Integer> avoids creating Integer wrapper objects, improving performance significantly in high-throughput scenarios.
Q5: Can a functional interface extend another interface?
Answer: Yes, but the resulting interface must still have only one abstract method (either inherited or its own, minus any that match Object methods).
Q6: What happens if @FunctionalInterface is on an interface with 2 abstract methods?
Answer: Compilation error. The annotation is a compile-time check. Without the annotation, the interface simply isn't functional and can't be used as a lambda target.
Q7: Write a generic retry mechanism using Supplier.
Attempt " + i + " failed, retrying...
Summary
In this chapter, we learned about Functional Interface in Java 8 in detail. Here are the key points:
- Basic introduction to the concept and why it is needed
- Syntax and structure with complete examples
- Internal working and best practices
- Real-world applications and use cases
- Common mistakes and how to avoid them
- How this topic is asked in interviews
To practice these concepts, write and run the code in your IDE and verify the output. Modify each example and experiment so that you deeply understand the concept.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Functional Interface in Java 8.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Java Master Course topic.
Search Terms
java-master-course, java master course, java, master, course, features, functional, interface
Related Java Master Course Topics