What is Stream API in Java - Quiz

Total: 5 questions

1. 

What is the Stream API in Java and how does a Stream differ from a Collection?

The Stream API is the set of classes and interfaces in the java.util.stream package, introduced in Java 8. It lets you process sequences of elements in a declarative (functional) style: you describe what must happen to the data instead of how to walk over it with loops.

A collection is a data structure: it holds elements in memory, knows its size, gives access by index and lets you add or remove elements. A stream holds nothing: it pulls elements from a source (a collection, an array, a file, a generator), pushes them through a pipeline of operations and closes.

Hence the key differences: a stream does not modify its source (filter(), map() and sorted() return a new stream while the original collection stays untouched), a stream is single-use (touching it after a terminal operation throws IllegalStateException), a stream has no index-based access and its operations are lazy. A collection can be traversed as many times as you want, a stream only once.

2. 

What are the ways to create a Stream in Java?

There are seven main ways:

  1. From a collectionwords.stream(); every Collection has this method.
  2. From explicit valuesStream.of("hello", "hola", "ciao").
  3. From an array of objectsArrays.stream(words) or Stream.of(words).
  4. From an array of primitivesArrays.stream(nums) or IntStream.of(nums); the result is an IntStream, not a Stream<Integer>.
  5. With a generator or an iteratorStream.generate(Math::random).limit(5), Stream.iterate(1, n -> n * 2).limit(5); such streams are infinite and need a bound (since Java 9 the three-argument Stream.iterate(1, n -> n < 100, n -> n * 2) carries its own stop condition).
  6. With a builderStream.<String>builder().add("h").add("i").build().
  7. From a numeric rangeIntStream.range(1, 4) (1, 2, 3) and IntStream.rangeClosed(1, 4) (1, 2, 3, 4).

There are more sources as well: Files.lines(path) for the lines of a file, String.chars() for characters, Random.ints(), Pattern.splitAsStream() and Stream.empty() for an empty stream.

3. 

What are the most common mistakes when working with the Java Stream API?

A handful of mistakes show up again and again, even with experienced developers:

  1. Reusing a stream. After a terminal operation the stream is closed; touching it again throws IllegalStateException. If you need a second pass, build a new stream or keep a Supplier<Stream<T>>.
  2. Forgetting the terminal operation. A chain of intermediate operations alone (filter, map, etc.) does nothing and reports no error — it silently never runs, because intermediate operations are lazy.
  3. Stream.of() with an array of primitives. Stream.of(new int[]{1, 2, 3}) gives a Stream<int[]> holding one element, not a stream of numbers. Use Arrays.stream(nums) or IntStream.of(nums) instead.
  4. An infinite stream with no bound. Stream.generate(Math::random).forEach(...) spins forever. Streams from generate() and from the two-argument iterate() must be bounded with limit() or takeWhile().
  5. Modifying the source while iterating. Adding to or removing from the backing collection inside a lambda throws ConcurrentModificationException:
    List<String> list = new ArrayList<>(List.of("a", "b", "c"));
    list.stream().forEach(s -> {
        if (s.equals("b")) list.remove(s); // ConcurrentModificationException
    });
  6. Side effects instead of collecting. Prefer collect() or toList() over forEach(result::add) — it is safer, especially with parallel streams, where the order and synchronization of side effects are not guaranteed.
4. 

What is the difference between IntStream and Stream<Integer>, and when should you use primitive streams?

Stream<Integer> is a stream of objects: every number is boxed into an Integer, which puts pressure on memory and the garbage collector. IntStream (as well as LongStream and DoubleStream) works with primitives directly, with no boxing.

The second difference is the method set. Primitive streams expose numeric operations that Stream<T> does not have: sum(), average(), max(), min(), summaryStatistics().

// Stream<Integer>: boxing on every element, no sum() method
int total1 = List.of(1, 2, 3).stream().mapToInt(Integer::intValue).sum();

// IntStream: primitives, sum() available
int total2 = IntStream.of(1, 2, 3).sum();               // 6
double avg = IntStream.of(1, 2, 3).average().orElse(0); // 2.0

Switching between them: mapToInt(), mapToLong() and mapToDouble() go from an object stream to a primitive one; boxed() or mapToObj() go back. Use primitive streams for numeric computations and aggregations, and object streams where you actually need the objects.

5. 

Is parallelStream() always faster than a sequential stream?

No. A parallel stream splits the data into chunks, processes them in the shared ForkJoinPool.commonPool() and merges the partial results. On small collections the cost of splitting and merging is larger than the useful work itself, so parallelStream() ends up slower than a sequential stream.

Parallelism pays off when several conditions hold at once: a large data volume, independent operations with no side effects and no shared mutable state, and an easily splittable source (an ArrayList, an array, IntStream.range) rather than a LinkedList or a stream over a file.

There is one more risk: all parallel streams share a single common ForkJoinPool by default, so one long-running or blocking task slows down every other parallel stream in the application. Decide by measuring, not by default.

Page 1 of 1