Skip to content
CodeKruPro

Your language · Java

Lambdas, streams and comprehensions

Functional style: lambdas, streams, comprehensions and generators.

Learn: Lambdas, streams and Optional in Java9 minute read

Functional style means passing small pieces of behaviour around as values, for example "this condition" or "this conversion". Java added it in Java 8 with lambdas, streams and Optional, so "what changed in Java 8?" is still a standard interview question. Selenium code (Selenium is a tool that controls a browser from code) uses these tools every day: you pass a lambda to an explicit wait (a wait that checks a condition again and again until a time limit), and you turn a list of elements into a list of texts with a stream.

Step 1: lambdas and functional interfaces

A lambda is a short function with no name: (a, b) -> a + b, or with a block body, x -> { log(x); return x * 2; }. You can use a lambda wherever Java expects a functional interface, which is an interface with exactly one abstract method (a method that has a name and parameters but no body). The common ones are in java.util.function:

  • Predicate<T> turns a T into true or false, for filters.
  • Function<T, R> turns a T into an R, for conversions.
  • Supplier<T> takes nothing and gives a T, for values made later.
  • Consumer<T> takes a T and returns nothing, for actions.

Older interfaces such as Runnable and Comparator also have one abstract method, so lambdas work with them too. A lambda can read a local variable only if it is final or never changed after it is set ("effectively final"), which is why count++ inside a lambda does not compile.

A method reference is a shorter lambda that only calls one method. The :: means "use this method": String::length, System.out::println, Integer::parseInt or ArrayList::new.

Step 2: streams

A stream is a pipeline of steps over a source such as a list (the stream does not store data and does not change the list). Intermediate operations (filter, map, flatMap, sorted, distinct) are lazy: they only describe work and return a new stream. A terminal operation (collect, toList, forEach, count, anyMatch) runs the pipeline and uses it up, so the stream cannot be reused. map turns each element into one new element; flatMap turns each element into a stream and joins them all into one. In the code below, record Product(...) is a short way to write a class that only holds data, and Collectors.groupingBy sorts items into groups (here a map from category to a count).

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

public class Main {
    record Product(String name, String category, double price) {}

    public static void main(String[] args) {
        List<Product> products = List.of(
            new Product("Pen", "office", 1.5),
            new Product("Desk", "furniture", 120.0),
            new Product("Lamp", "furniture", 35.0),
            new Product("Ink", "office", 4.0));

        List<String> cheapNames = products.stream()
            .filter(p -> p.price() < 50)
            .map(Product::name)
            .sorted()
            .toList();
        System.out.println(cheapNames);

        Map<String, Long> perCategory = products.stream()
            .collect(Collectors.groupingBy(Product::category, TreeMap::new, Collectors.counting()));
        System.out.println(perCategory);

        double total = products.stream().mapToDouble(Product::price).sum();
        System.out.println(total);

        Predicate<String> blank = String::isBlank;
        System.out.println(Stream.of("a", " ", "b").filter(blank.negate()).collect(Collectors.joining(",")));

        List<List<Integer>> pages = List.of(List.of(1, 2), List.of(3));
        System.out.println(pages.stream().flatMap(List::stream).map(n -> n * 10).toList());
        System.out.println(products.stream().anyMatch(p -> p.price() > 100));
    }
}
[Ink, Lamp, Pen]
{furniture=2, office=2}
160.5
a,b
[10, 20, 30]
true

Stream.toList() (Java 16) returns a list you cannot change. When you need to change the result, collect with Collectors.toCollection(ArrayList::new).

Step 3: Optional

Optional<T> is a box that holds a value or nothing. Use it as a return type to make "this might be missing" visible instead of returning null (null means "no value", and using it by mistake throws NullPointerException). Make one with Optional.of(value) (which throws on null), Optional.ofNullable(value) or Optional.empty(). Read it with map, orElse, orElseGet or orElseThrow. orElse(x) always works out x first, while orElseGet(supplier) calls the supplier only when the box is empty.

import java.util.*;

public class Main {
    static Optional<String> findUser(String id) {
        return id.equals("42") ? Optional.of("Asha") : Optional.empty();
    }

    static String createGuest() {
        System.out.println("creating guest...");
        return "guest";
    }

    public static void main(String[] args) {
        System.out.println(findUser("42").map(String::toUpperCase).orElse("none"));
        System.out.println(findUser("7").orElseGet(Main::createGuest));
        System.out.println(findUser("42").orElse(createGuest()));   // createGuest still runs
    }
}
ASHA
creating guest...
guest
creating guest...
Asha

Step 4: where you meet this in test code

List<String> texts = driver.findElements(By.cssSelector(".item"))
    .stream().map(WebElement::getText).toList();

new WebDriverWait(driver, Duration.ofSeconds(10))
    .until(d -> d.findElement(By.id("status")).getText().equals("Done"));

The wait takes a lambda and calls it again and again until it returns true or the time runs out (here, 10 seconds).

How to answer in an interview

For "Java 8 features", name lambdas, functional interfaces, method references, streams, Optional and default methods in interfaces, then give one test example: "I collect element texts with stream().map(WebElement::getText).toList(), and I pass a lambda to WebDriverWait.until." For a pipeline question, read it step by step and say what each step produces.

Common mistakes

  • Forgetting the terminal operation, so nothing runs.
  • Using a stream twice, which throws IllegalStateException.
  • Trying to change a local variable inside a lambda.
  • Using orElse(expensiveCall()) when orElseGet was needed.
  • Writing long pipelines full of side effects (steps that change something outside the pipeline, such as a variable or a file). A plain loop is often clearer, and interviewers accept either if you can explain why.

Recap

A lambda is a small function you can pass around, and it fits any interface with one abstract method. Streams are lazy pipelines that run only at a terminal operation and work once. map gives one result per element, and flatMap joins many. Optional marks a missing value; prefer orElseGet when the fallback is costly.

10 questions0/10 solved

StatusQuestionDifficultyAsked in
Not startedWhich are functional interfaces?EasyAsked in: Accenture, Capgemini, TCS
Not startedJava 8+ features: lambdas, streams, functional interfaces, OptionalMediumAsked in: Accenture, Capgemini, TCS
Pro+When does a stream pipeline run?Easy-MediumAsked in: Accenture, Capgemini, TCS
Pro+Using a stream twiceMedium
Pro+Optional: orElse versus orElseGetMediumAsked in: Accenture, Capgemini, TCS
Pro+Changing a local variable inside a lambdaEasy-Medium
Pro+Read a stream pipelineMediumAsked in: Accenture, Capgemini, TCS
Pro+Count each character with a streamMediumAsked in: Accenture, Capgemini, TCS
Pro+map vs flatMap; intermediate vs terminal operationsMediumAsked in: Accenture, Capgemini, TCS
Pro+Method references and their lambdasEasy-MediumAsked in: Tech Mahindra
Not startedNot startedIn progressIn progressSolvedSolvedPro+Pro+

The first 2 questions of each topic are free. Pro+ opens every question.

Back to the plan