Java Stream vs Iterator: Performance Benchmarks and Best Practices
This article explains Java 8 Stream fundamentals, compares intermediate and terminal operations, outlines stream characteristics and advantages over collections, and presents detailed benchmark results comparing Stream, parallel Stream, and traditional iterator across various data‑processing tasks.
Stream is a key abstraction added in Java SE 8, defined in java.util.stream, representing object streams ( Stream<T>) and specialized streams such as IntStream, LongStream, and DoubleStream.
Java 8 introduced Stream to replace some Collection operations; each stream represents a sequence of values and provides common aggregation operations, allowing convenient processing of collections, arrays, and other data structures.
Stream operation types
Intermediate operations
All operations applied after the data source is placed on the pipeline are called intermediate operations.
Intermediate operations return a stream, allowing chaining.
Examples include filter, distinct, map, sorted, etc.
Terminal operations
After all intermediate operations, a terminal operation is needed to retrieve data from the pipeline.
Terminal operations can produce a result or convert it to a collection, array, String, etc.
Characteristics of streams
Can be traversed only once
Once elements have passed through the pipeline, they cannot be operated on again; a new stream must be created from the source.
Uses internal iteration
Unlike external iteration with Iterator, stream processing is performed internally, which is more efficient for large data sets.
Advantages of streams over collections
No storage: streams do not store values; elements come from the source.
Functional style: operations produce results without modifying the source.
Lazy evaluation: many operations are lazy, enabling short‑circuiting.
Unbounded: streams can represent infinite sequences.
Concise code: stream pipelines are often more readable than traditional loops.
Stream vs Iterator efficiency comparison
Conclusion: traditional iterator (for‑loop) outperforms Stream for small data sizes; parallel streams excel for large data on multi‑core CPUs.
Test environment
System: Ubuntu 16.04 xenial
CPU: Intel Core i7-8550U
RAM: 16GB
JDK version: 1.8.0_151
JVM: HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
JVM Settings:
-Xms1024m
-Xmx6144m
-XX:MaxMetaspaceSize=512m
-XX:ReservedCodeCacheSize=1024m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=1001. Mapping test
Increment each element of a List<Integer> by 1 and collect into a new list. Benchmarked with Stream, iterator, and parallel Stream.
//stream
List<Integer> result = list.stream()
.mapToInt(x -> x)
.map(x -> ++x)
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
//iterator
List<Integer> result = new ArrayList<>();
for (Integer e : list) {
result.add(++e);
}
//parallel stream
List<Integer> result = list.parallelStream()
.mapToInt(x -> x)
.map(x -> ++x)
.boxed()
.collect(Collectors.toCollection(ArrayList::new));2. Filtering test
Filter elements greater than 200 from a List<Integer> and collect.
//stream
List<Integer> result = list.stream()
.mapToInt(x -> x)
.filter(x -> x > 200)
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
//iterator
List<Integer> result = new ArrayList<>(list.size());
for (Integer e : list) {
if (e > 200) {
result.add(e);
}
}
//parallel stream
List<Integer> result = list.parallelStream()
.mapToInt(x -> x)
.filter(x -> x > 200)
.boxed()
.collect(Collectors.toCollection(ArrayList::new));3. Natural sorting test
Sort a List<Integer> using Stream and iterator (Collections.sort).
//stream
List<Integer> result = list.stream()
.mapToInt(x -> x)
.sorted()
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
//iterator
List<Integer> result = new ArrayList<>(list);
Collections.sort(result);
//parallel stream
List<Integer> result = list.parallelStream()
.mapToInt(x -> x)
.sorted()
.boxed()
.collect(Collectors.toCollection(ArrayList::new));4. Reduction test
Find the maximum value in a List<Integer>.
//stream
int max = list.stream()
.mapToInt(x -> x)
.max()
.getAsInt();
//iterator
int max = -1;
for (Integer e : list) {
if (e > max) {
max = e;
}
}
//parallel stream
int max = list.parallelStream()
.mapToInt(x -> x)
.max()
.getAsInt();5. String joining test
Join List<Integer> elements into a comma‑separated string.
//stream
String result = list.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
//iterator
StringBuilder builder = new StringBuilder();
for (Integer e : list) {
builder.append(e).append(",");
}
String result = builder.length() == 0 ? "" : builder.substring(0, builder.length() - 1);
//parallel stream
String result = list.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));6. Mixed operations test
Filter nulls, remove duplicates, map, filter, and collect.
//stream
List<Integer> result = list.stream()
.filter(Objects::nonNull)
.mapToInt(x -> x + 1)
.filter(x -> x > 200)
.distinct()
.boxed()
.collect(Collectors.toCollection(ArrayList::new));
//iterator
HashSet<Integer> set = new HashSet<>(list.size());
for (Integer e : list) {
if (e != null && e > 200) {
set.add(e + 1);
}
}
List<Integer> result = new ArrayList<>(set);
//parallel stream
List<Integer> result = list.parallelStream()
.filter(Objects::nonNull)
.mapToInt(x -> x + 1)
.filter(x -> x > 200)
.distinct()
.boxed()
.collect(Collectors.toCollection(ArrayList::new));Experimental results summary
For small data (size ≤ 1000), iterator is faster, but the difference is sub‑millisecond and streams provide cleaner code.
For large data (size > 10000), streams, especially parallel streams on multi‑core CPUs, outperform iterator.
Parallel streams depend heavily on CPU core availability; without multiple cores, they may be slower due to ForkJoinPool overhead.
Recommendations for using streams
Use iterator for simple loops; use streams for multi‑step pipelines to gain readability with minimal performance loss.
Avoid parallel streams on single‑core CPUs; prefer them on multi‑core machines with large data sets.
When streams involve boxed types, convert to primitive streams before intermediate operations to reduce boxing overhead.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
