AI InterviewSearch…Search problems, topics, or pages…Ctrl KGet started
Skip to content

15 Streams / Lesson 6 of 7

Primitive streams — IntStream, LongStream, DoubleStream

12 min · +10 XP · 2 exercises · 5 questions

Read the text12 min
Exercises0/2
Quiz5 q

Quick answer

What is IntStream in Java?

IntStream is the Java stream specialised for int values: it carries primitives directly instead of boxing each one into an Integer, and adds numeric terminals such as sum, average, max and summaryStatistics. IntStream.range(0, n) replaces an index loop, mapToInt turns an object stream into one, and boxed() turns it back. LongStream and DoubleStream do the same for long and double.

A Stream<Integer> boxes every element: each int becomes an object on the heap, every arithmetic step unboxes and re-boxes, and sum() does not even exist on it. The three primitive specialisations — IntStream, LongStream, DoubleStream — fix that. They carry ints, longs and doubles directly, add the numeric terminals (sum, average, summaryStatistics), and make the range loop for (int i = 0; i < n; i++) expressible as a pipeline. This lesson covers them, the conversions between the worlds, and the two places (chars() and range) where they show up whether you asked or not.

Sources

IntStream.range(0, 5);           // 0 1 2 3 4
IntStream.rangeClosed(1, 5);     // 1 2 3 4 5
IntStream.of(3, 1, 4);
IntStream.iterate(1, x -> x * 2).limit(10);          // powers of two
IntStream.iterate(1, x -> x <= 1000, x -> x * 2);    // finite (Java 9)
IntStream.generate(() -> 7).limit(3);
Arrays.stream(new int[]{1, 2, 3});                   // int[] → IntStream (an Integer[] would give Stream<Integer>)
"hello".chars();                                      // IntStream of UTF-16 code units
"hello".codePoints();                                 // IntStream of code points — safe for emoji
new Random().ints(5, 0, 100);                         // five ints in [0, 100)
LongStream.rangeClosed(1, 20);                        // for factorials: long, not int

There is no range for DoubleStream; build one with IntStream.range(...).mapToDouble(i -> i * 0.1).

Numeric terminals — the reason they exist

int sum = IntStream.rangeClosed(1, 100).sum();                 // 5050
OptionalInt max = IntStream.of(3, 9, 2).max();                 // OptionalInt[9] — no comparator needed
OptionalDouble avg = IntStream.of(1, 2, 4).average();           // 2.333…
long count = IntStream.range(0, 10).filter(i -> i % 3 == 0).count();

IntSummaryStatistics st = IntStream.of(4, 8, 15, 16, 23, 42).summaryStatistics();
st.getCount(); st.getSum(); st.getMin(); st.getMax(); st.getAverage();   // all five in one pass

max/min need no comparator (the natural order of numbers is built in); average returns OptionalDouble (empty for no elements); sum of an empty stream is 0. Overflow is silent: IntStream.rangeClosed(1, 20).reduce(1, (a, b) -> a * b) wraps around — use LongStream or asLongStream() before multiplying, and Math.multiplyExact if you would rather fail than be wrong.

Moving between the worlds

From → toMethod
Stream<T> → IntStreammapToInt(ToIntFunction) — mapToInt(String::length)
IntStream → Stream<Integer>boxed()
IntStream → Stream<R>mapToObj(IntFunction) — mapToObj(i -> "#" + i)
IntStream → LongStream / DoubleStreamasLongStream(), asDoubleStream(), or mapToLong/mapToDouble
Stream<Integer> → int[]mapToInt(Integer::intValue).toArray()
IntStream → List<Integer>boxed().toList()

IntStream.toArray() gives an int[]; Stream<Integer>.toArray() gives an Object[] — a favourite source of surprise. Collectors work only on object streams: boxed() before collect(groupingBy(...)).

Working with characters

long vowels = s.chars().filter(c -> "aeiou".indexOf(c) >= 0).count();
String upper = s.chars().mapToObj(c -> String.valueOf((char) Character.toUpperCase(c))).collect(joining());
boolean allDigits = !s.isEmpty() && s.chars().allMatch(Character::isDigit);
Map<Character, Long> freq = s.chars().mapToObj(c -> (char) c).collect(groupingBy(c -> c, TreeMap::new, counting()));
String reversed = new StringBuilder(s).reverse().toString();   // not a stream job — know when to stop

chars() yields ints, so the char cast is on you; mapToObj(c -> (char) c) gets you a Stream<Character> for collectors. For text that may contain characters outside the basic plane (emoji, some CJK), codePoints() and Character.toChars are the correct pair.

Index-driven pipelines

Streams have no index. When you need one, generate it:

IntStream.range(0, list.size()).filter(i -> i % 2 == 0).mapToObj(list::get).toList();   // even positions
IntStream.range(0, a.length).mapToObj(i -> a[i] + "@" + i).forEach(System.out::println);
IntStream.range(0, n).map(i -> n - 1 - i);                                             // count down

If the loop body needs two indices or mutates as it goes, the for loop is the right tool; a stream contorted around an index is worse than the loop it replaced.

Boxing costs, measured honestly

On a million integers, Stream<Integer> summing via reduce(0, Integer::sum) allocates a million Integers (well, most of them — the small ones come from the cache) and runs several times slower than IntStream.sum(). It rarely matters at interview scale; it always matters in a hot loop over an int[]. The habit is cheap: reach for mapToInt the moment the pipeline turns numeric, and stay unboxed until a collector needs objects.

Interview angle

  • "Why do primitive streams exist?" To avoid boxing and to provide sum/average/summaryStatistics, which Stream<Integer> lacks.
  • "Arrays.stream(int[]) returns…?" IntStream; Arrays.stream(Integer[]) returns Stream<Integer>.
  • "How do you get from IntStream to a List<Integer>?" boxed().toList() (or collect(toList())).
  • "What does String.chars() return?" An IntStream of UTF-16 code units — cast to char, or use codePoints() for supplementary characters.
  • "What does average() return on an empty stream?" OptionalDouble.empty(); sum() returns 0.

Key takeaways

  • IntStream/LongStream/DoubleStream hold primitives: no boxing, numeric terminals built in.
  • range/rangeClosed replace index loops; iterate with a predicate for other progressions.
  • Convert with mapToInt, boxed, mapToObj, asLongStream; collectors need objects.
  • sum of nothing is 0; average/max/min return the Optional* types; overflow is silent — go long early.
  • chars() is an IntStream; cast to char or use codePoints().

Common questions

What is the difference between IntStream.range and rangeClosed?

IntStream.range(0, 5) excludes its end and yields 0 to 4, like a for (int i = 0; i < 5; i++) loop. IntStream.rangeClosed(1, 5) includes it and yields 1 to 5. DoubleStream has no range method.

How do you convert an IntStream to a List in Java?

Box the values first, because collections hold objects: intStream.boxed().toList(), or boxed().collect(Collectors.toList()). For an array instead, intStream.toArray() returns an int[] directly.

What does String.chars() return in Java?

An IntStream of the string's UTF-16 code units, so each element is an int you must cast to char; mapToObj(c -> (char) c) gives a Stream<Character> for collectors. For text with emoji or other supplementary characters, use codePoints() instead.

Why is IntStream faster than Stream<Integer>?

Stream<Integer> boxes every element into an Integer object and unboxes it for each arithmetic step, while IntStream works on int values directly. Over a million numbers the boxed version runs several times slower, so switch with mapToInt as soon as a pipeline turns numeric.

What does average() return on an empty IntStream?

An empty OptionalDouble, because the average of nothing is undefined. sum() of an empty stream returns 0, and max() and min() return an empty OptionalInt.

Mark as readSign in to keep your place.

Java IntStream, LongStream and DoubleStream Explained

IntStream, LongStream and DoubleStream carry Java primitives without boxing and add sum, average and summaryStatistics. Ranges, chars, conversions, overflow.

  • Course: Java study plan
  • Module: Streams
  • Kind: Lesson
  • Reading time: 12 min
  • Runtime: OpenJDK 18

What is IntStream in Java?

IntStream is the Java stream specialised for int values: it carries primitives directly instead of boxing each one into an Integer, and adds numeric terminals such as sum, average, max and summaryStatistics. IntStream.range(0, n) replaces an index loop, mapToInt turns an object stream into one, and boxed() turns it back. LongStream and DoubleStream do the same for long and double.

Lesson

A Stream<Integer> boxes every element: each int becomes an object on the heap, every arithmetic step unboxes and re-boxes, and sum() does not even exist on it. The three primitive specialisations — IntStream, LongStream, DoubleStream — fix that. They carry ints, longs and doubles directly, add the numeric terminals (sum, average, summaryStatistics), and make the range loop for (int i = 0; i < n; i++) expressible as a pipeline. This lesson covers them, the conversions between the worlds, and the two places (chars() and range) where they show up whether you asked or not.

Sources

IntStream.range(0, 5);           // 0 1 2 3 4
IntStream.rangeClosed(1, 5);     // 1 2 3 4 5
IntStream.of(3, 1, 4);
IntStream.iterate(1, x -> x * 2).limit(10);          // powers of two
IntStream.iterate(1, x -> x <= 1000, x -> x * 2);    // finite (Java 9)
IntStream.generate(() -> 7).limit(3);
Arrays.stream(new int[]{1, 2, 3});                   // int[] → IntStream (an Integer[] would give Stream<Integer>)
"hello".chars();                                      // IntStream of UTF-16 code units
"hello".codePoints();                                 // IntStream of code points — safe for emoji
new Random().ints(5, 0, 100);                         // five ints in [0, 100)
LongStream.rangeClosed(1, 20);                        // for factorials: long, not int

There is no range for DoubleStream; build one with IntStream.range(...).mapToDouble(i -> i * 0.1).

Numeric terminals — the reason they exist

int sum = IntStream.rangeClosed(1, 100).sum();                 // 5050
OptionalInt max = IntStream.of(3, 9, 2).max();                 // OptionalInt[9] — no comparator needed
OptionalDouble avg = IntStream.of(1, 2, 4).average();           // 2.333…
long count = IntStream.range(0, 10).filter(i -> i % 3 == 0).count();

IntSummaryStatistics st = IntStream.of(4, 8, 15, 16, 23, 42).summaryStatistics();
st.getCount(); st.getSum(); st.getMin(); st.getMax(); st.getAverage();   // all five in one pass

max/min need no comparator (the natural order of numbers is built in); average returns OptionalDouble (empty for no elements); sum of an empty stream is 0. Overflow is silent: IntStream.rangeClosed(1, 20).reduce(1, (a, b) -> a * b) wraps around — use LongStream or asLongStream() before multiplying, and Math.multiplyExact if you would rather fail than be wrong.

Moving between the worlds

From → toMethod
Stream<T> → IntStreammapToInt(ToIntFunction) — mapToInt(String::length)
IntStream → Stream<Integer>boxed()
IntStream → Stream<R>mapToObj(IntFunction) — mapToObj(i -> "#" + i)
IntStream → LongStream / DoubleStreamasLongStream(), asDoubleStream(), or mapToLong/mapToDouble
Stream<Integer> → int[]mapToInt(Integer::intValue).toArray()
IntStream → List<Integer>boxed().toList()

IntStream.toArray() gives an int[]; Stream<Integer>.toArray() gives an Object[] — a favourite source of surprise. Collectors work only on object streams: boxed() before collect(groupingBy(...)).

Working with characters

long vowels = s.chars().filter(c -> "aeiou".indexOf(c) >= 0).count();
String upper = s.chars().mapToObj(c -> String.valueOf((char) Character.toUpperCase(c))).collect(joining());
boolean allDigits = !s.isEmpty() && s.chars().allMatch(Character::isDigit);
Map<Character, Long> freq = s.chars().mapToObj(c -> (char) c).collect(groupingBy(c -> c, TreeMap::new, counting()));
String reversed = new StringBuilder(s).reverse().toString();   // not a stream job — know when to stop

chars() yields ints, so the char cast is on you; mapToObj(c -> (char) c) gets you a Stream<Character> for collectors. For text that may contain characters outside the basic plane (emoji, some CJK), codePoints() and Character.toChars are the correct pair.

Index-driven pipelines

Streams have no index. When you need one, generate it:

IntStream.range(0, list.size()).filter(i -> i % 2 == 0).mapToObj(list::get).toList();   // even positions
IntStream.range(0, a.length).mapToObj(i -> a[i] + "@" + i).forEach(System.out::println);
IntStream.range(0, n).map(i -> n - 1 - i);                                             // count down

If the loop body needs two indices or mutates as it goes, the for loop is the right tool; a stream contorted around an index is worse than the loop it replaced.

Boxing costs, measured honestly

On a million integers, Stream<Integer> summing via reduce(0, Integer::sum) allocates a million Integers (well, most of them — the small ones come from the cache) and runs several times slower than IntStream.sum(). It rarely matters at interview scale; it always matters in a hot loop over an int[]. The habit is cheap: reach for mapToInt the moment the pipeline turns numeric, and stay unboxed until a collector needs objects.

Interview angle

  • "Why do primitive streams exist?" To avoid boxing and to provide sum/average/summaryStatistics, which Stream<Integer> lacks.
  • "Arrays.stream(int[]) returns…?" IntStream; Arrays.stream(Integer[]) returns Stream<Integer>.
  • "How do you get from IntStream to a List<Integer>?" boxed().toList() (or collect(toList())).
  • "What does String.chars() return?" An IntStream of UTF-16 code units — cast to char, or use codePoints() for supplementary characters.
  • "What does average() return on an empty stream?" OptionalDouble.empty(); sum() returns 0.

Key takeaways

  • IntStream/LongStream/DoubleStream hold primitives: no boxing, numeric terminals built in.
  • range/rangeClosed replace index loops; iterate with a predicate for other progressions.
  • Convert with mapToInt, boxed, mapToObj, asLongStream; collectors need objects.
  • sum of nothing is 0; average/max/min return the Optional* types; overflow is silent — go long early.
  • chars() is an IntStream; cast to char or use codePoints().

Common questions

What is the difference between IntStream.range and rangeClosed?

IntStream.range(0, 5) excludes its end and yields 0 to 4, like a for (int i = 0; i < 5; i++) loop. IntStream.rangeClosed(1, 5) includes it and yields 1 to 5. DoubleStream has no range method.

How do you convert an IntStream to a List in Java?

Box the values first, because collections hold objects: intStream.boxed().toList(), or boxed().collect(Collectors.toList()). For an array instead, intStream.toArray() returns an int[] directly.

What does String.chars() return in Java?

An IntStream of the string's UTF-16 code units, so each element is an int you must cast to char; mapToObj(c -> (char) c) gives a Stream<Character> for collectors. For text with emoji or other supplementary characters, use codePoints() instead.

Why is IntStream faster than Stream<Integer>?

Stream<Integer> boxes every element into an Integer object and unboxes it for each arithmetic step, while IntStream works on int values directly. Over a million numbers the boxed version runs several times slower, so switch with mapToInt as soon as a pipeline turns numeric.

What does average() return on an empty IntStream?

An empty OptionalDouble, because the average of nothing is undefined. sum() of an empty stream returns 0, and max() and min() return an empty OptionalInt.

Exercises

Statistics in one pass

Read an integer n (n ≥ 1), n integers, then an integer k (0 ≤ k ≤ 20). Using IntStream/LongStream:

  • stats= count, sum, min, max and average (two decimals) from one summaryStatistics() call, formatted count=<c> sum=<s> min=<lo> max=<hi> avg=<a>.
  • evenSquares= the sum of the squares of the even values.
  • factorial= k! computed with LongStream.rangeClosed(1, k) and reduce — 0! is 1.

Example: 4 then 3 8 1 6 then 5 →

stats=count=4 sum=18 min=1 max=8 avg=4.50
evenSquares=100
factorial=120

Character census

Read one line of text. Using chars() pipelines (no index loops), print:

  • letters= how many characters are letters, digits= how many are digits, vowels= how many are vowels (aeiou, either case).
  • upper= the text in upper case, rebuilt from the stream with mapToObj and joining.
  • freq= the toString of a TreeMap<Character, Long> counting each lower-cased letter.

Example: Hi there, 42! →

letters=7 digits=2 vowels=3
upper=HI THERE, 42!
freq={e=2, h=2, i=1, r=1, t=1}

In this module: Streams

← Optional — a return type, not a null replacement · Parallel streams, ordering and performance →