Explain Codes LogoExplain Codes Logo

Java 8 Streams - collect vs reduce

java
collectors
parallel-processing
thread-safety
Alex KataevbyAlex Kataev·Feb 25, 2025
TLDR

Choose collect when you need to corral stream elements into a handy collection (think List, Set, or Map). That's where Collectors utility methods play their part:

List<String> gatheredTogether = stream.collect(Collectors.toList());

Impressive strains of data bundled up together. Like wrangling wild unicorns.

On the other hand, grab reduce when your mission includes fusing stream elements down into a single, distilled result. It's through continuous applications that reduce shines, proving itself worthy when calculating sums, min/max values, or string concatenation:

// Concatenate strings like there's no tomorrow String mixAndMatch = stream.reduce("", String::concat);

A beautiful blend, like mixing the perfect cocktail.

Digestible breakdown: How to use collect

Put simply, collect works by performing mutable reduction, usually the most efficient route when you're assembling results into a mutable container such as ArrayList or StringBuilder. Here's the skinny:

  1. Thread Safety: Working in parallel? Breathe easy. collect() ensures a safe passage during operation, providing thread-confined mutable containers.
  2. Custom Accumulators: Collectors equips you with off-the-shelf accumulators, taking care of the tricky stuff like grouping, partitioning, or summarizing.
  3. Performance with String Concatenation:
    Say goodbye to memory woes. With a StringBuilder and collect(), memory overhead takes a backseat and speed races ahead.
// Say hello to StringBuilder and goodbye to lousy laziness. String zappyResult = stream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();

Flipping the coin: Where to use reduce

Switch to reduce when handling immutable values and when an operation requires a fresh value each time (think: arithmetic operations).

  1. Immutable Reduction: Picture a flawless BigDecimal calculation. No state-changing mischief here.
  2. Sequential Processing: If you're sticking with sequential streams or aren't worried about parallel processing, reduce has your back.
  3. Binary Operations: Go binary when the resulting type matches the stream elements type.
// Integer::sum - SQL developers, now we are talking your language! int sum = stream.reduce(0, Integer::sum);

See? Numbers can play nice together.

Doubling down: Advanced techniques and considerations

Seasoned professionals, looking for a little bit more? We've got some caviar tips just for you.

Crafting Custom Collectors

At times, Collectors may not have the right pants for your party. No worries — put together a custom Collector for more control over the mutable reduction process:

// Because let's face it. One size doesn't fit all! Collector<Widget, ?, TreeSet<Widget>> intoSet = Collector.of(TreeSet::new, TreeSet::add, (left, right) -> { left.addAll(right); return left; });

Combining Collectors: A higher level of artistry

A touch of fusion anyone? Collectors are mix-and-match ready for complex data structures:

// Combine like Voltron! Map<Integer, List<String>> groupedByLength = stream.collect(Collectors.groupingBy(String::length, Collectors.toList()));

Ready for easy parallelization? Just remember, reduce might slow down if your binary operator isn't playing by the associative and commutative rules. Beware of incorrect results in parallel processing.

Performance considerations

Mind the cost of object creation when dealing with reduce operations! This unsuspecting monkey business may drive your performance into the ground faster than you can say "mutable reduction."

Visualization

TechniqueKitchen ToolDish Outcome
collect🍽 Plating Tweezers🌟 Artful Presentation
reduce🔪 Chef’s Knife💪 Concentrated Flavor

collect is like plating food with tweezers. Yes, you heard it right. Tweezers. For a diverse, and (dare we say it) refined dish:

// A smorgasbord of technological delight stream().collect(Collectors.toList());

reduce? That's where we get to don the chef's hat. It's all about combining the ingredients into something so complex, yet instantly recognizable:

// Like goulash for your CPU! stream().reduce((a, b) -> a + b);

Parallel streams and their peculiarities

collect with Parallel

Parallel streams can be interesting to deal with. collect is specially crafted to handle concurrent modifications with ease, often sacrificing a smidge of performance for correctness:

// It's time for a gathering of great minds! List<String> parallelCollected = stream.parallel().collect(Collectors.toList());

reduce in the Parallel Universe

Contrarily, reduce might not require synchronization for simple tasks, which is a blessing in terms of performance. But don't forget: always mind the gap (or thread safety, more technically).

// Just like an all-night LAN party, but with less pizza. int parallelSum = stream.parallel().reduce(0, Integer::sum);