Explain Codes LogoExplain Codes Logo

Java 8 lambda Void argument

java
lambda
functional-programming
best-practices
Nikita BarsukovbyNikita Barsukov·Feb 25, 2025
TLDR

For actionless lambdas in Java 8 with no return type or argument, embrace Runnable:

Runnable doNothingSpecial = () -> { /* Nothing to see here, move ahead. */ }; doNothingSpecial.run();

The code runs, but it's so silent you can hear a pin drop.

If you're trapped with an interface you don't control and it has an argument, use a Consumer<Void>, and pass null:

Consumer<Void> unfortunateConsumer = v -> { /* Really wish I had something to consume */ }; unfortunateConsumer.accept(null); // Like giving a vegan some bacon

When in doubt, go with Runnable for clarity and fluency. Use Consumer<Void> only when you're an unwilling participant in the situation.

A deep dive into functional interfaces

Switching gears with functional interfaces

In the java.util.function package, the right tool for the job is key:

  • Runnable: A direwolf when you want the job to run but neither argument nor result are needed. Very clean and precise.

  • Supplier<T>: The messenger of Saturn. Doesn't ask for anything but always yields a result.

    Supplier<String> getMessage = () -> "No arguments here, just taking orders!";
  • Consumer<T>: The vacuum cleaner of functional interfaces. He takes everything you give but returns nothing, kind of rude, don't you think?

    Consumer<String> print = System.out::println; print.accept("I eat strings for breakfast."); // Edgy!
  • Callable<T>: The risky teenager on a skateboard. Like Supplier but with an attitude that it might throw an exception.

    Callable<String> riskyTask = () -> { throw new IOException("Rebellion!"); };
  • Function<T, R>: The DJ of functional interfaces. Takes an input, applies some beats, adds rhythm, and produces an output.

    Function<String, Integer> strLength = s -> { return s != null ? s.length() : 0; };
  • UnaryOperator<T> and BinaryOperator<T>`: For when your function demands a uniform fair play. Takes and returns arguments of the same type. Equality for arguments (unlike our society)!

    UnaryOperator<Integer> square = x -> x * x // Because math is cool BinaryOperator<Integer> sum = Integer::sum; // Because addition is cool too
  • Predicate<T>: The Truth Teller. Always blurting out boolean values.

    Predicate<String> isNotEmpty = s -> !s.isEmpty(); // Have you met my chatty cousin, isEmpty?

Void-ing is not a choice

Void is like the Kanye of Java. It complicates things. Avoid it like your ex. Use Runnable or an appropriate functional interface for clear, beautiful code.

Default Methods: Feature not Bug

Adding extra behavior is like adopting a pet. Adopt don't shop. Add default methods, don't extend functional interfaces.

Practical usage of lambdas

When lambdas play with exceptions

Lambda might seem innocent but can throw a checked exception. Insert try-catch or consider Callable:

Callable<String> fetchRemoteData = () -> { try { return remoteApiCall(); } catch (IOException e) { throw new RuntimeException(e); // Because we can't just fail silently, can we? } };

Breath of fresh air with lambdas

Lambdas are your guide to clean code. The days of verboseness and clutter are over.

Bamboozling null with lambdas

null in Java is like a landmine, step on it and...BOOM: NullPointerException.

The default in action

default methods can be your next best friend for code cleanliness and common implementation:

interface EnhancedRunnable extends Runnable { default void beforeRun() { System.out.println("Wait! Let me tie my shoelaces."); } default void afterRun() { System.out.println("That was exhausting. Time for a drink!"); } }