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 → to | Method |
|---|---|
Stream<T> → IntStream | mapToInt(ToIntFunction) — mapToInt(String::length) |
IntStream → Stream<Integer> | boxed() |
IntStream → Stream<R> | mapToObj(IntFunction) — mapToObj(i -> "#" + i) |
IntStream → LongStream / DoubleStream | asLongStream(), 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, whichStream<Integer>lacks. - "
Arrays.stream(int[])returns…?"IntStream;Arrays.stream(Integer[])returnsStream<Integer>. - "How do you get from
IntStreamto aList<Integer>?"boxed().toList()(orcollect(toList())). - "What does
String.chars()return?" AnIntStreamof UTF-16 code units — cast tochar, or usecodePoints()for supplementary characters. - "What does
average()return on an empty stream?"OptionalDouble.empty();sum()returns0.
Key takeaways
IntStream/LongStream/DoubleStreamhold primitives: no boxing, numeric terminals built in.range/rangeClosedreplace index loops;iteratewith a predicate for other progressions.- Convert with
mapToInt,boxed,mapToObj,asLongStream; collectors need objects. sumof nothing is 0;average/max/minreturn theOptional*types; overflow is silent — golongearly.chars()is anIntStream; cast tocharor usecodePoints().
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 onesummaryStatistics()call, formattedcount=<c> sum=<s> min=<lo> max=<hi> avg=<a>.evenSquares=the sum of the squares of the even values.factorial=k!computed withLongStream.rangeClosed(1, k)andreduce—0!is1.
Example: 4 then 3 8 1 6 then 5 →
stats=count=4 sum=18 min=1 max=8 avg=4.50
evenSquares=100
factorial=120Character 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 withmapToObjandjoining.freq=thetoStringof aTreeMap<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
- Streams — pipelines over data
- Intermediate operations — map, filter, flatMap and friends
- Terminal operations — reduce, match, find, count
- Collectors — toMap, joining, groupingBy, partitioningBy
- Optional — a return type, not a null replacement
- Primitive streams — IntStream, LongStream, DoubleStream (this lesson)
- Parallel streams, ordering and performance
- Checkpoint — Streams
← Optional — a return type, not a null replacement · Parallel streams, ordering and performance →