Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

I was looking through some old code today—a module that handles a list of user-submitted tags for a blog engine—and I noticed a lot of "boilerplate lambdas." You know the kind. They aren't complex, but they're noisy. They basically just take an input and pass it directly into another method without changing a thing.

Let's look at a snippet I'm refactoring. I have a list of tags, and I want to print each one to the console in uppercase.

List<String> tags = List.of("java", "streams", "functional", "backend");

tags.stream()
    .map(s -> s.toUpperCase())
    .forEach(s -> System.out.println(s));

Cutting out the middleman

If you look at s -> System.out.println(s), it feels redundant. I'm declaring a variable s just to hand it immediately to println. I'm basically acting as a courier. In Java, when a lambda does nothing but call an existing method with the exact same arguments, we can use a Method Reference.

I'll try replacing the forEach lambda with the :: operator. Instead of defining the logic, I'm just pointing Java toward the method I want to use.

tags.stream()
    .map(s -> s.toUpperCase())
    .forEach(System.out::println);

It's cleaner. I'm telling Java: "Take whatever comes out of the stream and plug it directly into the println method of the System.out object."

Wait, what about the map call?

Now look at the .map(s -> s.toUpperCase()) part. That's also just a middleman. It takes a string s and calls a method on that same string. This is slightly different because toUpperCase() isn't a static method; it's an instance method called on the object being passed through the stream.

I wonder if I can use the :: syntax here too. I'll try referencing the String class itself.

tags.stream()
    .map(String::toUpperCase)
    .forEach(System.out::println);

This works perfectly. Even though toUpperCase isn't static, Java is smart enough to understand that the first argument of the functional interface (the Function in map) should be treated as the target of the method call. It's essentially saying: "For every string in this stream, call its own toUpperCase method."

Handling static logic

What if I have a custom utility method? Let's say I have a TagValidator class with a static method that checks if a tag is valid.

class TagValidator {
    public static boolean isValid(String tag) {
        return tag != null && tag.length() > 2;
    }
}

If I want to filter my list using this, I'd normally write .filter(tag -> TagValidator.isValid(tag)). But again, I'm just passing the argument through. I can shorten this by referencing the static method directly:

tags.stream()
    .filter(TagValidator::isValid)
    .map(String::toUpperCase)
    .forEach(System.out::println);

I personally prefer this style because it reads like a sentence: "Filter by tag validator's is-valid, map to string's to-upper-case, and for each, print."

The constructor shortcut

One last thing I stumbled upon while cleaning up this code. Sometimes we use lambdas to instantiate new objects, like s -> new Tag(s). If the lambda just calls a constructor, we can use the ClassName::new syntax.

Let's say I want to turn these strings into Tag objects:

List<Tag> tagObjects = tags.stream()
    .map(Tag::new)
    .toList();

It's a subtle change, but across a project with thousands of lines, removing those (s) -> ... wrappers makes the actual business logic pop. You stop seeing the "plumbing" and start seeing the intent.




📋 Practical Task

Refactoring the Inventory Processor

You are reviewing a piece of legacy code that processes a list of product names. The current implementation uses verbose lambdas that make the stream pipeline harder to read. Your task is to refactor the processInventory method to use Method References wherever possible.

import java.util.*;
import java.util.stream.*;

class Product {
    String name;
    Product(String name) { this.name = name; }
    public String getName() { return name; }
    @Override
    public String toString() { return "Product: " + name; }
}

class InventoryUtils {
    public static boolean isPremium(String name) {
        return name.startsWith("Gold") || name.startsWith("Platinum");
    }
}

public class InventoryManager {
    public static void processInventory(List<String> rawNames) {
        rawNames.stream()
            .filter(name -> InventoryUtils.isPremium(name))
            .map(name -> new Product(name))
            .map(product -> product.toString())
            .forEach(info -> System.out.println(info));
    }

    public static void main(String[] args) {
        List<String> items = List.of("Gold Watch", "Silver Ring", "Platinum Chain", "Bronze Coin");
        processInventory(items);
    }
}

Requirements:

  • Replace the filter lambda with a reference to the static method in InventoryUtils.
  • Replace the map lambda that creates a Product with a constructor reference.
  • Replace the map lambda that calls toString() with an instance method reference.
  • Replace the forEach lambda with a reference to System.out.println.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.