14. Java Stream API
T
Tuan Nguyen

14. Java Stream API

This section focuses on Java Stream API concepts including functional programming, lazy evaluation, stream operations, Optional, lambda expressions, collectors, parallel streams, and common performance considerations in modern Java applications.

1. What is Stream API?

Stream API is a Java feature introduced in Java 8 for processing collections in a functional and declarative style.

Example:

List<String> names = users.stream()
    .map(User::getName)
    .filter(name -> name.startsWith("A"))
    .toList();

Instead of writing manual loops:

for (...) {
}

developers can describe:

What should happen

instead of:

How to iterate manually

A stream represents a sequence of elements supporting operations such as:

Filtering
Mapping
Sorting
Grouping
Aggregation
Transformation

Stream API improves readability and functional-style programming.


2. Why do we use Stream API?

Stream API helps simplify data processing.

Without streams:

List<String> result = new ArrayList<>();

for (User user : users) {
    if (user.isActive()) {
        result.add(user.getName());
    }
}

With streams:

List<String> result = users.stream()
    .filter(User::isActive)
    .map(User::getName)
    .toList();

Advantages:

Cleaner code
Less boilerplate
Functional programming style
Easy transformation pipelines
Parallel processing support
Better readability

Streams are heavily used in modern enterprise Java applications.


3. Difference between Collection and Stream?

This is a common interview question.

Collection

Stream

Stores data

Processes data

In-memory structure

Computation pipeline

Supports direct modification

Does not modify source

Can iterate multiple times

Usually consumed once

Collection focuses on:

Data storage

Stream focuses on:

Data processing

Example:

List<String> list;

stores elements.

Example:

list.stream()

creates processing pipeline.


4. What is lazy evaluation?

Lazy evaluation means stream operations are not executed immediately.

Example:

users.stream()
    .filter(u -> {
        System.out.println(u.getName());
        return true;
    });

Nothing happens yet.

Execution starts only when a terminal operation appears.

Example:

.count();

This allows:

Optimization
Reduced unnecessary work
Efficient pipelines

Lazy evaluation is one of the key stream concepts.


5. What is intermediate operation?

Intermediate operations return another stream.

Examples:

map()
filter()
sorted()
distinct()
limit()
skip()

Characteristics:

Lazy
Chainable
Do not execute immediately

Example:

users.stream()
    .filter(User::isActive)
    .map(User::getName)

No actual processing happens yet.


6. What is terminal operation?

Terminal operations trigger stream execution and produce final result.

Examples:

collect()
count()
forEach()
reduce()
findFirst()
toList()

Example:

users.stream()
    .filter(User::isActive)
    .count();

count() triggers execution.

After terminal operation, stream cannot be reused.


7. What is map()?

map() transforms elements one-to-one.

Example:

List<String> names = users.stream()
    .map(User::getName)
    .toList();

Transformation:

User → String

Each input element produces exactly one output element.


8. What is filter()?

filter() removes elements that do not match a condition.

Example:

users.stream()
    .filter(User::isActive)

Only active users remain.

Filtering uses predicates returning:

true
or
false

9. What is flatMap()?

flatMap() transforms and flattens nested structures.

Example:

List<List<String>> list;

Using flatMap():

List<String> result = list.stream()
    .flatMap(Collection::stream)
    .toList();

Result:

Nested lists become single list

Very common for nested collections.


10. Difference between map and flatMap?


map()

One input produces one output.

Example:

User → Name

flatMap()

One input may produce multiple outputs which are flattened.

Example:

List<List<String>>
→ List<String>

Example:

.map(User::getOrders)

Result:

Stream<List<Order>>

Example:

.flatMap(user -> user.getOrders().stream())

Result:

Stream<Order>

11. What is reduce()?

reduce() combines stream elements into a single result.

Example:

int sum = numbers.stream()
    .reduce(0, Integer::sum);

Flow:

0 + 1 + 2 + 3 ...

Used for:

Summation
Aggregation
Combining values
Custom accumulation

12. What is collect()?

collect() converts stream results into collections or other structures.

Example:

List<String> names = users.stream()
    .map(User::getName)
    .collect(Collectors.toList());

Common collectors:

toList()
toSet()
toMap()
joining()
groupingBy()

13. What is groupingBy()?

groupingBy() groups elements based on classification logic.

Example:

Map<String, List<User>> grouped =
    users.stream()
        .collect(Collectors.groupingBy(User::getDepartment));

Result:

Department → Users

Very common in reporting and aggregation.


14. What is partitioningBy()?

partitioningBy() splits elements into two groups using boolean condition.

Example:

Map<Boolean, List<User>> result =
    users.stream()
        .collect(Collectors.partitioningBy(User::isActive));

Result:

true → active users
false → inactive users

Unlike groupingBy, partitioning always produces exactly two groups.


15. What is peek()?

peek() allows inspecting stream elements during processing.

Example:

users.stream()
    .peek(System.out::println)

Mostly used for:

Debugging
Logging
Inspection

Important:

peek() should not contain business logic

because streams are intended for functional-style operations.


16. What is distinct()?

distinct() removes duplicate elements.

Example:

numbers.stream()
    .distinct()

Internally uses:

equals()
hashCode()

for uniqueness checks.


17. What is sorted()?

sorted() sorts stream elements.

Example:

users.stream()
    .sorted(Comparator.comparing(User::getName))

Can use:

Natural ordering
Custom comparators

18. What is limit()?

limit(n) restricts number of elements.

Example:

users.stream()
    .limit(10)

Returns only first 10 elements.

Very useful for:

Pagination
Sampling
Performance optimization

19. What is skip()?

skip(n) ignores first N elements.

Example:

users.stream()
    .skip(10)

Commonly used with pagination.


20. What is Optional?

Optional is a container representing possible absence of value.

Example:

Optional<User>

Instead of returning:

null

Optional explicitly models missing value.


21. Why use Optional?

Benefits:

Reduces NullPointerException
Forces explicit null handling
Improves API readability
Encourages safer code

Example:

userRepository.findById(id)
    .orElseThrow();

Optional makes absence handling explicit.


22. What is orElse()?

orElse() provides default value if Optional is empty.

Example:

String name = optional.orElse("Unknown");

If value exists:

Return actual value

Otherwise:

Return default value

23. Difference between orElse and orElseGet?

Important interview question.


orElse()

Always evaluates argument.

Example:

optional.orElse(expensiveMethod());

expensiveMethod() executes even if Optional contains value.


orElseGet()

Executes supplier lazily.

Example:

optional.orElseGet(() -> expensiveMethod());

Method executes only if Optional is empty.

orElseGet() is better for expensive computations.


24. What is ifPresent()?

ifPresent() executes code only if value exists.

Example:

optional.ifPresent(System.out::println);

Avoids manual null checking.


25. What is method reference?

Method reference is shorthand lambda syntax.

Example lambda:

user -> user.getName()

Method reference equivalent:

User::getName

Cleaner and more readable.


26. What is lambda expression?

Lambda provides concise anonymous function syntax.

Example:

x -> x * 2

Before Java 8:

new Runnable() {
    @Override
    public void run() {
    }
}

With lambda:

() -> System.out.println("Hello")

Lambdas simplify functional programming.


27. What is functional interface?

Functional interface contains exactly one abstract method.

Example:

@FunctionalInterface
interface Calculator {
    int add(int a, int b);
}

Examples in Java:

Runnable
Callable
Function
Consumer
Supplier
Predicate

Lambdas work with functional interfaces.


28. What is parallel stream?

Parallel streams process data using multiple threads automatically.

Example:

users.parallelStream()
    .map(User::getName)
    .toList();

Internally uses:

ForkJoinPool

Benefits:

Potential performance improvement
Parallel CPU usage

Especially useful for CPU-intensive workloads.


29. When should parallel stream be avoided?

Parallel streams may cause problems for:

Small datasets
IO-heavy tasks
Shared mutable state
Database operations
Blocking operations

Problems include:

Thread contention
Higher overhead
Unpredictable performance
Concurrency bugs

Parallel streams are not automatically faster.

Always benchmark performance before using them.


30. What are stream performance issues?

Common issues:

Problem

Explanation

Excessive object creation

Streams create many temporary objects

Boxing/unboxing overhead

Primitive wrappers reduce performance

Overusing streams

Complex chains reduce readability

Parallel stream misuse

Thread overhead may hurt performance

Repeated stream creation

Unnecessary processing overhead

Example inefficient code:

list.stream()
    .filter(...)
    .collect(...)

inside tight loops repeatedly.

Streams improve readability, but performance-sensitive systems should still be profiled and benchmarked carefully.

Comments