Java Programming

Functional Interfaces, Lambdas and Method References

PGCP-BDA

functional programming

Functional programming emphasizes expressions, immutable data, pure transformations and functions passed as values to describe computation declaratively.

functional interface

A functional interface has one abstract method and can be implemented by a lambda or method reference.

Predicate

Predicate<T> accepts a T and returns a Boolean test result, with methods for composition and negation.

Function

Function<T,R> transforms a T into an R and supports before-and-after composition.

Consumer

Consumer<T> accepts a value and performs a side effect without returning a result.

Supplier

Supplier<T> produces a value without receiving an argument and is useful for deferred creation.

lambda expression

A lambda supplies parameters and a body for a functional-interface instance and captures only final or effectively final local variables.

method reference

A method reference reuses an existing static, bound, unbound or constructor operation when its signature matches a functional interface.

effectively final capture

A local variable may be captured by a lambda when it is assigned once and never changed, even without the final modifier.

Variable capture

A lambda may capture instance fields, static fields and local variables. A captured local variable must be final or effectively final:

int minimum = 40;
Predicate<Integer> passing = mark -> mark >= minimum;
// minimum = 50; // would make capture illegal

A local variable is effectively final when assigned once and never reassigned. The restriction gives the lambda a stable captured value even if it executes after the declaring method returns.

Object state reached through a final reference may still mutate, but mutation inside a lambda can make behaviour stateful and unsafe under concurrency. Prefer stateless functions in reusable and parallel operations.

Standard functional interfaces

The java.util.function package supplies reusable contracts:

InterfaceAbstract operationPurpose
Predicate<T>boolean test(T)Test a condition
Function<T,R>R apply(T)Transform a value
Consumer<T>void accept(T)Perform an action using a value
Supplier<T>T get()Produce a value without input
UnaryOperator<T>T apply(T)Transform within one type
BinaryOperator<T>T apply(T,T)Combine two values of one type

Predicates compose with and, or and negate. Functions compose with andThen and compose. Primitive specializations such as IntPredicate, IntFunction and ToIntFunction avoid unnecessary boxing in numeric work.

Nested and anonymous classes

Java's nested forms have different ownership:

  • a static nested class belongs to the outer type and has no implicit outer object;
  • a member inner class carries a reference to an enclosing instance;
  • a local class is declared inside a block and can capture effectively final locals;
  • an anonymous class defines and creates one unnamed implementation at an expression.

A lambda is not an anonymous-class spelling. It supplies an implementation of a functional interface without declaring a new class body in the language model. An anonymous class is useful when an implementation needs extra fields, initialization or several overridden methods. A lambda is concise for one behaviour contract.

Functional interfaces

A functional interface has one abstract method after inherited methods and relevant Object method rules are considered. It may still contain default, static and private methods.

@FunctionalInterface
interface PriceRule {
    double apply(double amount);

default String description() {
        return "Custom price rule";
    }
}

@FunctionalInterface is optional but useful because the compiler reports an error if later changes violate the single-abstract-method requirement.

A lambda does not have a complete type by itself. Its target functional-interface type supplies parameter types, return requirements and allowed checked exceptions:

PriceRule discount = amount -> amount * 0.90;

The same lambda shape may target different compatible interfaces depending on the surrounding assignment, argument, cast or return context.

Lambda syntax

Common lambda forms are:

() -> 42
name -> name.length()
(left, right) -> left.compareTo(right)
(int x, int y) -> {
    int sum = x + y;
    return sum;
}

Parentheses can be omitted for one inferred parameter. If a parameter type is written, all parameter types must be written. A single expression returns its value when the target requires one. A block body uses normal statements and must explicitly return along every required path.

The compiler checks lambda parameters and result against the target method. Overloaded methods accepting different functional interfaces can become ambiguous, in which case an explicit target variable or cast can clarify the intended type.

Method references

A method reference is a compact lambda when an existing method already matches the target:

FormExampleEquivalent idea
Static methodInteger::parseInts -> Integer.parseInt(s)
Bound instanceprefix::concats -> prefix.concat(s)
Unbound instanceString::lengths -> s.length()
ConstructorArrayList::new() -> new ArrayList<>()

The target type still determines how arguments are supplied. String::compareToIgnoreCase, for example, can receive one String as the receiver and another as its method argument when used as a Comparator.

Use a method reference when it improves clarity; a lambda is better when adaptation or extra logic is needed.

Lambda scope and this

A lambda does not introduce a new this. Inside an instance method, this continues to refer to the enclosing object. An anonymous class creates a new object scope, so this refers to the anonymous object.

class Counter {
    int value;
    Runnable lambda() {
        return () -> System.out.println(this.value);
    }
    Runnable anonymous() {
        return new Runnable() {
            @Override public void run() {
                System.out.println(this.getClass().getName());
            }
        };
    }
}

Lambdas also share the enclosing lexical scope and cannot redeclare a local name already used there. Anonymous-class bodies have ordinary class scope and can declare fields.

Optional extraction and fallbacks

get() on an empty Optional throws NoSuchElementException. Prefer methods that make the absence policy visible:

Student student = found.orElseThrow(
        () -> new StudentNotFoundException(id));

orElse(value) evaluates its argument before the method call, even when the Optional is present. orElseGet(supplier) invokes the supplier only when empty:

Profile profile = cached.orElseGet(this::loadProfile);

ifPresent, ifPresentOrElse and or support other policies. Optional works best as a return type for a possibly missing single result. It is usually inappropriate for required fields, method parameters or collections; an empty collection already represents no elements.

Continue learning

Related notes

Put this topic into timed practice

Open mock tests when you want full-exam pacing, or keep drilling in practice mode.