Skip to content
Lesson 12 of 15

Step 1 of 5 · Reading · ~3 min

Learn

Collections

Streams

You already process sequences with a loop, an if and an accumulator. Streams (Java 8+) describe the same work declaratively: say what happens to the data (keep these, transform those, combine the rest) and let the library run it. By the end of this lesson you can build a pipeline over an array or list, bridge between primitive and boxed streams, and turn a stream's result into a line of text.

A first pipeline

import java.util.Arrays;

int[] prices = {12, 7, 30, 7};

long pricey = Arrays.stream(prices)    // source
    .filter(p -> p > 10)               // keep: 12, 30
    .count();                          // terminal: 2

A pipeline always has three parts:

  1. Source: Arrays.stream(array), list.stream(), IntStream.rangeClosed(1, 5), Stream.of(...).
  2. Intermediate operations, each returning a new stream so they chain: filter (keep elements passing a test), map (transform each element), distinct, sorted, limit.
  3. One terminal operation that produces the result: sum(), count(), max(), collect(...), forEach(...), toArray().

Streams are lazy: intermediate steps do nothing until a terminal operation asks for the result, so a pipeline with no terminal operation never runs. A stream is also single use; reusing one throws IllegalStateException: stream has already been operated upon or closed. And a pipeline never changes its source: prices is untouched afterwards.

Lambdas and method references

p -> p > 10 is a lambda, an inline function: parameters, an arrow, a body. When a lambda only calls one existing method, a method reference is shorter: String::toUpperCase means s -> s.toUpperCase().

Two flavors of stream

Arrays.stream(int[]) gives an IntStream, a stream of primitive ints. It has numeric terminals like sum(), max() and average(). A stream built from an ArrayList<Integer> is a Stream<Integer>, a stream of boxed objects, and has none of those:

ArrayList<Integer> boxed = new ArrayList<>();
boxed.add(3);
boxed.add(1);
boxed.add(2);
boxed.stream().mapToInt(Integer::intValue).sum();   // 6: bridge to IntStream first

mapToInt converts to an IntStream, and boxed() goes the other way. An empty IntStream's sum() is simply 0.

Producing text from a stream

To build one string from the elements, convert them to strings and join:

import java.util.stream.Collectors;
import java.util.stream.IntStream;

String tags = IntStream.rangeClosed(1, 5)
    .mapToObj(n -> "#" + n)                 // IntStream -> Stream<String>
    .collect(Collectors.joining(","));      // "#1,#2,#3,#4,#5"

mapToObj turns each int into an object (here a String, by joining "#" and the number). Collectors.joining(sep) puts the separator between elements only, so there is no trailing separator to trim. The same shape works on words:

String[] drinks = {"tea", "coffee", "milk", "ale", "juice"};
String long4 = Arrays.stream(drinks)
    .filter(w -> w.length() > 3)
    .map(String::toUpperCase)
    .sorted()
    .collect(Collectors.joining(", "));     // "COFFEE, JUICE, MILK"

Order of stages matters

sorted() sorts whatever reaches it. Sorting before a map that squares negative numbers gives a different order than sorting after, because -9 sorts before 1 but its square 81 sorts after 1. Likewise distinct() removes duplicates of whatever it sees, so removing duplicates before or after a transformation can keep different numbers of elements. Read the pipeline top to bottom and say what the data looks like after each stage.

Remember the remainder rules

Inside a filter, Java's % keeps the sign of the left operand: -3 % 2 is -1. So n % 2 == 1 quietly rejects negative odd numbers; test oddness with n % 2 != 0.

Common mistakes

  • Forgetting the terminal operation: the pipeline compiles and does nothing.
  • Calling sum() on a Stream<Integer>: use mapToInt first.
  • Assuming a stage order that is not the one written, especially sorted and distinct.
  • Reusing a stream after a terminal operation.

Your exercise

Build one pipeline that takes a line of integers, keeps a subset, transforms them, tidies the result (duplicates, order) and prints it as a single line of text. The tests include negative odd numbers and pairs like -3 and 3, so the order of your stages is checked.

Up nextInheritance and superInheritance and Interfaces

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…