Lambda Expressions

Back

Loading concept...

Lambda Expressions: Your Magic Shortcut to Cleaner Code

The Story of the Lazy Chef 👹‍🍳

Imagine you own a restaurant. Every day, you write detailed instructions for your chef:

“Dear Chef, please take the vegetables, wash them, chop them into small pieces, add salt, cook for 10 minutes
”

That’s exhausting! What if you could just say: “Make soup” and the chef knows exactly what to do?

Lambda expressions are exactly that shortcut. Instead of writing long, formal code, you write a tiny recipe that Java understands instantly.


What is a Lambda Expression?

A lambda is a small piece of code that does ONE thing. Think of it like a sticky note with a quick instruction:

  • ❌ Old way: Write a whole class, give it a name, create an object

  • ✅ Lambda way: Just write what you want to happen!
// Old way (so much writing!)
Runnable task = new Runnable() {
    public void run() {
        System.out.println("Hello!");
    }
};

// Lambda way (short & sweet!)
Runnable task = () ->
    System.out.println("Hello!");

Same result. Less typing. More happiness.


Lambda Syntax: The Three Magic Parts

Every lambda has THREE parts, like a sandwich:

graph TD A["đŸ„Ș Lambda Sandwich"] --> B["🍞 Parameters"] A --> C["âžĄïž Arrow"] A --> D["đŸ„— Body"] B --> E["What goes IN"] C --> F["Points to action"] D --> G["What happens"]

The Formula

(parameters) -> { body }
Part What it means Example
( ) Input - what you receive (x) or (a, b)
-> Arrow - “do this” Always the same
{ } Action - what happens { return x * 2; }

Real Examples

No input, simple output:

() -> System.out.println("Hi!")

One input:

x -> x * 2

Two inputs:

(a, b) -> a + b

Multiple lines:

(x, y) -> {
    int sum = x + y;
    return sum * 2;
}

Quick Rules

Situation Shortcut
One parameter Skip the ( )
One line body Skip the { } and return
Zero parameters Must use ()

Lambdas with Interfaces: The Perfect Match

The Problem

Java is picky. You can’t just throw lambdas anywhere. They need a home - and that home is called a Functional Interface.

What’s a Functional Interface?

A functional interface is an interface with exactly ONE abstract method. That’s it!

@FunctionalInterface
interface Calculator {
    int compute(int a, int b);
}

Think of it like a job posting: “We need ONE person to do ONE thing.”

The lambda is the person applying for that job!

// The job posting (interface)
Calculator add = (a, b) -> a + b;

// Using the hired worker
int result = add.compute(5, 3); // 8

Java’s Built-in Functional Interfaces

Java gives you ready-made interfaces. No need to create your own!

graph TD A["🎁 Common Interfaces"] --> B["Predicate"] A --> C["Function"] A --> D["Consumer"] A --> E["Supplier"] B --> B1["Tests true/false"] C --> C1["Transforms input"] D --> D1["Uses input, no output"] E --> E1["Gives output, no input"]
Interface Purpose Lambda Example
Predicate<T> Test something x -> x > 5
Function<T,R> Convert something x -> x.toUpperCase()
Consumer<T> Use something x -> System.out.println(x)
Supplier<T> Create something () -> new ArrayList<>()

Example: Using Predicate

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

System.out.println(isEven.test(4));
// true

System.out.println(isEven.test(7));
// false

Method References: The Ultimate Shortcut

The Story Continues


Remember our lazy chef? What if the chef already knows how to make soup? You don’t even need to say “make soup” - you just point to the chef!

Method references let you point to an existing method instead of writing a lambda.

The Syntax

ClassName::methodName

That’s it. Two colons. Point to the method. Done.

Four Types of Method References

graph TD A["🎯 Method References"] --> B["Static Method"] A --> C["Instance Method"] A --> D["Object&&#35;39;s Method"] A --> E["Constructor"] B --> B1["Math::abs"] C --> C1["String::length"] D --> D1["myList::add"] E --> E1["ArrayList::new"]

Type 1: Static Method Reference

// Lambda way
Function<Integer, Double> sqrt =
    x -> Math.sqrt(x);

// Method reference way
Function<Integer, Double> sqrt =
    Math::sqrt;

Type 2: Instance Method on Parameter

// Lambda way
Function<String, Integer> len =
    s -> s.length();

// Method reference way
Function<String, Integer> len =
    String::length;

Type 3: Instance Method on Object

List<String> names = new ArrayList<>();

// Lambda way
Consumer<String> adder =
    s -> names.add(s);

// Method reference way
Consumer<String> adder =
    names::add;

Type 4: Constructor Reference

// Lambda way
Supplier<List<String>> maker =
    () -> new ArrayList<>();

// Method reference way
Supplier<List<String>> maker =
    ArrayList::new;

Quick Comparison

Lambda Method Reference
x -> System.out.println(x) System.out::println
x -> x.toLowerCase() String::toLowerCase
(a,b) -> a.compareTo(b) String::compareTo
() -> new Dog() Dog::new

Effectively Final Variables: The Invisible Lock

The Problem

Lambdas can use variables from outside. But there’s a rule: those variables must not change.

What is “Effectively Final”?

A variable is effectively final if:

  • You never reassign it after creation
  • Even if you don’t write final, Java treats it as final
String greeting = "Hello"; // effectively final

Consumer<String> sayHi =
    name -> System.out.println(
        greeting + " " + name
    );

sayHi.accept("World"); // "Hello World"

Why This Rule Exists

Imagine a lambda as a photograph. When you take the photo, it captures the scene at that moment. If things keep moving, the photo would be blurry!

Java wants your lambda to have a clear, stable “snapshot” of variables.

What Works

// ✅ This works - message never changes
String message = "Hi";
Runnable r = () ->
    System.out.println(message);

What Breaks

// ❌ This FAILS - message changes!
String message = "Hi";
message = "Bye"; // CHANGE!

Runnable r = () ->
    System.out.println(message);
// Compiler error!

The Workaround: Arrays or Objects

If you NEED to change values, wrap them:

// Using an array (one element)
int[] counter = {0};

Runnable increment = () ->
    counter[0]++;

increment.run();
System.out.println(counter[0]); // 1

The array reference stays the same - only the contents change!


Putting It All Together

A Complete Example

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

public class LambdaDemo {
    public static void main(String[] args) {
        List<String> names = Arrays.asList(
            "Alice", "Bob", "Charlie"
        );

        // Lambda: filter names
        Predicate<String> startsWithA =
            s -> s.startsWith("A");

        // Method reference: print
        Consumer<String> printer =
            System.out::println;

        // Effectively final variable
        String prefix = "Name: ";

        // Use them together!
        names.stream()
            .filter(startsWithA)
            .map(n -> prefix + n)
            .forEach(printer);

        // Output: Name: Alice
    }
}

Summary: Your Lambda Toolbox

Concept What It Does Example
Lambda Syntax Short function x -> x * 2
Functional Interface Lambda’s home @FunctionalInterface
Method Reference Point to method Math::sqrt
Effectively Final Variables can’t change No reassignment

You Did It! 🎉

You just learned one of the most powerful features in modern Java. Lambdas make your code:

  • Shorter - Less typing
  • Clearer - Easy to read
  • Flexible - Easy to change

Next time you see ->, you’ll know exactly what’s happening. You’re not just writing code anymore - you’re writing elegant code.

Now go practice! The more lambdas you write, the more natural they become.

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.