Java 8 Features — Functional Interface

Functional Interface / SAM Interface

Prepared by Srikanth Mamillapalli

1. Functional Interface

A Functional Interface is an interface that has a maximum of one abstract method and can be implemented using a Lambda Expression.

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}
Key point: A Lambda Expression can be used to implement an interface with a maximum of one abstract method.

2. What are Functional or SAM Interfaces?

An interface with only one abstract method is known as a functional interface. It is also known as a SAM (Single Abstract Method) interface.

The interface represents a function through its single abstract method, which is why it is called a functional interface.

Methods Allowed

  • One abstract method.
  • Default methods.
  • Static methods.
  • Overridden methods.

The @FunctionalInterface annotation can be used to declare a Functional Interface.

If @FunctionalInterface is used on an interface with more than one abstract method, the compiler reports an error.

Examples Mentioned in the Source

InterfaceAbstract Method
Runnablerun()
ComparablecompareTo()
ActionListeneractionPerformed()
Callablecall()

3. Functional Interfaces in Java

The source lists the following commonly used functional interfaces:

Function<T, R>

Takes one input and produces one result.

BiFunction<T, U, R>

Takes two inputs and produces one result.

Consumer<T>

Consumes one input.

BiConsumer<T, U>

Consumes two inputs.

Supplier<T>

Supplies a value without an input parameter.

Predicate<T>

Tests one input and returns a boolean.

BiPredicate<T, U>

Tests two inputs and returns a boolean.

UnaryOperator<T>

Performs an operation on one value of the same type.

BinaryOperator<T>

Performs an operation on two values of the same type.

Consumer
Predicate
Function
Supplier
Functional InterfaceGeneric Form
FunctionFunction<T, R>
BiFunctionBiFunction<T, U, R>
ConsumerConsumer<T>
BiConsumerBiConsumer<T, U>
SupplierSupplier<T>
PredicatePredicate<T>
BiPredicateBiPredicate<T, U>
UnaryOperatorUnaryOperator<T>
BinaryOperatorBinaryOperator<T>

4. Predicate<T>

A Predicate represents a condition that accepts one input and returns a boolean result.

public class PredicateExample {

    public static void main(String[] args) {

        Predicate<String> isLongerThan5 =
            s -> s.length() > 5;

        System.out.println(
            isLongerThan5.test("Hello")
        ); // false

        System.out.println(
            isLongerThan5.test("Functional")
        ); // true
    }
}
Example: The predicate checks whether the length of a String is greater than 5.

5. BiPredicate<T, U>

BiPredicate accepts two inputs and returns a boolean result.

public class BiPredicateExample {

    public static void main(String[] args) {

        BiPredicate<String, Integer> isLengthEqual =
            (str, len) -> str.length() == len;

        System.out.println(
            isLengthEqual.test("Java", 4)
        ); // true

        System.out.println(
            isLengthEqual.test("Spring", 3)
        ); // false
    }
}

6. Function<T, R>

Function accepts one input and produces a result.

public class FunctionExample {

    public static void main(String[] args) {

        Function<String, Integer> stringLength =
            s -> s.length();

        System.out.println(
            stringLength.apply("Java")
        ); // 4

        System.out.println(
            stringLength.apply("Functional")
        ); // 10
    }
}
Remember: Function uses apply() to execute the function.

7. BiFunction<T, U, R>

BiFunction accepts two input values and produces one result.

public class BiFunctionExample {

    public static void main(String[] args) {

        BiFunction<Integer, Integer, Integer> add =
            (a, b) -> a + b;

        System.out.println(
            add.apply(10, 20)
        ); // Output: 30

        System.out.println(
            add.apply(5, 3)
        ); // Output: 8
    }
}
Method: apply(T, U) accepts two inputs and returns the result.

8. Consumer<T>

Consumer accepts an input and performs an operation without returning a result.

public class ConsumerExample {

    public static void main(String[] args) {

        Consumer<String> greeter =
            name -> System.out.println(
                "Hello, " + name
            );

        greeter.accept("Alice");
        // Hello, Alice

        greeter.accept("Bob");
        // Hello, Bob
    }
}
Remember: Consumer uses accept() and does not return a value.

9. BiConsumer<T, U>

BiConsumer accepts two input values and performs an operation without returning a result.

public class BiConsumerExample {

    public static void main(String[] args) {

        BiConsumer<String, Integer> printInfo =
            (name, age) ->
                System.out.println(
                    "Name: " + name +
                    ", Age: " + age
                );

        printInfo.accept("Alice", 25);
        // Output: Alice is 25 years old.

        printInfo.accept("Bob", 30);
        // Output: Bob is 30 years old.
    }
}

10. BiConsumer with Map

The source demonstrates using a BiConsumer to process the key and value of a Map through Map.forEach().

public class BiConsumerWithMap {

    public static void main(String[] args) {

        Map<String, Integer> marks =
            new HashMap<>();

        marks.put("Math", 90);
        marks.put("Science", 85);
        marks.put("English", 92);

        BiConsumer<String, Integer> displayEntry =
            (subject, score) ->
                System.out.println(
                    subject + " = " + score
                );

        marks.forEach(displayEntry);
    }
}
Map
Key + Value
BiConsumer

11. BiConsumer andThen()

The source demonstrates chaining two BiConsumers using andThen().

public class BiConsumerAndThenExample {

    public static void main(String[] args) {

        // First BiConsumer: prints name and age
        BiConsumer<String, Integer> print =
            (name, age) ->
                System.out.println(
                    "Name: " + name +
                    ", Age: " + age
                );

        // Second BiConsumer: prints a custom message
        BiConsumer<String, Integer> greet =
            (name, age) ->
                System.out.println(
                    "Hello " + name +
                    "! You are " + age +
                    " years young."
                );

        // Chaining them using andThen()
        BiConsumer<String, Integer> combined =
            print.andThen(greet);

        combined.accept("Alice", 25);
    }
}
Execution flow: print executes first, followed by greet.

12. Supplier<T>

Supplier provides a value without taking an input parameter. The source demonstrates a Supplier that generates a random Integer.

public class SupplierExample {

    public static void main(String[] args) {

        Supplier<Integer> randomSupplier =
            () -> new Random().nextInt(100);

        System.out.println(
            randomSupplier.get()
        );

        System.out.println(
            randomSupplier.get()
        );
    }
}
Remember: Supplier uses get() to obtain a value.

13. UnaryOperator<T>

UnaryOperator is used when the input and output are of the same type.

public class UnaryOperatorExample {

    public static void main(String[] args) {

        UnaryOperator<String> toUpperCase =
            str -> str.toUpperCase();

        System.out.println(
            toUpperCase.apply("java")
        ); // JAVA

        System.out.println(
            toUpperCase.apply("functional")
        ); // FUNCTIONAL
    }
}
Remember: UnaryOperator is a specialized Function where input and output have the same type.

14. BinaryOperator<T>

BinaryOperator is used when two inputs and the result are of the same type.

public class BinaryOperatorExample {

    public static void main(String[] args) {

        BinaryOperator<Integer> add =
            (a, b) -> a + b;

        System.out.println(
            add.apply(10, 20)
        ); // Output: 30
    }
}
Remember: BinaryOperator is a specialized BiFunction where both inputs and the result are the same type.

15. BinaryOperator with Stream reduce()

The source demonstrates using a BinaryOperator as the accumulator for a Stream reduce() operation.

public class StreamReduceExample {

    public static void main(String[] args) {

        List<Integer> numbers =
            Arrays.asList(1, 2, 3, 4, 5);

        BinaryOperator<Integer> sum =
            (a, b) -> a + b;

        int result =
            numbers.stream().reduce(0, sum);

        System.out.println(
            "Sum: " + result
        ); // Output: Sum: 15
    }
}
1, 2, 3, 4, 5
Stream
reduce(0, sum)
15

16. Quick Revision

InterfaceInputOutput / PurposeTypical Method
Predicate<T>1booleantest()
BiPredicate<T,U>2booleantest()
Function<T,R>1Result Rapply()
BiFunction<T,U,R>2Result Rapply()
Consumer<T>1No resultaccept()
BiConsumer<T,U>2No resultaccept()
Supplier<T>0Supplies Tget()
UnaryOperator<T>1Same type Tapply()
BinaryOperator<T>2Same type Tapply()

Easy Memory Trick

Predicate

Question? → true / false

Function

Transform → input to output

Consumer

Consume → input, no return

Supplier

Supply → no input, returns value

UnaryOperator

One → same input/output type

BinaryOperator

Two → same input/output type

The functional interfaces and examples above are based on the uploaded Functional Interface training PDF. fileciteturn3file0L4-L23