Fundamentals 16 min read

Mastering Java Lambda Expressions: A Deep Dive

This article explains what Java lambda expressions are, why they were added in Java 8, their syntax and classification, how they relate to functional interfaces, and demonstrates practical examples including anonymous inner‑class refactoring, stream usage, method references, and custom functional interfaces.

Architect's Guide
Architect's Guide
Architect's Guide
Mastering Java Lambda Expressions: A Deep Dive

What is a Lambda Expression?

A lambda expression can be understood as an anonymous function that allows a function to be passed as an argument to another function; it represents a piece of code that can be supplied as a parameter, embodying functional programming concepts.

Why Were Lambdas Introduced?

Java developers saw other languages (e.g., JavaScript, Python) using closures or lambdas and complained that the most widely used language lacked functional programming support. In response, Java 8 introduced lambda expressions.

Lambda expressions make programming more efficient.

Refactoring Example: From Anonymous Class to Lambda

Original code using a concrete class and a separate Runnable implementation:

package com.isea.java;
public class TestLambda {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
        thread.close();
    }
}
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Hello");
    }
}

Refactored with an anonymous inner class (note the inline comments have been translated to English):

package com.isea.java;
public class TestLambda {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            // Here we create a new instance of the interface and implement its method.
            // The overridden run() method is passed into the constructor.
            @Override
            public void run() {
                System.out.println("Hello");
            }
        }).start();
    }
}

Further simplified by assigning the anonymous Runnable to a variable:

package com.isea.java;
public class TestLambda {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello");
            }
        });
        thread.start();
    }
}

Most concise form using a lambda expression:

package com.isea.java;
public class TestLambda {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("Hello")).start();
    }
}

Result: the code is dramatically shorter while preserving the same behavior.

Lambda Syntax

([parameter list]) -> {lambda body}

Copy the parentheses, write the arrow "->", and place the implementation logic inside braces.

Key annotations and modifiers:

@FunctionalInterface

default methods

static methods

Characteristics: the "->" separates parameters from the implementation; the parentheses contain the parameters, and the braces contain the logic.

Lambda Classification

No‑arg, no return value

package com.isea.java;
public class TestLambda {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("Hello")); // () cannot be omitted; {} can be omitted for a single statement
    }
}

With arguments, no return value

package com.isea.java;
import java.util.ArrayList;
public class TestLambda {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("AAAAA");
        list.add("BBBBB");
        list.add("CCCCC");
        list.add("DDDDD");
        // Parameter type can be inferred; parentheses can be omitted for a single parameter
        list.forEach(t -> System.out.print(t + "\t"));
        // Or using a method reference: list.forEach(System.out::println);
        // Output: AAAAA    BBBBB    CCCCC    DDDDD
    }
}

No‑arg, returns a value

package com.isea.java;
import java.util.Random;
import java.util.stream.Stream;
public class TestLambda {
    public static void main(String[] args) {
        Random random = new Random();
        Stream<Integer> stream = Stream.generate(() -> random.nextInt(100));
        stream.forEach(t -> System.out.println(t)); // Generates an infinite stream of integers < 100
    }
}

With arguments and a return value

Example: sorting students by name using a Collator:

package com.isea.java;
import java.text.Collator;
import java.util.TreeSet;
public class TestLambda {
    public static void main(String[] args) {
        Collator collator = Collator.getInstance();
        TreeSet<Student> set = new TreeSet<>((s1, s2) -> collator.compare(s1.getName(), s2.getName()));
        set.add(new Student(10, "Zhang Fei"));
        set.add(new Student(3, "Zhou Yu"));
        set.add(new Student(1, "Song Jiang"));
        set.forEach(student -> System.out.println(student));
    }
}
class Student {
    private int id;
    private String name;
    public Student(int id, String name) { this.id = id; this.name = name; }
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    @Override
    public String toString() {
        return "Student{id=" + id + ", name='" + name + "'}";
    }
}

What Is a Functional Interface?

A functional interface (also called a SAM – Single Abstract Method interface) is an interface that contains exactly one abstract method, though it may have default or static methods and the methods inherited from Object. Since Java 8, the @FunctionalInterface annotation can be used to mark such interfaces.

@FunctionalInterface
public interface Runnable {
    void run();
}

Only variables of a functional‑interface type can be assigned a lambda expression. The lambda is implicitly converted to an instance of the functional interface. For example, the Thread constructor expects a Runnable implementation, so a lambda can be passed directly:

new Thread(() -> System.out.println("hello")).start();

The compiler infers that the lambda provides the implementation of Runnable.run(). The parameter‑return correspondence holds: if the abstract method has parameters and a return value, the lambda must match that signature.

Creating a Custom Functional Interface

package com.isea.java;
@FunctionalInterface
public interface IMyInterface {
    void study();
}
public class TestIMyInterface {
    public static void main(String[] args) {
        IMyInterface iMyInterface = () -> System.out.println("I like study");
        iMyInterface.study(); // The lambda assigns the method body to the functional interface
    }
}

The Four Core Functional Interfaces in java.util.function

These interfaces are frequently used; many others exist in the same package.

Consumer<T> : void accept(T t) – takes an argument, returns nothing.

Supplier<T> : T get() – no arguments, returns a value.

Predicate<T> : boolean test(T t) – takes an argument, returns a boolean.

Function<T,R> : R apply(T t) – takes an argument, returns a result.

Example using Consumer to adjust salaries below 10,000:

package com.isea.java;
import java.util.HashMap;
public class TestLambda {
    public static void main(String[] args) {
        HashMap<String, Double> map = new HashMap<>();
        map.put("Zhou Yu", 9000.0);
        map.put("Song Jiang", 12000.0);
        map.put("Zhang Fei", 8000.0);
        map.forEach((k, v) -> {
            if (v < 10000.0) map.put(k, 10000.0);
        });
        map.forEach((k, v) -> System.out.print(k + ":" + v + "\t\t"));
        // Output: Zhang Fei:10000.0    Zhou Yu:10000.0    Song Jiang:12000.0
    }
}

Method References

When a lambda merely calls an existing method, a method reference can make the code even shorter.

Constructor reference : Supplier<Student> s = Student::new; Class name :: instance method :

TreeSet<String> set = new TreeSet<>(String::compareTo);

Object :: instance method : set.forEach(System.out::println); Class name :: static method : Stream<Double> stream = Stream.generate(Math::random); These references rely on the same parameter‑to‑method mapping rules as lambdas.

Finally, the author reflects on the writing process, noting that deep, incremental effort is required to truly understand seemingly superficial concepts.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaLambdaFunctional InterfaceStream APIMethod Reference
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.