Lambda Expressions in Java: Syntax and Examples. Practical Tasks
The most common way exercise 2 below breaks is a swapped pair of predicates. The call notEmpty.and(notNull).test(null) throws NullPointerException even though the null check is right there in the code. The reason: and() evaluates left to right and short-circuits, so !s.isEmpty() runs first and blows up on null. The working order is notNull.and(notEmpty). Details like this stay invisible while you read theory and surface the moment you write the code yourself.
A lambda expression in Java is a compact way to implement a functional interface - an interface with exactly one abstract method - without writing an anonymous class. Lambdas and method references arrived in Java 8 and are still the foundation of the Stream API and of functional-style code in the language. Below are 7 practice problems, from a bare () -> ... up to predicate composition and a static method reference. Most of them come with a video walkthrough of the solution.
What to review before you start
Every exercise leans on one built-in functional interface from the java.util.function package. If you forgot a signature, open the matching lesson in a second tab - but write the solution yourself.
| Exercise | Interface | Abstract method | What you practice |
|---|---|---|---|
| 1, 7 | Your own Printable | void print() | Zero-argument lambda, method reference |
| 2, 3 | Predicate<T> | boolean test(T t) | Composition: and(), or(), negate() |
| 4 | Consumer<T> | void accept(T t) | Chaining actions with andThen() |
| 5 | Function<T, R> | R apply(T t) | Turning one type into another |
| 6 | Supplier<T> | T get() | Producing a value with no input |
For the theory itself, see the lesson on lambda expressions in Java.
Exercise 1. A lambda for the Printable interface
Declare your own interface Printable with a single method void print() and implement it with a lambda expression that prints any message to the console.
@FunctionalInterface
interface Printable {
void print();
}
// Printable printable = ...;
// printable.print();
Note that the method takes no parameters, so the parameter list of the lambda is a pair of empty parentheses. The @FunctionalInterface annotation is optional, but it makes the compiler verify that there really is exactly one abstract method.
Exercise 2. Null and empty string check (Predicate.and)
This one has three steps and is built on the Predicate interface:
- Write a
Predicate<String>lambda that returnstruewhen the string is notnull. - Write a second
Predicate<String>lambda that returnstruewhen the string is not empty. - Combine both with the default method
and()into a single "not null and not empty" check and test it againstnull,""and"Java".
Predicate<String> notNull = s -> /* your code */;
Predicate<String> notEmpty = s -> /* your code */;
Predicate<String> hasText = /* combine them here */;
System.out.println(hasText.test(null)); // false
System.out.println(hasText.test("")); // false
System.out.println(hasText.test("Java")); // true
Where this usually goes wrong
The default method and() is implemented as t -> test(t) && other.test(t), so it short-circuits from left to right. The predicate that guards against null has to come first, otherwise step 3 fails on the null input.
Exercise 3. First and last letter of a string (Predicate.or)
Write a program that checks whether a string starts with the capital letter 'J' or 'N' and at the same time ends with the capital letter 'A'. Build the check out of separate Predicate<String> instances combined with or() and and().
Test it on "Java", "Nika", "Scala" and "java". The check is case-sensitive, so "java" must return false.
Exercise 4. Consumer and andThen
You have a class HeavyBox with a weight field. Write a lambda of type Consumer<HeavyBox> that takes a HeavyBox and prints "Shipped a box weighing n", where n is the weight of that box.
Then create a second Consumer<HeavyBox> - printing the delivery address, for example - and chain both actions together with the default method andThen(). Make sure both messages are printed for the same object and in the expected order.
Exercise 5. Sign of a number with Function
Write a lambda of type Function<Integer, String> that takes a number and returns the string "Positive number", "Negative number" or "Zero".
Function<Integer, String> sign = n -> /* your code */;
System.out.println(sign.apply(7)); // Positive number
System.out.println(sign.apply(-3)); // Negative number
System.out.println(sign.apply(0)); // Zero
Exercise 6. Random number with Supplier
Write a lambda of type Supplier<Integer> that returns a random integer from 0 to 10 inclusive. Call get() several times in a loop and confirm that the values change while both ends of the range stay reachable and are never exceeded.
Exercise 7. Static method reference
Go back to exercise 1 and rewrite the Printable implementation as a static method reference: move the body of the lambda into a separate static method and pass it as ClassName::methodName.
When a method reference beats a lambda
A method reference fits only when the lambda does nothing but call one existing method with the very same arguments. As soon as extra logic shows up inside - a condition, some formatting, a second call - keep the plain lambda, otherwise you end up inventing artificial wrapper methods just to keep the reference syntax.
Self-check before you watch the solutions
Run through this short list first - it catches most of the mistakes beginners make with lambdas:
- Does the code compile without warnings and without an explicit cast? If you need a cast, you probably picked the wrong functional interface.
- Did you test the boundary values:
null, an empty string, zero, a negative number? - Are you trying to modify a local variable of the enclosing method inside the lambda? That will not compile - captured variables must be effectively final.
- Are there braces and a
returnwhere a single expression would have been enough? - Does the formatting follow the code style checklist?
Frequently Asked Questions
What is a lambda expression in Java in simple terms?
It is a short form of writing parameters -> body that replaces an anonymous class implementing a functional interface. The compiler knows which method you are implementing because such an interface has exactly one abstract method. Lambda expressions are available starting with Java 8.
How is a lambda expression different from an anonymous class?
An anonymous class creates a separate class with its own scope, so this inside it points to the anonymous object itself. Inside a lambda, this refers to the enclosing class, no extra class file is generated, and the call is wired up through invokedynamic. An anonymous class can also implement an interface with any number of methods, while a lambda works only with a functional interface.
Why can't I change a local variable inside a lambda?
A lambda captures a copy of the local variable's value rather than the variable itself, so Java requires it to be effectively final: assigned once and never reassigned. If you really need a counter, use a field of the class, a one-element array, or AtomicInteger.
Which functional interface should I choose: Predicate, Function, Consumer or Supplier?
Look at the input and the output. An argument in, a boolean out - Predicate. An argument in, a value of another type out - Function. An argument in, nothing out, just an action - Consumer. Nothing in, a value out - Supplier. For primitives use the specialized variants such as IntPredicate or IntSupplier to avoid boxing and unboxing.
Comments