Java · Functional Programming

Lambda Expressions and Functional Interfaces

Lambda expressions provide a compact way to represent behavior in Java. They are especially useful with functional interfaces, collections, streams, callbacks, and APIs that expect small pieces of executable logic.

What is a Lambda Expression?

A lambda expression is a compact way to provide the implementation of a functional interface method.

It is commonly used when behavior needs to be passed as data—for example, when sorting, filtering, processing collections, or supplying a callback.

Think of it this way: instead of defining a separate class just to describe one small action, a lambda lets you provide that action directly where it is needed.

Basic Syntax

(parameters) -> { code block }

Example:

(int a, int b) -> { return a + b; }

If the body contains a single expression, Java can often infer even more:

(a, b) -> a + b

Functional Interfaces

A functional interface contains exactly one abstract method. Lambda expressions can be used wherever an instance of such an interface is expected.

@FunctionalInterface
interface Greeting {
    void sayHello();
}

Using a lambda:

Greeting g = () -> System.out.println("Hello!");
g.sayHello();

The @FunctionalInterface annotation is optional, but useful because the compiler checks that the interface still has only one abstract method.

Built-in Functional Interfaces

Java provides many ready-to-use functional interfaces in java.util.function.

InterfaceInputOutputTypical method
Consumer<T>One valueNothingaccept
Supplier<T>NothingOne valueget
Function<T,R>One valueOne valueapply
Predicate<T>One valuebooleantest

Consumer

Consumer<String> printer =
    s -> System.out.println(s);

printer.accept("Printing from Consumer!");

Supplier

Supplier<Integer> answer = () -> 42;

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

Function

Function<String, Integer> lengthFunction =
    str -> str.length();

System.out.println(lengthFunction.apply("Apple"));

Predicate

Predicate<Integer> isEven =
    x -> x % 2 == 0;

System.out.println(isEven.test(10));

Why Use Lambdas?

  • They reduce boilerplate code.
  • They make behavior-oriented APIs easier to read.
  • They work naturally with collections and streams.
  • They support composition through functional interfaces.

List Example

List<String> names =
    Arrays.asList("Alice", "Bob", "Charlie");

names.forEach(
    name -> System.out.println("Hi " + name)
);

Lambda vs Anonymous Class

Before lambdas, small behaviors were often written using anonymous classes.

Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Running");
    }
};

Runnable r2 =
    () -> System.out.println("Running");

The lambda version is shorter while expressing the same single behavior.

Important Limitations

  • A lambda can target only a functional interface.
  • Local variables captured by a lambda must be final or effectively final.
  • this inside a lambda refers to the surrounding object, unlike many anonymous-class cases.
  • Overusing lambdas can hurt readability when the logic becomes too large or complex.

Conclusion

Lambda expressions are most useful for short pieces of behavior. Start with simple examples, then use them with collections, event handlers, and the Stream API.