CoreJava Java8

Java 8 – Functional Interfaces: Predicate, Function, Consumer, Supplier

Java 8 – Functional Interfaces: Predicate, Function, Consumer, Supplier

A functional interface is any interface with exactly one abstract method. Java 8 introduced the java.util.function package with four core functional interfaces that cover the most common patterns. Understanding them is essential for working with lambdas, streams, and method references.

The Four Core Functional Interfaces

Interface Method Input → Output Purpose
Predicate<T> test(T t) T → boolean Test a condition — returns true/false
Function<T, R> apply(T t) T → R Transform one type to another
Consumer<T> accept(T t) T → void Process a value, produce no result
Supplier<T> get() () → T Supply a value, take no input

1. Predicate<T> – Test a Condition

import java.util.function.Predicate;
import java.util.*;
import java.util.stream.Collectors;

public class PredicateExample {

    public static void main(String[] args) {

        Predicate<Integer> isEven     = n -> n % 2 == 0;
        Predicate<String>  isLong     = s -> s.length() > 5;
        Predicate<Integer> isPositive = n -> n > 0;

        System.out.println(isEven.test(4));        // true
        System.out.println(isEven.test(7));        // false
        System.out.println(isLong.test("Lambda")); // false (6 chars = not > 5... wait: 6 > 5 = true)

        // Combining predicates
        Predicate<Integer> isEvenAndPositive = isEven.and(isPositive);
        Predicate<Integer> isEvenOrNegative  = isEven.or(isPositive.negate());
        Predicate<Integer> isOdd             = isEven.negate();

        System.out.println(isEvenAndPositive.test(4));   // true
        System.out.println(isEvenAndPositive.test(-2));  // false
        System.out.println(isOdd.test(3));               // true

        // Using Predicate with Stream
        List<Integer> numbers = Arrays.asList(1, -2, 3, -4, 5, 6, -7, 8);
        List<Integer> positiveEvens = numbers.stream()
            .filter(isEvenAndPositive)
            .collect(Collectors.toList());
        System.out.println("Positive evens: " + positiveEvens);  // [6, 8]

        // Using Predicate with custom objects
        List<String> names = Arrays.asList("Alice", "Bob", "Charlotte", "Dan", "Elizabeth");
        Predicate<String> longName = s -> s.length() > 4;
        names.stream()
             .filter(longName)
             .forEach(System.out::println);
        // Alice, Charlotte, Elizabeth
    }
}

2. Function<T, R> – Transform a Value

import java.util.function.Function;
import java.util.*;
import java.util.stream.Collectors;

public class FunctionExample {

    public static void main(String[] args) {

        Function<String, Integer>  length    = String::length;
        Function<String, String>   upper     = String::toUpperCase;
        Function<Integer, Integer> square    = n -> n * n;
        Function<Integer, String>  intToStr  = n -> "Number: " + n;

        System.out.println(length.apply("Lambda"));    // 6
        System.out.println(upper.apply("java"));       // JAVA
        System.out.println(square.apply(5));           // 25
        System.out.println(intToStr.apply(42));        // Number: 42

        // Chaining with andThen() — apply f1, then f2 on the result
        Function<String, String> upperAndTrim = upper.andThen(String::trim);
        System.out.println(upperAndTrim.apply("  hello  "));  // HELLO (trimmed after uppercase)

        // compose() — apply f2 first, then f1
        Function<Integer, Integer> doubleSquare = square.compose(n -> n * 2);
        // compose: first n*2, then square → (5*2)^2 = 100
        System.out.println(doubleSquare.apply(5));   // 100

        // Using Function with Stream
        List<String> words = Arrays.asList("apple", "banana", "cherry");
        List<Integer> lengths = words.stream()
            .map(length)
            .collect(Collectors.toList());
        System.out.println("Lengths: " + lengths);   // [5, 6, 6]

        // BiFunction — two inputs
        java.util.function.BiFunction<String, Integer, String> repeat =
            (s, n) -> s.repeat(n);
        System.out.println(repeat.apply("Java", 3));  // JavaJavaJava
    }
}

3. Consumer<T> – Consume a Value

import java.util.function.Consumer;
import java.util.*;

public class ConsumerExample {

    public static void main(String[] args) {

        Consumer<String>  print      = System.out::println;
        Consumer<String>  printUpper = s -> System.out.println(s.toUpperCase());
        Consumer<Integer> printSquare = n -> System.out.println(n + "^2 = " + (n*n));

        print.accept("Hello Consumer!");         // Hello Consumer!
        printUpper.accept("java 8");             // JAVA 8
        printSquare.accept(5);                   // 5^2 = 25

        // andThen() — chain two consumers, both run on the same input
        Consumer<String> printBoth = print.andThen(printUpper);
        printBoth.accept("lambda");
        // lambda
        // LAMBDA

        // Using Consumer with forEach
        List<String> products = Arrays.asList("Laptop", "Mouse", "Keyboard");
        products.forEach(print);

        // BiConsumer — two inputs
        java.util.function.BiConsumer<String, Double> printProduct =
            (name, price) -> System.out.printf("%-10s ₹%.2f%n", name, price);
        printProduct.accept("Laptop", 75000.0);   // Laptop     ₹75000.00
        printProduct.accept("Mouse",   1500.0);   // Mouse      ₹1500.00

        Map<String, Double> catalog = new LinkedHashMap<>();
        catalog.put("Tablet",   35000.0);
        catalog.put("Monitor",  15000.0);
        catalog.forEach(printProduct);
    }
}

4. Supplier<T> – Supply a Value

import java.util.function.Supplier;
import java.util.*;

public class SupplierExample {

    public static void main(String[] args) {

        Supplier<String>       greeting = () -> "Hello, Java 8!";
        Supplier<Double>       random   = Math::random;
        Supplier<List<String>> listSupplier = ArrayList::new;

        System.out.println(greeting.get());    // Hello, Java 8!
        System.out.println(random.get());      // random number
        System.out.println(listSupplier.get()); // []

        // Lazy evaluation — Supplier defers computation until get() is called
        Supplier<String> expensive = () -> {
            System.out.println("Computing...");
            return "Expensive Result";
        };

        System.out.println("Before get()");
        String value = expensive.get();   // "Computing..." printed here
        System.out.println(value);

        // Practical: Optional.orElseGet() with Supplier — only called if empty
        Optional<String> empty = Optional.empty();
        String result = empty.orElseGet(() -> "Default from Supplier");
        System.out.println(result);  // Default from Supplier

        // Factory pattern — Supplier as a factory
        Supplier<java.time.LocalDateTime> now = java.time.LocalDateTime::now;
        System.out.println(now.get());
        // Wait a bit...
        System.out.println(now.get());  // different time each call
    }
}

Creating Custom Functional Interfaces

import java.util.function.*;

@FunctionalInterface
interface TriFunction<A, B, C, R> {
    R apply(A a, B b, C c);
}

// Usage
TriFunction<Integer, Integer, Integer, Integer> sum3 =
    (a, b, c) -> a + b + c;

System.out.println(sum3.apply(1, 2, 3));   // 6

@FunctionalInterface
interface Validator<T> {
    boolean validate(T value);

    default Validator<T> and(Validator<T> other) {
        return value -> this.validate(value) && other.validate(value);
    }
}

Validator<String> notEmpty   = s -> !s.isEmpty();
Validator<String> notTooLong = s -> s.length() <= 20;
Validator<String> combined   = notEmpty.and(notTooLong);

System.out.println(combined.validate("Hello"));                  // true
System.out.println(combined.validate(""));                       // false
System.out.println(combined.validate("This is way too long!!")); // false

Specialized Variants

Interface Avoids Method
IntPredicate, LongPredicate, DoublePredicate Integer/Long/Double boxing test(int)
IntFunction<R> Integer boxing on input apply(int)
ToIntFunction<T> Integer boxing on output applyAsInt(T)
UnaryOperator<T> N/A — extends Function<T,T> apply(T)
BinaryOperator<T> N/A — extends BiFunction<T,T,T> apply(T,T)

Summary

The four core functional interfaces cover every function shape: Predicate tests a condition, Function transforms input to output, Consumer processes input without returning, and Supplier produces a value without taking input. All four are used extensively in the Stream API — filter() takes a Predicate, map() takes a Function, forEach() takes a Consumer, and orElseGet() takes a Supplier. Learn these four interfaces and you can understand any lambda-based Java 8 code at a glance.

Topics: CoreJava Java8
← Newer Post Older Post →

Comments

https://www.blogger.com/comment/frame/6690124484600543990?po=7556465848330975943&hl=en&saa=85391&origin=https://www.java9r.com