Java Streams

Streams in Java 8 provide a powerful way to work with sequences of data in a declarative and functional style. Below is a comprehensive cheat sheet that covers key concepts, methods, and examples.


1. Creating Streams

  • From a Collection:
  List<String> list = Arrays.asList("a", "b", "c");
  Stream<String> stream = list.stream();
  • From Arrays:
  String[] array = {"a", "b", "c"};
  Stream<String> stream = Arrays.stream(array);
  • From Values:
  Stream<String> stream = Stream.of("a", "b", "c");
  • Empty Stream:
  Stream<String> emptyStream = Stream.empty();
  • Generate Infinite Streams (using Stream.generate and Stream.iterate):
  Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 2);

2. Stream Operations

Stream operations are divided into intermediate (lazy) and terminal (eager).


Intermediate Operations (Lazy Evaluation)

These operations return a new stream and do not trigger processing until a terminal operation is invoked.

  • Filter (selects elements based on a condition):
  stream.filter(s -> s.startsWith("a"))
        .forEach(System.out::println);
  • Map (transforms elements):
  stream.map(String::toUpperCase)
        .forEach(System.out::println);
  • Distinct (removes duplicates):
  stream.distinct()
        .forEach(System.out::println);
  • Sorted (sorts elements):
  stream.sorted()
        .forEach(System.out::println);
  • Peek (intermediate operation for debugging, without consuming the stream):
  stream.peek(System.out::println)
        .filter(s -> s.length() > 3)
        .collect(Collectors.toList());
  • Limit (limits the number of elements):
  stream.limit(3)
        .forEach(System.out::println);
  • Skip (skips the first N elements):
  stream.skip(2)
        .forEach(System.out::println);
  • FlatMap (flatten nested collections):
  Stream<List<String>> nested = Stream.of(Arrays.asList("a", "b"), Arrays.asList("c", "d"));
  nested.flatMap(Collection::stream)
        .forEach(System.out::println);

Terminal Operations (Eager Evaluation)

These operations trigger the processing of the stream and produce a result.

  • ForEach (perform an action for each element):
  stream.forEach(System.out::println);
  • Collect (collect results into a collection or other types):
  List<String> result = stream.collect(Collectors.toList());
  Set<String> resultSet = stream.collect(Collectors.toSet());
  • Count (counts the elements):
  long count = stream.count();
  • Reduce (reduces the stream to a single value):
  Optional<String> concatenated = stream.reduce((s1, s2) -> s1 + s2);
  • Max/Min (finds the maximum or minimum based on a comparator):
  Optional<String> max = stream.max(String::compareTo);
  Optional<String> min = stream.min(String::compareTo);
  • AnyMatch/AllMatch/NoneMatch (predicate tests):
  boolean anyMatch = stream.anyMatch(s -> s.startsWith("a"));
  boolean allMatch = stream.allMatch(s -> s.length() == 1);
  boolean noneMatch = stream.noneMatch(s -> s.contains("x"));
  • FindFirst/FindAny (returns an element based on conditions):
  Optional<String> first = stream.findFirst();
  Optional<String> any = stream.findAny();

3. Collectors Utility Class

Collectors are commonly used with the collect() method to accumulate elements into collections, summarizers, or grouping operations.

  • toList (collect into a List):
  List<String> list = stream.collect(Collectors.toList());
  • toSet (collect into a Set):
  Set<String> set = stream.collect(Collectors.toSet());
  • joining (concatenate elements into a String):
  String result = stream.collect(Collectors.joining(", "));
  • groupingBy (group by a classifier):
  Map<Integer, List<String>> groupedByLength = stream.collect(Collectors.groupingBy(String::length));
  • partitioningBy (split into two groups based on a predicate):
  Map<Boolean, List<String>> partitioned = stream.collect(Collectors.partitioningBy(s -> s.length() > 2));
  • counting (count the elements):
  long count = stream.collect(Collectors.counting());
  • summarizingInt (summary statistics for an int-valued property):
  IntSummaryStatistics stats = stream.collect(Collectors.summarizingInt(String::length));
  • reducing (aggregate elements using a reduction operation):
  Optional<String> concatenated = stream.collect(Collectors.reducing((s1, s2) -> s1 + s2));

4. Working with Optional

Optional is a container that may or may not contain a value.

  • Creating an Optional:
  Optional<String> optional = Optional.of("Hello");
  Optional<String> emptyOptional = Optional.empty();
  • If Present:
  optional.ifPresent(System.out::println);
  • Get the value (with a default):
  String value = optional.orElse("Default");
  String valueWithException = optional.orElseThrow(() -> new IllegalArgumentException("Value missing"));
  • Map and FlatMap (transforming the value):
  Optional<String> upper = optional.map(String::toUpperCase);

5. Parallel Streams

Streams can be processed in parallel for potential performance improvements.

  • Using Parallel Stream:
  stream.parallel().forEach(System.out::println);
  • Collecting Results from Parallel Streams:
  List<String> result = stream.parallel().collect(Collectors.toList());

Note: Parallel streams can be more efficient for large datasets but come with overhead, so they should be used judiciously.


6. Example

List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");

List<String> filteredWords = words.stream()
    .filter(w -> w.length() > 5)
    .map(String::toUpperCase)
    .sorted()
    .collect(Collectors.toList());

filteredWords.forEach(System.out::println);

This will output:

BANANA
CHERRY
ELDERBERRY

7. Common Pitfalls

  • Nulls in Streams: Nulls are generally not allowed in streams, especially when working with collectors.
  • Stateful Operations: Operations like distinct() and sorted() may require additional memory and could affect performance.
  • Ordering: Some stream operations like sorted() and forEachOrdered() preserve ordering, but parallel() can cause non-deterministic order unless properly managed.

8. Best Practices

  • Use method references (::) where possible for readability.
  • Avoid using side effects inside streams. Keep the operations as pure as possible.
  • Prefer functional programming style over mutable operations like for loops.

This cheat sheet covers the essential aspects of Java 8 streams, but there’s more to explore depending on your use case. The official Java Streams API documentation is a great place to dive deeper.

Leave a Reply