HomeBytesheetsRoadmapsUI TemplatesUI ToolsStack Discussion
4 Topics
Chapter•90 Minutes

Lambdas, Functional Interfaces, Streams, and Optional

Use Java's functional programming features effectively with lambdas, method references, streams, collectors, parallel streams, and Optional.

Lambdas and Functional Interfaces

A lambda is a compact implementation of a functional interface: an interface with one abstract method.

Predicate<String> nonEmpty = value -> !value.isBlank();
Function<String, Integer> length = String::length;
Consumer<String> log = System.out::println;

Common functional interfaces include Predicate<T>, Function<T,R>, Consumer<T>, and Supplier<T>. Primitive-specialized forms can reduce boxing in hot numeric code.

A lambda should generally be small. Complex behavior is often clearer as a named method.

Key Takeaways
  • A lambda targets a functional interface.
  • Method references can improve readability.
  • Prefer small, intention-revealing lambdas.

Stream Pipelines

Streams provide a declarative way to process sequences. A pipeline typically has a source, intermediate operations, and a terminal operation.

List<String> names = List.of("Java", "Kotlin", "JavaScript");

List<String> result = names.stream()
    .filter(name -> name.startsWith("J"))
    .map(String::toUpperCase)
    .toList();

Intermediate operations such as filter, map, sorted, and distinct are generally lazy. A terminal operation such as toList, count, findFirst, or forEach drives execution.

Streams are not collections and are usually single-use.

Lazy evaluation

Calling filter() or map() alone does not process elements. A terminal operation drives the pipeline.

Key Takeaways
  • Streams are pipelines, not storage containers.
  • Intermediate operations are generally lazy.
  • A stream should normally be consumed once.

Collectors, Grouping, and Reduction

Collectors turn streams into lists, sets, maps, grouped structures, and summaries.

Map<Integer, List<String>> byLength =
    names.stream()
        .collect(Collectors.groupingBy(String::length));

int totalLength =
    names.stream()
        .mapToInt(String::length)
        .sum();

groupingBy is useful for classification. partitioningBy splits values into two groups. Reduction operations combine elements into a result.

Prefer built-in collectors when they express the operation clearly. Custom collectors require careful supplier, accumulator, combiner, and finisher design.

Key Takeaways
  • Use groupingBy for classification and aggregation.
  • Primitive streams can reduce boxing for numeric operations.
  • Custom collectors must account for sequential and parallel semantics.

Optional and Absence Modeling

Optional<T> models a value that may be absent. It is most useful as a return type when absence is a meaningful outcome.

Optional<String> username = findUsername();

String display = username
    .map(String::trim)
    .filter(value -> !value.isEmpty())
    .orElse("anonymous");

Avoid using Optional as a field or parameter by default. orElse evaluates its fallback immediately, while orElseGet evaluates a supplier lazily.

Avoid get() without first establishing presence.

Optional is not a magic null killer

Use Optional deliberately at API boundaries where absence is part of the contract.

Key Takeaways
  • Use Optional to model meaningful absence.
  • orElseGet is lazy; orElse evaluates its argument eagerly.
  • Avoid unsafe Optional.get() patterns.