Backend Development 17 min read

Comprehensive Guide to Java Stream API and Optional Usage

This article provides an in‑depth tutorial on Java 8 Stream operations—including creation, intermediate and terminal operations, parallel streams, reduction, collection, and conversion—followed by a detailed explanation of the Optional class with examples of creation, presence checks, default values, filtering, mapping, and practical usage patterns.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Comprehensive Guide to Java Stream API and Optional Usage

Preface

I have long admired Java Stream and finally have the chance to explore its features and usage thoroughly; understanding its source code deeply will require further study, and while learning Stream I also discovered the powerful Optional class.

Stream

Stream operations can be divided into two types:

1) Intermediate operations, which may appear multiple times, each returning a new stream and allowing method chaining.

2) Terminal operations, which can appear only once; after execution the stream is consumed and cannot be used further, so it must be placed at the end.

Intermediate operations are lazy; they are not executed until a terminal operation triggers actual traversal, enabling multiple actions in a single pass and greatly improving performance.

0x1. Creating Streams

For arrays you can use Arrays.stream() or Stream.of() ; for collections you can call the stream() method added to the Collection interface.

public static void main(String[] args) {
    String[] arr = new String[]{"a", "b", "c"};
    Stream
stream = Arrays.stream(arr);
    stream = Stream.of("a", "b", "c");
    List
list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("c");
    stream = list.stream();
}

Inspecting Stream source shows that of() internally calls Arrays.stream() .

public static
Stream
of(T... values) {
    return Arrays.stream(values);
}

Collections also provide parallelStream() , which creates a parallel stream using the default ForkJoinPool.commonPool() .

List
aList = new ArrayList<>();
Stream
parallelStream = aList.parallelStream();

0x2. Operating on Streams

0x2.1. Filtering

Use filter() to select elements that satisfy a predicate.

public static void main(String[] args) {
    List
list = new ArrayList<>();
    list.add("周杰伦");
    list.add("王力宏");
    list.add("陶喆");
    list.add("林俊杰");
    Stream
stream = list.stream().filter(element -> element.contains("王"));
    stream.forEach(System.out::println);
}

Output:

王力宏

0x2.2. Mapping

Use map() to transform each element, e.g., converting strings to their lengths.

public static void main(String[] args) {
    List
list = new ArrayList<>();
    list.add("周杰伦");
    list.add("王力宏");
    list.add("陶喆");
    list.add("林俊杰");
    Stream
stream = list.stream().map(String::length);
    stream.forEach(System.out::println);
}

Output:

3
3
2
3

0x2.3. Matching

Stream provides anyMatch() , allMatch() , and noneMatch() for element matching.

public static void main(String[] args) {
    List
list = new ArrayList<>();
    list.add("周杰伦");
    list.add("王力宏");
    list.add("陶喆");
    list.add("林俊杰");
    boolean anyMatchFlag = list.stream().anyMatch(e -> e.contains("王"));
    boolean allMatchFlag = list.stream().allMatch(e -> e.length() > 1);
    boolean noneMatchFlag = list.stream().noneMatch(e -> e.endsWith("沉"));
    System.out.println(anyMatchFlag);
    System.out.println(allMatchFlag);
    System.out.println(noneMatchFlag);
}

Output:

true
true
true

0x2.4. Reducing

The reduce() method combines stream elements. Two forms exist:

Optional
reduce(BinaryOperator
accumulator)

No identity value; returns an Optional .

T reduce(T identity, BinaryOperator
accumulator)

With identity; returns a value of the same type as the identity.

public static void main(String[] args) {
    Integer[] ints = {0, 1, 2, 3};
    List
list = Arrays.asList(ints);
    Optional
optional = list.stream().reduce((a, b) -> a + b);
    Optional
optional1 = list.stream().reduce(Integer::sum);
    System.out.println(optional.orElse(0));
    System.out.println(optional1.orElse(0));
    int reduce = list.stream().reduce(6, (a, b) -> a + b);
    System.out.println(reduce);
    int reduce1 = list.stream().reduce(6, Integer::sum);
    System.out.println(reduce1);
}

Output:

6
6
12
12

0x3. Converting Streams

Use collect() to transform a stream back into a collection or other data structures.

public static void main(String[] args) {
    List
list = new ArrayList<>();
    list.add("周杰伦");
    list.add("王力宏");
    list.add("陶喆");
    list.add("林俊杰");
    String[] strArray = list.stream().toArray(String[]::new);
    System.out.println(Arrays.toString(strArray));
    List
list1 = list.stream().map(String::length).collect(Collectors.toList());
    List
list2 = list.stream().collect(Collectors.toCollection(ArrayList::new));
    System.out.println(list1);
    System.out.println(list2);
    String str = list.stream().collect(Collectors.joining(", ")).toString();
    System.out.println(str);
}

Output:

[周杰伦, 王力宏, 陶喆, 林俊杰]
[3, 3, 2, 3]
[周杰伦, 王力宏, 陶喆, 林俊杰]
周杰伦, 王力宏, 陶喆, 林俊杰

0x4. Full Example

import java.util.ArrayList;
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.Map;

public class Java8Tester {
    public static void main(String args[]) {
        // Count empty strings
        List
strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
        System.out.println("列表: " + strings);
        long count = strings.stream().filter(string -> string.isEmpty()).count();
        System.out.println("空字符串数量为: " + count);
        count = strings.stream().filter(string -> string.length() == 3).count();
        System.out.println("字符串长度为 3 的数量为: " + count);
        List
filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
        System.out.println("筛选后的列表: " + filtered);
        String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
        System.out.println("合并字符串: " + mergedString);
        // Additional numeric example omitted for brevity
        long parallelCount = strings.parallelStream().filter(string -> string.isEmpty()).count();
        System.out.println("空字符串的数量为: " + parallelCount);
    }
}

Sample output demonstrates counting, filtering, joining, and parallel processing of collections.

Optional

The Optional class provides a container for possibly absent values, avoiding null references.

0x1. Creating Optional Objects

Use Optional.empty() for an empty optional, Optional.of() for a non‑null value, and Optional.ofNullable() for a value that may be null.

Optional
empty = Optional.empty();
System.out.println(empty); // Optional.empty
Optional
opt = Optional.of("id10t.");
System.out.println(opt); // Optional[id10t.]
String name = "id10t.";
Optional
optOrNull = Optional.ofNullable(name);
System.out.println(optOrNull); // Optional[id10t.]
optOrNull = Optional.ofNullable(null);
System.out.println(optOrNull); // Optional.empty

0x2. Checking Presence

Use isPresent() (or Java 11+ isEmpty() ) to test whether a value exists.

Optional
opt = Optional.of("id10t.");
System.out.println(opt.isPresent()); // true
Optional
optOrNull = Optional.ofNullable(null);
System.out.println(optOrNull.isPresent()); // false

0x3. Non‑Null Expressions

ifPresent() executes a consumer when the value is present; Java 9+ adds ifPresentOrElse() for both present and absent cases.

Optional
opt = Optional.of("id10t.");
opt.ifPresent(str -> System.out.println(str.length()));
opt.ifPresentOrElse(str -> System.out.println(str.length()), () -> System.out.println("null"));

0x4. Providing Default Values

orElse() returns the contained value or a default; orElseGet() lazily supplies the default via a supplier, avoiding unnecessary computation.

String nullName = null;
String name = Optional.ofNullable(nullName).orElse("id10t.");
System.out.println(name); // id10t.
String name2 = Optional.ofNullable(nullName).orElseGet(() -> "id10t.");
System.out.println(name2); // id10t.

0x5. Filtering Values

filter() applies a Predicate to an optional, returning either the original optional or Optional.empty() .

String password = "123456";
Optional
opt = Optional.ofNullable(password);
System.out.println(opt.filter(pwd -> pwd.length() > 6).isPresent()); // false
Predicate
len6 = pwd -> pwd.length() > 6;
Predicate
len10 = pwd -> pwd.length() < 10;
password = "1234567";
opt = Optional.ofNullable(password);
boolean result = opt.filter(len6.and(len10)).isPresent();
System.out.println(result); // true

0x6. Transforming Values

map() converts the contained value to another type, producing a new Optional .

String name = "id10t.";
Optional
nameOptional = Optional.of(name);
Optional
intOpt = nameOptional.map(String::length);
System.out.println(intOpt.orElse(0)); // 7
JavaFunctional ProgrammingCollectionsOptionalStream APIJava 8
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.