NBKRIST – Java Hub

Java Lambda Expressions — A Practical Guide

My View on Lambdas

Having seen the evolution of Java over the years, I can confidently say that Lambdas and Streams have revolutionized the way we write code. I thought I would share my experience. When I was working as a software engineer, after I started using Lambdas and Streams, I almost became addicted to this style of coding — building functional pipelines felt like crafting beautiful logic flows. I love lambdas; they are cute, precise, and elegant to use. Having worked on older versions of Java, I felt a huge sense of relief and joy when I adopted lambdas — it was as if Java got a fresh new life.

Java’s introduction of Lambdas marked a paradigm shift in how we write and think about Java code. It transformed Java from an imperative to a hybrid functional language, allowing developers to focus more on what to do rather than how to do it. This evolution placed Java back in the forefront of modern programming languages, alongside Python and Kotlin. The simplicity, power, and expressiveness of Lambdas and Streams inspire developers to think differently, code smartly, and love Java all over again.

“Lambdas transformed Java from verbose to versatile — a leap that made coding not just efficient but joyful.”

1. What is a Lambda Expression?

A lambda expression is a short block of code that takes parameters and returns a value. It provides a clear and concise way to represent a single-method interface (a functional interface) as an expression. Lambdas enable functional-style programming in Java and make code more compact and readable—especially when used with collections and streams.

// Conceptual form (parameters) -> { body } // Example: a lambda that adds two integers (int a, int b) -> a + b

2. How to create and use Lambda expressions

To use a lambda you need a functional interface — an interface with exactly one abstract method. Java provides the @FunctionalInterface annotation to make intent explicit, though it's optional.

@FunctionalInterface interface Operation { int apply(int a, int b); } class LambdaExample { public static void main(String[] args) { Operation sum = (a,b) -> a + b; // inferred types Operation mul = (a,b) -> { return a * b; }; // block body with return System.out.println("Sum: " + sum.apply(5, 7)); // Sum: 12 System.out.println("Mul: " + mul.apply(5, 7)); // Mul: 35 } }

Use lambdas with collection APIs — e.g., List.forEach, Stream.map, filter, etc.

List names = List.of("Alice","Bob","Charlie"); names.stream() .filter(n -> n.length() > 3) .map(n -> n.toUpperCase()) .forEach(System.out::println);

3. Passing Lambdas as parameters & returning Lambdas from methods

Lambdas are objects — you can pass them as parameters or return them from methods using functional interfaces.

// Passing a lambda public static int operate(int a, int b, Operation op) { return op.apply(a,b); } // Returning a lambda public static Operation makeAdder(int x) { return (a,b) -> a + b + x; // captures x (effectively final) } // Usage Operation adder = makeAdder(10); System.out.println( operate(2,3, adder) ); // prints 15

4. java.util.function — Predicate, Function, Supplier, Consumer

Java provides common functional interfaces in java.util.function. Learn their purpose with compact examples.

// Predicate - boolean test(T t) Predicate isLong = s -> s.length() > 5; System.out.println(isLong.test("NBKRIST")); // true // Function - R apply(T t) Function len = s -> s.length(); System.out.println(len.apply("Lambda")); // 6 // Supplier - T get() Supplier random = () -> Math.random(); System.out.println(random.get()); // e.g., 0.345 // Consumer - void accept(T t) Consumer printer = s -> System.out.println(">> " + s); printer.accept("Learn Lambdas");

They compose nicely. Example: combine Predicates or use Function chaining.

Predicate p1 = s -> s.startsWith("A"); Predicate p2 = s -> s.endsWith("e"); Predicate combined = p1.and(p2); System.out.println(combined.test("Alice")); // true Function plus2 = x -> x + 2; Function times3 = x -> x * 3; Function pipeline = plus2.andThen(times3); System.out.println(pipeline.apply(4)); // (4+2)*3 = 18

5. Creating Threads with Lambdas & Comparing Strings

Lambda makes creating Runnable instances concise. Also, lambdas pair nicely with Comparator for sorting.

// Runnable via lambda Runnable job = () -> { System.out.println("Working in: " + Thread.currentThread().getName()); }; Thread t = new Thread(job); t.start(); // Comparator for strings (case-insensitive) Comparator ci = (a,b) -> a.compareToIgnoreCase(b); List s = new ArrayList<>(List.of("Venkat","Raju","Chiru","Anil")); s.sort(ci); s.forEach(System.out::println); //result: Anil, Chiru, Raju, Venkat

Also showing a short example of using lambda to compare by length:

// Comparator by length Comparator byLen = (a,b) -> Integer.compare(a.length(), b.length()); List names = List.of("Rajani","Chiru","Amit","Venkat"); List sorted = new ArrayList<>(names); sorted.sort(byLen); // Result: [Amit, Chiru, Rajani, Venkat]

6. Best Practices & Common Pitfalls

7. Quick Hands-on Exercises

1. Write a Predicate<Integer> that checks prime numbers.
Hint: Use IntStream.rangeClosed(2, n-1) and noneMatch.
2. Create a Function<String,String> that reverses a string.
Hint: Use new StringBuilder(s).reverse().toString().
3. Produce a Supplier<LocalDate> that returns current date.
Hint: () -> LocalDate.now().

Key Summary