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())whenorElseGetwas 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
| Status | Question | Difficulty | Asked in |
|---|---|---|---|
| Not started | Which are functional interfaces? | Easy | Asked in: Accenture, Capgemini, TCS |
| Not started | Java 8+ features: lambdas, streams, functional interfaces, Optional | Medium | Asked in: Accenture, Capgemini, TCS |
| Pro+ | When does a stream pipeline run? | Easy-Medium | Asked in: Accenture, Capgemini, TCS |
| Pro+ | Using a stream twice | Medium | |
| Pro+ | Optional: orElse versus orElseGet | Medium | Asked in: Accenture, Capgemini, TCS |
| Pro+ | Changing a local variable inside a lambda | Easy-Medium | |
| Pro+ | Read a stream pipeline | Medium | Asked in: Accenture, Capgemini, TCS |
| Pro+ | Count each character with a stream | Medium | Asked in: Accenture, Capgemini, TCS |
| Pro+ | map vs flatMap; intermediate vs terminal operations | Medium | Asked in: Accenture, Capgemini, TCS |
| Pro+ | Method references and their lambdas | Easy-Medium | Asked in: Tech Mahindra |
The first 2 questions of each topic are free. Pro+ opens every question.