Java 8 Feature

    Functional Interfaces

    The foundation that makes lambda expressions possible in Java

    ?
    Predicate<T>
    T → boolean
    ƒ
    Function<T,R>
    T → R
    Consumer<T>
    T → void
    Supplier<T>
    () → T
    Predicate: n -> n > 0

    What is a Functional Interface?

    A Functional Interface is simply an interface with exactly one abstract method. That's it! This single method is the "function" that your lambda expression will implement.

    💡 Think of it this way:

    A functional interface is like a contract with one job. When you write a lambda, you're providing the implementation for that one job. Java knows which method you're implementing because there's only one choice!

    The Rules:

    Exactly ONE abstract method (the "functional method")
    Can have any number of default methods
    Can have any number of static methods
    Can have methods inherited from Object
    FunctionalInterfaceExample.java
    // This IS a functional interface (one abstract method)@FunctionalInterfacepublicinterfaceCalculator{intcalculate(int a,int b);// THE abstract method// These DON'T count against the "one method" rule:defaultvoidprintInfo(){System.out.println("Calculator interface");}staticCalculatorcreateAdder(){return(a, b)-> a + b;}}// This is NOT a functional interface (two abstract methods)publicinterfaceNotFunctional{voidmethod1();voidmethod2();// Oops! Two abstract methods}

    The @FunctionalInterface Annotation

    The @FunctionalInterface annotation is optional but highly recommended. It tells the compiler to verify that your interface follows the functional interface rules.

    ✅ What it does:

    • Compile-time check for exactly one abstract method
    • Prevents accidental addition of second abstract method
    • Documents your intent clearly
    • IDE support for lambda conversion

    ⚠️ Important:

    • The annotation is NOT required
    • Interfaces without it can still be functional
    • Runnable, Comparator work with lambdas (pre-Java 8)
    • Think of it like @Override – helpful safety check
    AnnotationExample.java
    // With annotation - compiler enforces the rule@FunctionalInterfacepublicinterfaceMyFunction{voidexecute();// void anotherMethod();  // Compiler ERROR if you add this!}// Without annotation - still works, but no safety netpublicinterfaceAlsoFunctional{voidexecute();// If someone adds another method, lambdas will break!}

    Built-in Functional Interfaces (java.util.function)

    Java 8 comes with a rich set of pre-built functional interfaces. You'll use these 90% of the time instead of creating your own!

    InterfaceInput → OutputMethodUse Case
    Predicate<T>T → booleantest(T)Filtering, checking conditions
    Function<T,R>T → Rapply(T)Transforming/mapping values
    Consumer<T>T → voidaccept(T)Performing actions (printing, saving)
    Supplier<T>() → Tget()Creating/providing values
    UnaryOperator<T>T → Tapply(T)Transform same type
    BinaryOperator<T>(T, T) → Tapply(T,T)Combining two values (add, max)

    🎯 Memory Trick:

    Predicate = tests (returns true/false)
    Function = transforms (input → different output)
    Consumer = consumes (takes input, returns nothing)
    Supplier = supplies (takes nothing, returns output)

    Detailed Examples of Each Interface

    BuiltInInterfaces.java
    importjava.util.function.*;// ========== PREDICATE: Test if something is true ==========// "Does this thing match my condition?"Predicate<String> isEmpty = s -> s.isEmpty();Predicate<Integer> isPositive = n -> n >0;Predicate<String> startsWithA = s -> s.startsWith("A");System.out.println(isEmpty.test(""));// trueSystem.out.println(isPositive.test(-5));// falseSystem.out.println(startsWithA.test("Alice"));// true// Used in: stream.filter(), removeIf(), Optional.filter()// ========== FUNCTION: Transform one thing to another ==========// "Convert X to Y"Function<String,Integer> length = s -> s.length();Function<Integer,String> intToString = n ->"Number: "+ n;System.out.println(length.apply("Hello"));// 5System.out.println(intToString.apply(42));// "Number: 42"// Used in: stream.map(), Optional.map()// ========== CONSUMER: Do something with the input ==========// "Take this and DO something (print, save, send)"Consumer<String> printer = s ->System.out.println(s);Consumer<List<String>> clearList = list -> list.clear();
    printer.accept("Hello World!");// Prints: Hello World!// Used in: forEach(), Optional.ifPresent()// ========== SUPPLIER: Create/provide something ==========// "Give me a value (I don't need any input)"Supplier<Double> randomNumber =()->Math.random();Supplier<LocalDate> today =()->LocalDate.now();Supplier<List<String>> newList =()->newArrayList<>();System.out.println(randomNumber.get());// 0.73628...System.out.println(today.get());// 2024-01-15// Used in: Optional.orElseGet(), Stream.generate()

    Two-Parameter Variants (Bi-Interfaces)

    When you need to work with two inputs, Java provides "Bi" versions:

    BiVariants.java
    // BiPredicate<T, U> - Test with TWO inputsBiPredicate<String,Integer> hasLength =(s, len)-> s.length()== len;System.out.println(hasLength.test("Hello",5));// trueSystem.out.println(hasLength.test("Hi",5));// false// BiFunction<T, U, R> - Transform TWO inputs into outputBiFunction<String,String,String> concat =(a, b)-> a +" "+ b;BiFunction<Integer,Integer,Integer> multiply =(a, b)-> a * b;System.out.println(concat.apply("Hello","World"));// "Hello World"System.out.println(multiply.apply(5,3));// 15// BiConsumer<T, U> - Consume TWO inputsBiConsumer<String,Integer> printNTimes =(s, n)->{for(int i =0; i < n; i++){System.out.println(s);}};
    printNTimes.accept("Hello",3);// Prints "Hello" 3 times// PRACTICAL: BiConsumer with Map.forEach()Map<String,Integer> scores =newHashMap<>();
    scores.put("Alice",95);
    scores.put("Bob",87);
    scores.forEach((name, score)->System.out.println(name +" scored "+ score));

    📌 Note:

    There's no BiSupplier because suppliers don't take inputs anyway!

    Composing Functions (Chaining)

    One powerful feature of functional interfaces is composition – combining simple functions to build complex ones. Think of it like LEGO blocks!

    ComposingFunctions.java
    // ===== PREDICATE COMPOSITION =====Predicate<String> notEmpty = s ->!s.isEmpty();Predicate<String> notTooLong = s -> s.length()<20;// Combine with AND, OR, NEGATEPredicate<String> validInput = notEmpty.and(notTooLong);Predicate<String> invalidInput = notEmpty.negate();// IS emptySystem.out.println(validInput.test("Hello"));// true (not empty AND not too long)System.out.println(validInput.test(""));// false (empty)// ===== FUNCTION COMPOSITION =====Function<String,String> trim =String::trim;Function<String,String> toUpper =String::toUpperCase;Function<String,Integer> length =String::length;// andThen: Apply THIS first, THEN the argument// trim → toUpper → lengthFunction<String,Integer> pipeline = trim.andThen(toUpper).andThen(length);System.out.println(pipeline.apply("  hello  "));// 5// compose: Apply ARGUMENT first, then THIS// trim first → then lengthFunction<String,Integer> composed = length.compose(trim);System.out.println(composed.apply("  hello  "));// 5// ===== CONSUMER COMPOSITION =====Consumer<String> log = s ->System.out.println("LOG: "+ s);Consumer<String> save = s ->saveToDatabase(s);// Chain consumers with andThenConsumer<String> logAndSave = log.andThen(save);
    logAndSave.accept("data");// First logs, then saves

    💡 Composition Methods:

    • and(), or(), negate() – for Predicates
    • andThen(), compose() – for Functions
    • andThen() – for Consumers

    💡 Tips & Best Practices

    ✅ DO: Use Built-in Interfaces First

    Before creating your own functional interface, check if one already exists in java.util.function. It probably does!

    ✅ DO: Add @FunctionalInterface

    Always annotate your custom functional interfaces. It's free documentation and compiler safety.

    ✅ DO: Use Primitive Specializations

    For primitives, use IntPredicate, LongFunction, DoubleConsumer etc. to avoid boxing overhead.

    Java Example
    // Avoid boxing (slower)Predicate<Integer> isPositive = n -> n >0;// Use primitive version (faster)IntPredicate isPositiveFast = n -> n >0;

    ❌ DON'T: Create Interfaces for Standard Patterns

    Don't create StringValidator when Predicate<String> works perfectly.

    💡 TIP: Name Your Lambdas When Complex

    For readability, assign complex lambdas to well-named variables:

    Java Example
    // Hard to read
    stream.filter(p -> p.getAge()>18&& p.getCountry().equals("USA")&& p.isActive());// Easier to readPredicate<Person> isEligibleVoter = p -> 
    p.getAge()>18&& 
    p.getCountry().equals("USA")&& 
    p.isActive();
    stream.filter(isEligibleVoter);

    📝 Quick Summary

    Key Concepts:

    • Functional Interface = 1 abstract method
    • Enables lambda expressions
    • Use @FunctionalInterface annotation
    • Default/static methods don't count

    The Big Four:

    • Predicate – returns boolean
    • Function – transforms values
    • Consumer – performs actions
    • Supplier – provides values