• Home
  • LLMs
  • Docker
  • Kubernetes
  • Java
  • All
  • About
Java | Streams
  1. The Stream Interface
  2. Intermediate and Terminal Operations
  3. Methods of the Stream Interface
  4. Package "java.util.stream"

  1. The Stream Interface
    Java allows the construction of a processing flow (or pipeline) that contains a set of steps (or operations) applied sequentially to the elements of a data source.

    The operations that can be applied to a stream are of two types: intermediate operations or terminal operations.

    • The processing flow begins by creating an instance of the java.util.stream.Stream interface from a data source (collection, array, set of elements). The Stream interface represents a sequence of elements supporting sequential and parallel aggregate operations.

      Example:
      import java.util.Arrays;
      import java.util.stream.Stream;
      
      // Creates a stream from a set of values.
      Stream<Integer> integerStream1 = Stream.of(1, 2, 3);
      
      // Creates a stream from an array.
      Integer[] values = {1, 2, 3};
      Stream<Integer> integerStream2 = Stream.of(values);
      Stream<Integer> integerStream3 = Arrays.stream(values);
      
      // Creates a stream from a list.
      Stream<Integer> integerStream4 = Arrays.asList(values).stream();
    • Intermediate operations in the pipeline produce a new instance of the Stream interface.

      Example:
      import java.util.stream.Stream;
      
      Stream<Integer> integerStream1 = Stream.of(1, 2, 3);
      
      // The intermediate operation "filter" transforms a stream into another stream
      Stream<Integer> integerStream2 = integerStream1.filter(i -> i % 2 == 0);
      
      // The intermediate operation "map" transforms a stream into another stream
      Stream<String> stringStream = integerStream2.map(i -> String.valueOf(i));
    • The terminal operation in the pipeline produces a result or a side effect. This operation closes the stream and triggers the execution of all intermediate operations in the pipeline. Without a terminal operation, no processing occurs, as intermediate operations are executed lazily.

      Terminal operations are either non-short-circuiting (they must process all elements) or short-circuiting (they may terminate early). Examples include reduction operations like collect, reduce, and count, as well as short-circuiting operations like findFirst and anyMatch.

      Example:
      import java.util.stream.Stream;
      
      // The terminal operation "count" produces a result
      long count = Stream.of(1, 2, 3).count();
      
      // The terminal operation "forEach" produces a side effect
      Stream.of(1, 2, 3).forEach(System.out::println);
  2. Intermediate and Terminal Operations
    • Intermediate operations are executed lazily only when a terminal operation is invoked. In other words, intermediate operations in a pipeline are never executed if the pipeline does not declare a terminal operation.

      Also, elements of the data source are consumed only as needed.

      Example:
      import java.util.stream.Stream;
      
      // filter and limit are intermediate operations; they won't be applied yet
      Stream<Integer> integerStream1 = Stream.of(1, 2, 3, 4, 5, 6).filter(i -> i % 2 == 0).limit(2);
      
      // forEach is a terminal operation; all intermediate operations along with the terminal operation will now be applied
      integerStream1.forEach(System.out::println);
    • A stream can only be consumed once. Once a terminal operation has been applied to a Stream instance, that stream is closed and cannot be reused.

      If any operation (intermediate or terminal) is applied to a Stream instance after it has already been operated upon or closed, an exception will be thrown:
      "IllegalStateException: stream has already been operated upon or closed".

      Example:
      import java.util.stream.Stream;
      
      Stream<Integer> integerStream1 = Stream.of(1, 2, 3);
      
      // OK: first operation on "integerStream1"
      Stream<Integer> integerStream2 = integerStream1.filter(i -> i % 2 == 0);
      
      // Runtime Exception: IllegalStateException - stream has already been operated upon or closed
      Stream<Integer> integerStream3 = integerStream1.filter(i -> i % 2 == 0);
      
      // Runtime Exception: IllegalStateException - stream has already been operated upon or closed
      long count = integerStream1.count();
      
      // OK: first operation on "integerStream2"
      System.out.println(integerStream2.count());
      
      // Runtime Exception: IllegalStateException - stream has already been operated upon or closed
      System.out.println(integerStream2.count());
    • The chain of operations applied to a sequence of elements can be executed sequentially (by default) or in parallel.

      Example:
      import java.util.Arrays;
      
      Integer[] values = {1, 2, 3};
      
      // stream returns a sequential Stream
      Arrays.stream(values).forEach(System.out::println);
      Arrays.asList(values).stream().forEach(System.out::println);
      
      // parallel/parallelStream returns a possibly parallel Stream
      Arrays.stream(values).parallel().forEach(System.out::println);
      Arrays.asList(values).parallelStream().forEach(System.out::println);
  3. Methods of the Stream Interface
    • collect (terminal operation)
      The Java API defines two versions of the collect method.
      • The first method takes three parameters:
        <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);

        Example:
        import java.util.function.BiConsumer;
        import java.util.function.Supplier;
        import java.util.stream.Stream;
        
        Stream<Integer> integerStream = Stream.of(1, 2, 3);
        
        Supplier<StringBuilder> supplier = StringBuilder::new;
        BiConsumer<StringBuilder, ? super Integer> accumulator = StringBuilder::append;
        BiConsumer<StringBuilder, StringBuilder> combiner = StringBuilder::append;
        
        StringBuilder stringBuilder = integerStream.collect(supplier, accumulator, combiner);
      • The second method takes a single parameter:
        <R, A> R collect(Collector<? super T, A, R> collector);

        Example:
        import java.util.List;
        import java.util.stream.Collectors;
        import java.util.stream.Stream;
        
        Stream<Integer> integerStream = Stream.of(1, 2, 3);
        
        // Collectors.toList() returns a Collector that accumulates elements into a List
        List<Integer> list = integerStream.collect(Collectors.toList());

    • map (intermediate operation)
      The method map applies the given function to each element of the stream and produces a new stream containing the results.
      <R> Stream<R> map(Function<? super T, ? extends R> mapper);

      Example:
      import java.util.stream.Stream;
      
      Stream<Integer> intStream = Stream.of(1, 2, 3);
      Stream<String> stringStream = intStream.map(String::valueOf);
      stringStream.forEach(System.out::println);
    • filter (intermediate operation)
      The method filter applies the given predicate to filter elements of the stream, retaining only those that match.
      Stream<T> filter(Predicate<? super T> predicate);

      Example:
      import java.util.stream.Stream;
      
      Stream<Integer> intStream1 = Stream.of(1, 2, 3, 4, 5);
      Stream<Integer> intStream2 = intStream1.filter(i -> i % 2 == 0);
      intStream2.forEach(System.out::println);
    • findFirst / findAny (terminal operations)
      The method findFirst returns the first element of the stream wrapped in an Optional.
      Optional<T> findFirst();

      The method findAny returns any element of the stream wrapped in an Optional, possibly the first but not guaranteed.
      Optional<T> findAny();

      These are short-circuiting terminal operations that stop processing the stream once an element is found.

      Example:
      import java.util.Optional;
      import java.util.stream.Stream;
      
      Optional<Integer> firstValue = Stream.of(1, 2, 3).findFirst();
      if (firstValue.isPresent()) {
          System.out.println(firstValue.get());
      }
      
      Optional<Integer> anyValue = Stream.of(1, 2, 3).findAny();
      anyValue.ifPresent(System.out::println);
    • anyMatch / allMatch / noneMatch (terminal operations)
      The method anyMatch returns true if any element matches the predicate.
      boolean anyMatch(Predicate<? super T> predicate);

      The method allMatch returns true if all elements match the predicate.
      boolean allMatch(Predicate<? super T> predicate);

      The method noneMatch returns true if no elements match the predicate.
      boolean noneMatch(Predicate<? super T> predicate);

      These are short-circuiting terminal operations that stop processing as soon as the result is determined.

      Example:
      import java.util.stream.Stream;
      
      // anyMatch
      boolean anyMatch = Stream.of(1, 2, 3).anyMatch(i -> i % 2 == 1);
      System.out.println(anyMatch); // true
      
      // allMatch
      boolean allMatch = Stream.of(1, 2, 3).allMatch(i -> i % 2 == 1);
      System.out.println(allMatch); // false
      
      // noneMatch
      boolean noneMatch = Stream.of(1, 2, 3).noneMatch(i -> i % 2 == 1);
      System.out.println(noneMatch); // false
    • reduce (terminal operation)
      The method reduce performs a reduction on the elements of the stream using an accumulator function and returns an Optional or a single value.
      Optional<T> reduce(BinaryOperator<T> accumulator);
      T reduce(T identity, BinaryOperator<T> accumulator);

      Example:
      import java.util.Optional;
      import java.util.stream.Stream;
      
      // Sum using reduce with Optional
      Optional<Integer> sum1 = Stream.of(1, 2, 3, 4, 5).reduce((a, b) -> a + b);
      sum1.ifPresent(System.out::println); // 15
      
      // Sum using reduce with identity
      int sum2 = Stream.of(1, 2, 3, 4, 5).reduce(0, (a, b) -> a + b);
      System.out.println(sum2); // 15
    • limit / skip (intermediate operations)
      The method limit returns a stream with at most the specified number of elements.
      Stream<T> limit(long maxSize);

      The method skip returns a stream with the first specified number of elements discarded.
      Stream<T> skip(long n);

      Example:
      import java.util.stream.Stream;
      
      // limit - only first 3 elements
      Stream.of(1, 2, 3, 4, 5).limit(3).forEach(System.out::println); // 1, 2, 3
      
      // skip - first 2 elements
      Stream.of(1, 2, 3, 4, 5).skip(2).forEach(System.out::println); // 3, 4, 5
      
      // skip and limit
      Stream.of(1, 2, 3, 4, 5).skip(2).limit(3).forEach(System.out::println); // 3, 4, 5
    • distinct (intermediate operation)
      The method distinct returns a stream with duplicate elements removed based on their equals() method.
      Stream<T> distinct();

      Example:
      import java.util.stream.Stream;
      
      Stream.of(1, 1, 2, 3, 4, 4, 5).distinct().forEach(System.out::println); // 1, 2, 3, 4, 5
    • sorted (intermediate operation)
      The method sorted returns a stream with elements sorted in natural order or according to a provided Comparator.
      Stream<T> sorted();
      Stream<T> sorted(Comparator<? super T> comparator);

      Example:
      import java.util.Comparator;
      import java.util.stream.Stream;
      
      // Natural order sorting
      Stream.of(1, 3, 2, 5, 4, 1).sorted().forEach(System.out::println); // 1, 1, 2, 3, 4, 5
      
      // Custom comparator - reverse order
      Stream.of(1, 3, 2, 5, 4, 1).sorted(Comparator.reverseOrder()).forEach(System.out::println); // 5, 4, 3, 2, 1, 1
  4. Package "java.util.stream"
    The Java library provides a set of interfaces for working with streams, which are found in the "java.util.stream" package:

    • BaseStream: BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseable

      Base interface for streams that handle elements of type T.
      public interface BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseable {
          Iterator<T> iterator();
      
          Spliterator<T> spliterator();
      
          boolean isParallel();
      
          S sequential();
      
          S parallel();
      
          S unordered();
      
          S onClose(Runnable closeHandler);
      
          @Override
          void close();
      }
    • Stream: Stream<T> extends BaseStream<T, Stream<T>>
      This is the main specialization of the "BaseStream" interface for object references. It defines several methods for transformation, filtering, aggregation, etc. (filter, map, reduce, distinct, count, ...).
      public interface Stream<T> extends BaseStream<T, Stream<T>> {
          // Filtering operations (intermediate operations)
          Stream<T> filter(Predicate<? super T> predicate);
      
          // Mapping operations (intermediate operations)
          <R> Stream<R> map(Function<? super T, ? extends R> mapper);
      
          // count operations (terminal operations)
          long count();
      
          // reduce operations (terminal operations)
          Optional<T> reduce(BinaryOperator<T> accumulator);
      
          // ... other methods
      }
    • IntStream: IntStream extends BaseStream<Integer, IntStream>
      This interface is a specialization of the "BaseStream" interface for primitive int values. It defines several methods for transformation, filtering, aggregation, etc. (filter, map, reduce, distinct, count, sum, average, ...).
      public interface IntStream extends BaseStream<Integer, IntStream> {
          // Filtering operations (intermediate operations)
          IntStream filter(IntPredicate predicate);
      
          // Mapping operations (intermediate operations)
          IntStream map(IntUnaryOperator mapper);
      
          // sum operations (terminal operations)
          int sum();
      
          // average operations (terminal operations)
          OptionalDouble average();
      
          // ... other methods
      }
    • LongStream: LongStream extends BaseStream<Long, LongStream>
      This interface is a specialization of the "BaseStream" interface for primitive long values. It defines several methods for transformation, filtering, aggregation, etc. (filter, map, reduce, distinct, count, sum, average, ...).
      public interface LongStream extends BaseStream<Long, LongStream> {
          // Filtering operations (intermediate operations)
          LongStream filter(LongPredicate predicate);
      
          // Mapping operations (intermediate operations)
          LongStream map(LongUnaryOperator mapper);
      
          // sum operations (terminal operations)
          long sum();
      
          // average operations (terminal operations)
          OptionalDouble average();
      
          // ... other methods
      }
    • DoubleStream: DoubleStream extends BaseStream<Double, DoubleStream>
      This interface is a specialization of the "BaseStream" interface for primitive double values. It defines several methods for transformation, filtering, aggregation, etc. (filter, map, reduce, distinct, count, sum, average, ...).
      public interface DoubleStream extends BaseStream<Double, DoubleStream> {
          // Filtering operations (intermediate operations)
          DoubleStream filter(DoublePredicate predicate);
      
          // Mapping operations (intermediate operations)
          DoubleStream map(DoubleUnaryOperator mapper);
      
          // sum operations (terminal operations)
          double sum();
      
          // average operations (terminal operations)
          OptionalDouble average();
      
          // ... other methods
      }
© 2025  mtitek