Java Streams & Lambdas

Functional Java

Java 8 brought functional programming into the language — lambdas, method references, and the Stream API turned verbose loops into readable pipelines.

Java 8+ Interview Key 4 Topics
01

Lambdas & Functional Interfaces

A lambda is an anonymous function you can pass as a value. It works with any functional interface — an interface with exactly one abstract method, like Runnable, Comparator, or Function.

Java — Lambdas
import java.util.function.*;

// (params) -> body
Function<Integer, Integer> square = x -> x * x;
square.apply(5);              // 25

BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
add.apply(3, 4);            // 7

Predicate<String> isEmpty = s -> s.isEmpty();
isEmpty.test("");            // true

Consumer<String> printer = s -> System.out.println(s);
printer.accept("hi");        // prints "hi"

// Sorting with a lambda comparator
List<String> names = new ArrayList<>(List.of("Bob", "Al", "Carol"));
names.sort((a, b) -> a.length() - b.length());   // [Al, Bob, Carol]
02

Method References

When a lambda just calls one existing method, a method reference (::) says the same thing more concisely. s -> s.toUpperCase() becomes String::toUpperCase.

Java — :: Method References
names.forEach(System.out::println);      // instead of s -> System.out.println(s)

names.stream()
     .map(String::toUpperCase)          // instead of s -> s.toUpperCase()
     .sorted(Comparator.naturalOrder())
     .forEach(System.out::println);

// constructor reference
Supplier<ArrayList<String>> makeList = ArrayList::new;
03

The Stream API

A stream is a pipeline of operations over a collection. Intermediate steps (filter, map, sorted) are lazy and chainable; a terminal step (collect, count, forEach) runs the whole pipeline once.

Java — Stream Pipeline
import java.util.stream.*;

List<Integer> nums = List.of(1, 2, 3, 4, 5, 6);

// filter -> map -> collect: even numbers, doubled, into a list
List<Integer> result = nums.stream()
    .filter(n -> n % 2 == 0)      // [2, 4, 6]
    .map(n -> n * 2)              // [4, 8, 12]
    .collect(Collectors.toList());

// reduce / sum
int sum = nums.stream().mapToInt(Integer::intValue).sum();   // 21

// group into a Map
Map<Boolean, List<Integer>> byParity = nums.stream()
    .collect(Collectors.groupingBy(n -> n % 2 == 0));
// {false=[1,3,5], true=[2,4,6]}

// count / anyMatch
long big = nums.stream().filter(n -> n > 3).count();   // 3
Streams are single-use — once a terminal operation runs, the stream is consumed. Call .stream() again on the source collection to run a new pipeline.
04

Optional

Optional<T> is a container that either holds a value or is empty — a type-level signal that a result might be absent, designed to reduce NullPointerException.

Java — Optional
Optional<String> found = names.stream()
    .filter(n -> n.startsWith("C"))
    .findFirst();               // might be empty

found.isPresent();               // true / false
found.orElse("none");            // value, or "none" if empty
found.ifPresent(System.out::println);   // runs only if a value exists

// map inside the Optional without null checks
int len = found.map(String::length).orElse(0);
Return Optional<T> from a method that might not find a result, instead of returning null. The caller is then forced by the type to handle the empty case, which is exactly how NullPointerException gets designed out.

Quick Quiz

1. A functional interface has…
2. String::toUpperCase is a…
3. In a stream, filter and map are…
4. A stream can be used…
5. Optional exists to reduce…