Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

Collections & Streams in Java 8

Overview

Java 8 introduced a new API called Streams, which allows developers to process collections of objects in a functional manner. Streams provide a clean and efficient way to handle data transformations and perform operations such as filtering, mapping, and reducing collections.

Creating Streams

Streams can be created from various data sources, such as collections, arrays, and even custom generators.

Example: Creating Streams

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class StreamCreationExample {
    public static void main(String[] args) {
        // From a collection
        List list = Arrays.asList("a", "b", "c");
        Stream streamFromList = list.stream();

        // From an array
        String[] array = {"d", "e", "f"};
        Stream streamFromArray = Arrays.stream(array);

        // Displaying the streams
        streamFromList.forEach(System.out::println);
        streamFromArray.forEach(System.out::println);
    }
}

Stream Operations

Streams support two types of operations: intermediate and terminal operations.

Intermediate Operations

Intermediate operations return a new stream and are lazy, meaning they are not executed until a terminal operation is invoked. Some common intermediate operations are:

  • filter: Filters elements based on a predicate.
  • map: Transforms elements using a function.
  • flatMap: Transforms each element into a stream and flattens the result.
  • distinct: Removes duplicate elements.
  • sorted: Sorts elements based on a comparator.
  • limit: Limits the number of elements.
  • skip: Skips the first N elements.

Terminal Operations

Terminal operations produce a result or a side effect and terminate the stream pipeline. Some common terminal operations are:

  • forEach: Performs an action for each element.
  • collect: Collects elements into a collection.
  • reduce: Reduces elements to a single value using a binary operator.
  • count: Counts the number of elements.
  • anyMatch, allMatch, noneMatch: Match elements based on a predicate.
  • findFirst, findAny: Finds an element.

Example: Intermediate and Terminal Operations

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamOperationsExample {
    public static void main(String[] args) {
        List list = Arrays.asList("a", "b", "c", "d", "e", "f", "a");

        // Intermediate operations
        List result = list.stream()
                                  .filter(s -> s.compareTo("c") > 0)
                                  .map(String::toUpperCase)
                                  .distinct()
                                  .sorted()
                                  .collect(Collectors.toList());

        // Terminal operation
        result.forEach(System.out::println);
    }
}

Optional Class

The Optional class in Java 8 is a container object which may or may not contain a non-null value. It provides methods to deal with the presence or absence of a value in a more explicit and less error-prone way compared to using null.

Creating Optional Objects

Optional objects can be created using the Optional.of(), Optional.ofNullable(), and Optional.empty() methods.

Example: Creating Optional Objects

import java.util.Optional;

public class OptionalCreationExample {
    public static void main(String[] args) {
        // Creating an Optional with a non-null value
        Optional nonEmptyOptional = Optional.of("Hello, World!");
        System.out.println(nonEmptyOptional);

        // Creating an Optional with a potentially null value
        Optional nullableOptional = Optional.ofNullable(null);
        System.out.println(nullableOptional);

        // Creating an empty Optional
        Optional emptyOptional = Optional.empty();
        System.out.println(emptyOptional);
    }
}

Working with Optional Objects

The Optional class provides several methods to work with the contained value, such as isPresent(), ifPresent(), orElse(), and orElseGet().

Example: Working with Optional Objects

import java.util.Optional;

public class OptionalUsageExample {
    public static void main(String[] args) {
        Optional optional = Optional.of("Hello, World!");

        // Checking if a value is present
        if (optional.isPresent()) {
            System.out.println("Value is present: " + optional.get());
        } else {
            System.out.println("Value is absent");
        }

        // Performing an action if a value is present
        optional.ifPresent(value -> System.out.println("Value: " + value));

        // Providing a default value if the value is absent
        String defaultValue = optional.orElse("Default Value");
        System.out.println("Default Value: " + defaultValue);

        // Providing a default value using a supplier
        String suppliedValue = optional.orElseGet(() -> "Supplied Value");
        System.out.println("Supplied Value: " + suppliedValue);

        // Throwing an exception if the value is absent
        try {
            String value = optional.orElseThrow(() -> new RuntimeException("Value is absent"));
            System.out.println("Value: " + value);
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
        }
    }
}

Conclusion

The Stream API and Optional class in Java 8 provide powerful tools for processing collections and handling optional values. By leveraging these features, you can write more concise, expressive, and robust code.