JV

Java Fundamentals

19 lessons

Progress0%
1. Introduction to Java
1What is Java?
2. Variables and Data Types
1Primitive Types
3. Control Flow
ConditionalsLoops
4. Methods
Defining MethodsMethod Overloading
5. Object-Oriented Programming
Classes and ObjectsInheritanceInterfaces and Abstract Classes
6. Collections
ArrayList and LinkedListHashMap and HashSet
7. Exception Handling
Checked & Unchecked Exceptionstry-with-resources & Custom Exceptions
8. Generics
Generic Classes & MethodsWildcards & Type Erasure
9. Modern Java
Lambdas & Functional InterfacesStream API & Optional
10. File I/O
java.nio.file APIBuffered I/O & try-with-resources
All Tutorials
JavaModern Java
Lesson 17 of 19 min
Chapter 9 · Lesson 2

Stream API & Optional

The Stream API (Java 8+)

A Stream<T> is a sequence of elements that supports functional-style operations. Streams do not store data — they compute values on demand from a source (collection, array, generator).

Creating Streams

  • collection.stream() — sequential stream
  • collection.parallelStream() — parallel stream
  • Stream.of(a, b, c) — stream of known values
  • Arrays.stream(arr) — stream from array
  • Stream.generate(supplier) / Stream.iterate(seed, fn) — infinite streams

Intermediate Operations (lazy — produce another stream)

  • filter(Predicate) — keep matching elements
  • map(Function) — transform each element
  • flatMap(Function<T, Stream<R>>) — flatten nested streams
  • distinct() — remove duplicates
  • sorted() / sorted(Comparator) — sort
  • limit(n) / skip(n) — slice the stream
  • peek(Consumer) — inspect elements without consuming (useful for debugging)

Terminal Operations (eager — trigger computation)

  • collect(Collector) — accumulate into a collection or other container
  • forEach(Consumer) — iterate
  • count() — number of elements
  • reduce(identity, BinaryOperator) — fold
  • min(Comparator) / max(Comparator) — return Optional<T>
  • findFirst() / findAny() — return Optional<T>
  • anyMatch / allMatch / noneMatch(Predicate) — boolean short-circuits

Collectors

  • Collectors.toList() / toSet() / toUnmodifiableList()
  • Collectors.joining(delimiter) — concatenate strings
  • Collectors.groupingBy(classifier) — Map<K, List<V>>
  • Collectors.counting(), summingInt(), averagingInt()

Optional<T> (Java 8+)

Optional<T> is a container that either holds a non-null value or is empty. It is designed to be a return type — a method returning Optional<User> explicitly communicates that the result may be absent.

Key methods:

  • Optional.of(value) — must not be null
  • Optional.ofNullable(value) — null becomes empty
  • Optional.empty() — explicitly empty
  • isPresent() / isEmpty() (Java 11)
  • get() — throws if empty (avoid if possible)
  • orElse(default) / orElseGet(Supplier) / orElseThrow()
  • map(Function) / flatMap(Function) / filter(Predicate) — transform the contained value

Code Examples

Stream pipeline: filter, map, collectjava
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamPipelineDemo {
    record Employee(String name, String dept, double salary) {}

    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
            new Employee("Alice",   "Engineering", 95000),
            new Employee("Bob",     "Marketing",   72000),
            new Employee("Carol",   "Engineering", 110000),
            new Employee("Dave",    "Marketing",   68000),
            new Employee("Eve",     "Engineering", 88000),
            new Employee("Frank",   "HR",          61000)
        );

        // 1. Filter + map + collect to list
        List<String> highEarners = employees.stream()
            .filter(e -> e.salary() > 85000)
            .map(Employee::name)
            .sorted()
            .collect(Collectors.toList());
        System.out.println("High earners: " + highEarners);

        // 2. Count matching elements
        long engCount = employees.stream()
            .filter(e -> "Engineering".equals(e.dept()))
            .count();
        System.out.println("Engineers   : " + engCount);

        // 3. Reduce — total salary
        double total = employees.stream()
            .mapToDouble(Employee::salary)
            .sum();
        System.out.printf("Total salary: $%.0f%n", total);

        // 4. Joining — comma-separated names
        String names = employees.stream()
            .map(Employee::name)
            .collect(Collectors.joining(", "));
        System.out.println("Names       : " + names);

        // 5. flatMap — flatten list of lists
        List<List<Integer>> nested = Arrays.asList(
            Arrays.asList(1, 2, 3),
            Arrays.asList(4, 5),
            Arrays.asList(6, 7, 8, 9)
        );
        List<Integer> flat = nested.stream()
            .flatMap(List::stream)
            .collect(Collectors.toList());
        System.out.println("Flat        : " + flat);
    }
}

Stream pipelines are lazy: intermediate operations (filter, map) are only executed when a terminal operation (collect, count, sum) is invoked. mapToDouble returns a DoubleStream with numeric aggregation methods.

groupingBy and statisticsjava
import java.util.Arrays;
import java.util.DoubleSummaryStatistics;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class GroupingByDemo {
    record Product(String name, String category, double price) {}

    public static void main(String[] args) {
        List<Product> products = Arrays.asList(
            new Product("Laptop",    "Electronics", 1200.0),
            new Product("Phone",     "Electronics",  800.0),
            new Product("Desk",      "Furniture",    350.0),
            new Product("Chair",     "Furniture",    200.0),
            new Product("Headphones","Electronics",  150.0),
            new Product("Bookshelf", "Furniture",    120.0)
        );

        // Group by category -> List<Product>
        Map<String, List<Product>> byCategory = products.stream()
            .collect(Collectors.groupingBy(Product::category));
        byCategory.forEach((cat, list) -> {
            List<String> names = list.stream().map(Product::name).collect(Collectors.toList());
            System.out.println(cat + ": " + names);
        });

        // Count per category
        Map<String, Long> countByCategory = products.stream()
            .collect(Collectors.groupingBy(Product::category, Collectors.counting()));
        System.out.println("Counts: " + countByCategory);

        // Average price per category
        Map<String, Double> avgPrice = products.stream()
            .collect(Collectors.groupingBy(Product::category,
                     Collectors.averagingDouble(Product::price)));
        avgPrice.forEach((cat, avg) ->
            System.out.printf("Avg %s: $%.2f%n", cat, avg));

        // Summary statistics for all prices
        DoubleSummaryStatistics stats = products.stream()
            .collect(Collectors.summarizingDouble(Product::price));
        System.out.printf("Min: $%.0f  Max: $%.0f  Avg: $%.2f%n",
            stats.getMin(), stats.getMax(), stats.getAverage());
    }
}

groupingBy is a downstream collector that partitions elements into a Map. The second argument to groupingBy specifies how to aggregate each group — counting(), averagingDouble(), or any other collector.

Optional chainingjava
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class OptionalDemo {
    record User(String name, String email) {}

    static Optional<User> findUser(List<User> users, String name) {
        return users.stream()
            .filter(u -> u.name().equals(name))
            .findFirst();
    }

    static Optional<String> getEmailDomain(String email) {
        if (email == null || !email.contains("@")) return Optional.empty();
        return Optional.of(email.split("@")[1]);
    }

    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User("Alice", "alice@example.com"),
            new User("Bob",   null),
            new User("Carol", "carol@company.org")
        );

        // orElse — provide a default
        String name = findUser(users, "Dave")
            .map(User::name)
            .orElse("Unknown");
        System.out.println("Found     : " + name);

        // map + orElse — transform if present
        String email = findUser(users, "Alice")
            .map(User::email)
            .orElse("no email");
        System.out.println("Email     : " + email);

        // flatMap — chaining Optional-returning methods
        String domain = findUser(users, "Carol")
            .map(User::email)
            .flatMap(OptionalDemo::getEmailDomain)
            .orElse("unknown domain");
        System.out.println("Domain    : " + domain);

        // Bob has null email — ofNullable prevents NPE
        String bobDomain = findUser(users, "Bob")
            .flatMap(u -> Optional.ofNullable(u.email()))
            .flatMap(OptionalDemo::getEmailDomain)
            .orElse("no domain");
        System.out.println("Bob domain: " + bobDomain);

        // filter — only keep if condition met
        Optional<User> adminUser = findUser(users, "Alice")
            .filter(u -> u.email() != null && u.email().endsWith(".com"));
        System.out.println("Admin     : " + adminUser.map(User::name).orElse("none"));

        // orElseThrow — throw if absent
        try {
            findUser(users, "Ghost").orElseThrow(
                () -> new java.util.NoSuchElementException("User not found"));
        } catch (java.util.NoSuchElementException e) {
            System.out.println("Exception : " + e.getMessage());
        }
    }
}

Optional chains prevent null checks from cluttering the call site. flatMap is used when a transformation itself returns an Optional, avoiding Optional<Optional<T>>. Never use get() without isPresent() — prefer orElse, orElseGet, or orElseThrow.

Quick Quiz

1. When are intermediate stream operations (filter, map) actually executed?

2. Which Collectors method groups stream elements into a `Map<K, List<V>>`?

3. What is the difference between `Optional.of(value)` and `Optional.ofNullable(value)`?

4. Which operation would you use to transform a `Stream<List<String>>` into a flat `Stream<String>`?

Was this lesson helpful?

PreviousNext