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

A few years back, I was reviewing a pull request from a junior dev who was trying to implement a simple filter for a list of transactions. He had written a custom Comparator using an anonymous inner class. It was about seven lines of code—boilerplate overrides, curly braces, and type declarations—all to accomplish one single line of logic: return t1.getAmount() - t2.getAmount();. I remember thinking that the "ceremony" of the code was completely drowning out the actual intent. It's a classic Java struggle: we often spend more time telling the compiler how to structure the code than actually telling it what to do.

Stripping Away the Boilerplate

This is exactly why lambda expressions were introduced. At its core, a lambda is just an anonymous method—a way to pass a block of code as if it were an object. Instead of creating a whole class structure just to implement one method, you can now define the logic right where you need it. If you've ever used languages like JavaScript or Python, this will feel familiar, but in Java, it's strictly tied to how the type system works.

Take that transaction example. Instead of the bulky anonymous class, you can use the (parameters) -> expression syntax. It looks like this:

List<Transaction> transactions = getTransactions();
transactions.sort((t1, t2) -> t1.getAmount().compareTo(t2.getAmount()));

Notice what disappeared. We didn't have to write new Comparator<Transaction>() or public int compare(...). Java looks at the sort method, sees it expects a Comparator, and infers that your lambda is the implementation of that interface's single method. I call this "type inference," and it's what makes your code actually readable.

The Secret Sauce: Functional Interfaces

Now, you can't just throw a lambda anywhere. A lambda expression only works with what we call a "Functional Interface." This is simply any interface that has exactly one abstract method. If an interface has two or more abstract methods, Java has no way of knowing which one your lambda is supposed to be implementing, and the compiler will throw a fit.

You'll see the @FunctionalInterface annotation on many built-in Java interfaces. It's not strictly required for the lambda to work, but it's a safeguard. It tells the compiler, "If anyone accidentally adds a second method to this interface, let me know immediately because it will break all the lambdas using it." You'll run into these constantly in the java.util.function package, such as Predicate<T> (for boolean tests), Consumer<T> (for taking an action), and Function<T, R> (for transforming one type to another).

Scope and the "Effectively Final" Rule

One thing that trips up almost everyone the first time they use lambdas is how they handle local variables. If you try to use a variable from the method surrounding your lambda, that variable must be "effectively final." This means that while you might not have explicitly marked it as final, you cannot change its value after it's initialized.

I've seen developers try to increment a counter inside a lambda, only to realize Java won't let them. This is because lambdas capture the value of the variable, not the variable itself. If Java allowed you to change a local variable from inside a lambda, you'd run into nightmare-inducing concurrency bugs since lambdas are often executed in different threads. If you absolutely must change a value, use an AtomicInteger or an array wrapper, though usually, a redesign of the logic is the better path.




📋 Practical Task

Implementing a High-Priority Task Filter

You are building a task management system. You have a Task class with the following properties: String description, int priority (1-10, where 10 is highest), and boolean isCompleted.

Your goal is to use a lambda expression to filter a list of tasks. Specifically, you need to:

  • Create a List<Task> containing at least five different tasks with varying priorities and completion statuses.
  • Use the removeIf method (which takes a Predicate) to remove all tasks that are already completed OR have a priority lower than 5.
  • Use the forEach method with a lambda to print the descriptions of the remaining high-priority, incomplete tasks.

Ensure your solution avoids anonymous inner classes and uses the concise -> syntax.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.