Skip to main content

Stop Writing For Loops: Master Java Streams with 5 Essential Patterns

Β· 14 min read
Mahmut Salman
Software Developer

I used to think Java Streams were confusing magic. I avoided them for months, writing nested for loops like it was 2005. Then I saw a senior developer calculate cart totals in ONE line of code that took me 15 lines. I felt like a caveman discovering fire. πŸ”₯ Let me teach you the 5 patterns that cover 90% of real-world Stream usageβ€”no PhD required.

The Moment I Realized I Was Living in the Past​

Picture this: Code review time. I proudly submitted this masterpiece:

// My "beautiful" code
public Integer calculateTotalItems(List<CartItemDTO> items) {
Integer totalItems = 0;
for (CartItemDTO item : items) {
totalItems += item.getQuantity();
}
return totalItems;
}

public BigDecimal calculateTotalPrice(List<CartItemDTO> items) {
BigDecimal totalPrice = BigDecimal.ZERO;
for (CartItemDTO item : items) {
totalPrice = totalPrice.add(item.getSubtotal());
}
return totalPrice;
}

Senior developer's feedback: "You know Streams exist, right?"

His version:

// One-liner magic
Integer totalItems = items.stream()
.mapToInt(CartItemDTO::getQuantity)
.sum();

BigDecimal totalPrice = items.stream()
.map(CartItemDTO::getSubtotal)
.reduce(BigDecimal.ZERO, BigDecimal::add);

My reaction: "What sorcery is this?!" 🀯

His response: "It's called Java 8. Been around since 2014."


What Are Java Streams? (The Simple Explanation)​

The Assembly Line Analogy πŸ­β€‹

Think of a Stream as an assembly line in a factory:

Raw Materials β†’ Conveyor Belt β†’ Processing Stations β†’ Final Product
(List) (stream()) (operations) (collect())

Example:

Shopping Cart Items β†’ stream() β†’ filter() β†’ map() β†’ collect() β†’ Product IDs

Each "station" (method) does ONE job:

  • filter() - Quality control (keep only good items)
  • map() - Transform items (extract specific part)
  • reduce() - Combine items (add them up)
  • collect() - Package final result

Why Streams Matter​

Old Way Problems:

// ❌ Verbose and repetitive
List<Long> productIds = new ArrayList<>();
for (CartItemDTO item : items) {
if (item.getQuantity() > 0) {
productIds.add(item.getProductId());
}
}

Problems:

  1. ❌ 5 lines for simple operation
  2. ❌ Hard to read intent
  3. ❌ Easy to make mistakes (off-by-one, wrong variable)
  4. ❌ Can't run in parallel easily

Stream Way:

// βœ… Clear and concise
List<Long> productIds = items.stream()
.filter(item -> item.getQuantity() > 0)
.map(CartItemDTO::getProductId)
.collect(Collectors.toList());

Benefits:

  1. βœ… 4 lines β†’ readable
  2. βœ… Intent is crystal clear
  3. βœ… Less error-prone
  4. βœ… Can easily parallelize with .parallelStream()

Stream Fundamentals: The Building Blocks​

The Stream Pipeline​

Every Stream operation follows this pattern:

Source β†’ Intermediate Operations β†’ Terminal Operation β†’ Result

Example:

items.stream()                           // ← Source: Start the pipeline
.filter(item -> item.getQuantity() > 0) // ← Intermediate: Process
.map(CartItemDTO::getProductId) // ← Intermediate: Process
.collect(Collectors.toList()); // ← Terminal: End & get result

Key Concept: Lazy Evaluation​

Streams don't do anything until you ask for a result!

// This does NOTHING (no terminal operation)
items.stream()
.filter(item -> item.getQuantity() > 0)
.map(CartItemDTO::getProductId);
// ↑ No result, nothing happened

// This EXECUTES (has terminal operation)
List<Long> ids = items.stream()
.filter(item -> item.getQuantity() > 0)
.map(CartItemDTO::getProductId)
.collect(Collectors.toList()); // ← Terminal operation triggers execution

The 5 Essential Patterns (90% of Real-World Usage)​

Pattern 1: FILTERING πŸ”β€‹

Use Case: Keep only items that match a condition

Real-World Scenario: "Show me only items that are in stock"

Basic Filter​

// What you want: "Give me only items with quantity > 0"
List<CartItemDTO> inStockItems = items.stream()
.filter(item -> item.getQuantity() > 0)
.collect(Collectors.toList());

Breaking it down:

  • filter(item -> item.getQuantity() > 0) - Keep items where quantity > 0
  • item -> - Lambda: For each item, do this check
  • collect(Collectors.toList()) - Gather results into a List

Old Way Comparison:

// Old Java (pre-Java 8)
List<CartItemDTO> inStockItems = new ArrayList<>();
for (CartItemDTO item : items) {
if (item.getQuantity() > 0) {
inStockItems.add(item);
}
}

Notice: Stream version reads like English: "Filter items by quantity greater than zero"

More Filter Examples​

// Example 1: Only premium items
List<CartItemDTO> premiumItems = items.stream()
.filter(CartItemDTO::isPremium) // ← Method reference shorthand
.collect(Collectors.toList());

// Example 2: Only items cheaper than $50
List<CartItemDTO> affordableItems = items.stream()
.filter(item -> item.getPrice().compareTo(new BigDecimal("50")) < 0)
.collect(Collectors.toList());

// Example 3: Multiple conditions (AND)
List<CartItemDTO> premiumInStock = items.stream()
.filter(item -> item.getQuantity() > 0 && item.isPremium())
.collect(Collectors.toList());

// Example 4: Multiple conditions (OR)
List<CartItemDTO> specialItems = items.stream()
.filter(item -> item.isPremium() || item.isOnSale())
.collect(Collectors.toList());

// Example 5: Complex conditions
List<CartItemDTO> qualifiedItems = items.stream()
.filter(item -> {
boolean inStock = item.getQuantity() > 0;
boolean affordable = item.getPrice().compareTo(new BigDecimal("100")) < 0;
boolean goodRating = item.getRating() >= 4.0;
return inStock && affordable && goodRating;
})
.collect(Collectors.toList());

When to use: Whenever you need to "keep only items that..."


Pattern 2: TRANSFORMING (Map) πŸ”„β€‹

Use Case: Convert each item into something different

Real-World Scenario: "I have cart items, but I only need product IDs"

Basic Map​

// What you want: "Give me just the product IDs"
List<Long> productIds = items.stream()
.map(CartItemDTO::getProductId)
.collect(Collectors.toList());

Breaking it down:

  • map(CartItemDTO::getProductId) - Transform each CartItemDTO into its productId
  • Result: List of CartItemDTOs β†’ List of Longs

Old Way Comparison:

// Old Java
List<Long> productIds = new ArrayList<>();
for (CartItemDTO item : items) {
productIds.add(item.getProductId());
}

More Map Examples​

// Example 1: Get all product names
List<String> productNames = items.stream()
.map(CartItemDTO::getProductName)
.collect(Collectors.toList());

// Example 2: Transform names to uppercase
List<String> upperNames = items.stream()
.map(CartItemDTO::getProductName)
.map(String::toUpperCase) // ← Chain transformations!
.collect(Collectors.toList());

// Example 3: Calculate subtotals
List<BigDecimal> subtotals = items.stream()
.map(CartItemDTO::getSubtotal)
.collect(Collectors.toList());

// Example 4: Complex transformation
List<String> itemSummaries = items.stream()
.map(item -> item.getProductName() + " ($" + item.getPrice() + ")")
.collect(Collectors.toList());
// Result: ["Laptop ($1200.00)", "Mouse ($25.00)", ...]

// Example 5: Transform to different DTO
List<ProductSummaryDTO> summaries = items.stream()
.map(item -> new ProductSummaryDTO(
item.getProductId(),
item.getProductName(),
item.getPrice()
))
.collect(Collectors.toList());

When to use: Whenever you need to "convert each item into..."


Pattern 3: COMBINING (Reduce) βž•β€‹

Use Case: Combine all items into a single value

Real-World Scenario: "Calculate total price of all items"

Basic Reduce​

// What you want: "Add up all the subtotals"
BigDecimal totalPrice = items.stream()
.map(CartItemDTO::getSubtotal)
.reduce(BigDecimal.ZERO, BigDecimal::add);

Breaking it down:

  • map(CartItemDTO::getSubtotal) - Get each item's subtotal
  • reduce(BigDecimal.ZERO, BigDecimal::add) - Start with ZERO, add each subtotal
    • First parameter: Starting value
    • Second parameter: How to combine (the add method)

Old Way Comparison:

// Old Java
BigDecimal totalPrice = BigDecimal.ZERO;
for (CartItemDTO item : items) {
totalPrice = totalPrice.add(item.getSubtotal());
}

More Reduce Examples​

// Example 1: Sum quantities (shortcut with mapToInt)
Integer totalQuantity = items.stream()
.mapToInt(CartItemDTO::getQuantity)
.sum(); // ← sum() is a built-in shortcut

// Example 2: Find maximum price
BigDecimal maxPrice = items.stream()
.map(CartItemDTO::getPrice)
.reduce(BigDecimal.ZERO, BigDecimal::max);

// Example 3: Concatenate all product names
String allNames = items.stream()
.map(CartItemDTO::getProductName)
.reduce("", (acc, name) -> acc + ", " + name);
// Result: ", Laptop, Mouse, Keyboard"

// Example 4: Concatenate with better formatting
String formattedNames = items.stream()
.map(CartItemDTO::getProductName)
.collect(Collectors.joining(", "));
// Result: "Laptop, Mouse, Keyboard"

// Example 5: Calculate average price
Double avgPrice = items.stream()
.mapToDouble(item -> item.getPrice().doubleValue())
.average()
.orElse(0.0);

// Example 6: Count items (simple way)
long count = items.stream().count();

// Example 7: Custom reduction - multiply all prices (unusual but shows concept)
BigDecimal product = items.stream()
.map(CartItemDTO::getPrice)
.reduce(BigDecimal.ONE, BigDecimal::multiply);

When to use: Whenever you need to "combine all items into one value"


Pattern 4: CHECKING CONDITIONS βœ…β€‹

Use Case: Test if items match a condition

Real-World Scenario: "Does cart have any premium items?"

Basic Checks​

// Does cart have ANY premium items?
boolean hasPremium = items.stream()
.anyMatch(CartItemDTO::isPremium);

// Are ALL items in stock?
boolean allInStock = items.stream()
.allMatch(item -> item.getQuantity() > 0);

// Does cart have NO premium items?
boolean noPremium = items.stream()
.noneMatch(CartItemDTO::isPremium);

Breaking it down:

  • anyMatch() - Returns true if at least one item matches
  • allMatch() - Returns true if all items match
  • noneMatch() - Returns true if no items match

Old Way Comparison:

// Old Java - anyMatch
boolean hasPremium = false;
for (CartItemDTO item : items) {
if (item.isPremium()) {
hasPremium = true;
break; // Can stop early
}
}

// Old Java - allMatch
boolean allInStock = true;
for (CartItemDTO item : items) {
if (item.getQuantity() <= 0) {
allInStock = false;
break;
}
}

More Check Examples​

// Example 1: Does cart have expensive items (>$100)?
boolean hasExpensive = items.stream()
.anyMatch(item -> item.getPrice().compareTo(new BigDecimal("100")) > 0);

// Example 2: Are all items affordable (<$50)?
boolean allAffordable = items.stream()
.allMatch(item -> item.getPrice().compareTo(new BigDecimal("50")) < 0);

// Example 3: Is cart value over $1000?
boolean over1000 = items.stream()
.map(CartItemDTO::getSubtotal)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.compareTo(new BigDecimal("1000")) > 0;

// Example 4: Does cart have items from specific category?
boolean hasElectronics = items.stream()
.anyMatch(item -> item.getCategory().equals("Electronics"));

// Example 5: Are all items rated 4 stars or higher?
boolean allWellRated = items.stream()
.allMatch(item -> item.getRating() >= 4.0);

// Example 6: Is cart empty or all quantities zero?
boolean effectivelyEmpty = items.isEmpty() || items.stream()
.noneMatch(item -> item.getQuantity() > 0);

When to use: Whenever you need to check "if any...", "if all...", or "if none..."


Pattern 5: FINDING & SORTING πŸ”Žβ€‹

Use Case: Get specific item(s) or sort items

Real-World Scenario: "Find the most expensive item" or "Sort by price"

Finding Items​

// Find the most expensive item
Optional<CartItemDTO> mostExpensive = items.stream()
.max(Comparator.comparing(CartItemDTO::getPrice));

CartItemDTO expensive = mostExpensive.orElse(null); // Handle if not found

// Find the cheapest item
Optional<CartItemDTO> cheapest = items.stream()
.min(Comparator.comparing(CartItemDTO::getPrice));

// Find first premium item
Optional<CartItemDTO> firstPremium = items.stream()
.filter(CartItemDTO::isPremium)
.findFirst();

// Find any premium item (faster in parallel streams)
Optional<CartItemDTO> anyPremium = items.stream()
.filter(CartItemDTO::isPremium)
.findAny();

Breaking it down:

  • max() / min() - Find largest/smallest based on comparator
  • findFirst() - Get first item (returns Optional)
  • findAny() - Get any item (useful in parallel processing)
  • Optional - Container that might have a value or be empty (prevents null errors)

Old Way Comparison:

// Old Java - find max
CartItemDTO mostExpensive = null;
BigDecimal maxPrice = BigDecimal.ZERO;
for (CartItemDTO item : items) {
if (item.getPrice().compareTo(maxPrice) > 0) {
maxPrice = item.getPrice();
mostExpensive = item;
}
}

Sorting Items​

// Sort by price (low to high)
List<CartItemDTO> sortedByPrice = items.stream()
.sorted(Comparator.comparing(CartItemDTO::getPrice))
.collect(Collectors.toList());

// Sort by price (high to low)
List<CartItemDTO> sortedDescending = items.stream()
.sorted(Comparator.comparing(CartItemDTO::getPrice).reversed())
.collect(Collectors.toList());

// Sort by multiple fields: price, then name
List<CartItemDTO> sortedMultiple = items.stream()
.sorted(Comparator.comparing(CartItemDTO::getPrice)
.thenComparing(CartItemDTO::getProductName))
.collect(Collectors.toList());

Limiting and Skipping​

// Get only first 3 items
List<CartItemDTO> first3 = items.stream()
.limit(3)
.collect(Collectors.toList());

// Skip first 2, get the rest
List<CartItemDTO> skipFirst2 = items.stream()
.skip(2)
.collect(Collectors.toList());

// Pagination: Get items 10-20
List<CartItemDTO> page2 = items.stream()
.skip(10)
.limit(10)
.collect(Collectors.toList());

// Top 5 most expensive items
List<CartItemDTO> top5 = items.stream()
.sorted(Comparator.comparing(CartItemDTO::getPrice).reversed())
.limit(5)
.collect(Collectors.toList());

More Finding Examples​

// Example 1: Find item by product ID
Optional<CartItemDTO> found = items.stream()
.filter(item -> item.getProductId().equals(5L))
.findFirst();

// Example 2: Get distinct product categories
List<String> categories = items.stream()
.map(CartItemDTO::getCategory)
.distinct() // ← Remove duplicates
.collect(Collectors.toList());

// Example 3: Check if item exists
boolean exists = items.stream()
.anyMatch(item -> item.getProductId().equals(5L));

// Example 4: Get top 3 rated items
List<CartItemDTO> topRated = items.stream()
.sorted(Comparator.comparing(CartItemDTO::getRating).reversed())
.limit(3)
.collect(Collectors.toList());

When to use: Whenever you need to "find the...", "sort by...", or "get first N..."


Combining Patterns: Real-World Examples​

Example 1: E-Commerce Cart Total​

Requirement: Calculate total price of in-stock items only

BigDecimal total = items.stream()
.filter(item -> item.getQuantity() > 0) // Pattern 1: Filter
.map(CartItemDTO::getSubtotal) // Pattern 2: Transform
.reduce(BigDecimal.ZERO, BigDecimal::add); // Pattern 3: Reduce

Old Way:

BigDecimal total = BigDecimal.ZERO;
for (CartItemDTO item : items) {
if (item.getQuantity() > 0) {
total = total.add(item.getSubtotal());
}
}

Example 2: Premium Items Summary​

Requirement: Get names of top 3 most expensive premium items

List<String> topPremium = items.stream()
.filter(CartItemDTO::isPremium) // Pattern 1: Filter
.sorted(Comparator.comparing(CartItemDTO::getPrice).reversed()) // Pattern 5: Sort
.limit(3) // Pattern 5: Limit
.map(CartItemDTO::getProductName) // Pattern 2: Transform
.collect(Collectors.toList());

Old Way:

List<CartItemDTO> premiumItems = new ArrayList<>();
for (CartItemDTO item : items) {
if (item.isPremium()) {
premiumItems.add(item);
}
}

premiumItems.sort((a, b) -> b.getPrice().compareTo(a.getPrice()));

List<String> topPremium = new ArrayList<>();
for (int i = 0; i < Math.min(3, premiumItems.size()); i++) {
topPremium.add(premiumItems.get(i).getProductName());
}

Example 3: Validation Check​

Requirement: Check if cart is valid (all items in stock and total under $10,000)

boolean allInStock = items.stream()
.allMatch(item -> item.getQuantity() > 0); // Pattern 4: Check

BigDecimal total = items.stream()
.map(CartItemDTO::getSubtotal) // Pattern 2: Transform
.reduce(BigDecimal.ZERO, BigDecimal::add); // Pattern 3: Reduce

boolean validCart = allInStock &&
total.compareTo(new BigDecimal("10000")) < 0;

Example 4: Product Statistics​

Requirement: Get average price of electronics

Double avgElectronicsPrice = items.stream()
.filter(item -> item.getCategory().equals("Electronics")) // Pattern 1: Filter
.mapToDouble(item -> item.getPrice().doubleValue()) // Pattern 2: Transform
.average() // Pattern 3: Reduce
.orElse(0.0);

Example 5: Complex Business Logic​

Requirement: Apply 10% discount to items over $100, then calculate total

BigDecimal discountedTotal = items.stream()
.map(item -> {
BigDecimal price = item.getPrice();
if (price.compareTo(new BigDecimal("100")) > 0) {
// Apply 10% discount
return price.multiply(new BigDecimal("0.9"))
.multiply(new BigDecimal(item.getQuantity()));
}
return item.getSubtotal();
})
.reduce(BigDecimal.ZERO, BigDecimal::add);

Quick Reference Guide​

When to Use Which Pattern​

What You NeedPatternMethod
Keep matching itemsFilter.filter()
Convert itemsTransform.map()
Calculate total/sumReduce.reduce() / .sum()
Check if any/all matchCheck.anyMatch() / .allMatch()
Find max/minFind.max() / .min()
Sort itemsSort.sorted()
Get first N itemsLimit.limit()
Get averageReduce.average()
Count itemsReduce.count()

Common Terminal Operations​

.collect(Collectors.toList())  // β†’ List
.collect(Collectors.toSet()) // β†’ Set
.reduce() // β†’ Single value
.sum() // β†’ Number
.count() // β†’ long
.average() // β†’ OptionalDouble
.min() / .max() // β†’ Optional
.anyMatch() / .allMatch() // β†’ boolean
.findFirst() / .findAny() // β†’ Optional
.forEach() // β†’ void (side effects)

Performance Tips & Best Practices​

βœ… Do This​

// 1. Use method references when possible
items.stream()
.map(CartItemDTO::getProductName) // βœ… Clean
.collect(Collectors.toList());

// 2. Use mapToInt/mapToDouble for primitives (better performance)
int total = items.stream()
.mapToInt(CartItemDTO::getQuantity) // βœ… Primitive stream
.sum();

// 3. Short-circuit operations
boolean hasExpensive = items.stream()
.anyMatch(item -> item.getPrice().compareTo(new BigDecimal("100")) > 0);
// βœ… Stops at first match, doesn't check all items

// 4. Use parallel streams for large datasets (1000+ items)
BigDecimal total = items.parallelStream()
.map(CartItemDTO::getSubtotal)
.reduce(BigDecimal.ZERO, BigDecimal::add);

❌ Don't Do This​

// 1. Don't use lambdas when method reference works
items.stream()
.map(item -> item.getProductName()) // ❌ Verbose
.collect(Collectors.toList());

// 2. Don't modify external variables (side effects)
List<String> names = new ArrayList<>();
items.stream()
.forEach(item -> names.add(item.getProductName())); // ❌ Bad practice

// Use collect instead:
List<String> names = items.stream()
.map(CartItemDTO::getProductName)
.collect(Collectors.toList()); // βœ… Functional

// 3. Don't reuse streams
Stream<CartItemDTO> stream = items.stream();
long count = stream.count();
// stream.collect(Collectors.toList()); // ❌ IllegalStateException

// Create new stream for each operation:
long count = items.stream().count(); // βœ…
List<CartItemDTO> list = items.stream().collect(Collectors.toList()); // βœ…

// 4. Don't use parallel for small datasets or order-dependent operations
items.parallelStream() // ❌ Overhead not worth it for <1000 items
.limit(10)
.collect(Collectors.toList());

How to Approach New Problems​

When facing a new requirement, ask yourself:

Step 1: What do I need?​

NeedPattern
"Keep only items that..."β†’ Filter
"Convert each item to..."β†’ Map
"Calculate total/sum of..."β†’ Reduce
"Check if any/all..."β†’ Check
"Find the max/min..."β†’ Find
"Sort items by..."β†’ Sort

Step 2: Combine patterns​

Most real problems use multiple patterns:

Filter β†’ Map β†’ Reduce
Filter β†’ Sort β†’ Limit
Filter β†’ Map β†’ Collect

Step 3: Practice​

Convert your existing for loops to Streams:

Before:

List<String> result = new ArrayList<>();
for (Item item : items) {
if (item.isValid()) {
result.add(item.getName());
}
}

After:

List<String> result = items.stream()
.filter(Item::isValid)
.map(Item::getName)
.collect(Collectors.toList());

Common Mistakes to Avoid​

Mistake 1: Forgetting Terminal Operation​

// ❌ This does NOTHING
items.stream()
.filter(item -> item.getQuantity() > 0)
.map(CartItemDTO::getProductId);

// βœ… Add terminal operation
List<Long> ids = items.stream()
.filter(item -> item.getQuantity() > 0)
.map(CartItemDTO::getProductId)
.collect(Collectors.toList()); // ← Terminal operation

Mistake 2: Not Handling Optional​

// ❌ Can throw NoSuchElementException
CartItemDTO item = items.stream()
.max(Comparator.comparing(CartItemDTO::getPrice))
.get(); // ← Dangerous if list is empty

// βœ… Handle Optional properly
CartItemDTO item = items.stream()
.max(Comparator.comparing(CartItemDTO::getPrice))
.orElse(null); // or .orElseThrow() or .orElseGet()

Mistake 3: Modifying Source Collection​

// ❌ ConcurrentModificationException
items.stream()
.filter(item -> item.getQuantity() == 0)
.forEach(items::remove); // ← Don't modify source during stream

// βœ… Collect items to remove first
List<CartItemDTO> toRemove = items.stream()
.filter(item -> item.getQuantity() == 0)
.collect(Collectors.toList());
items.removeAll(toRemove);

Summary: The 5 Patterns​

Pattern Cheat Sheet​

#PatternUse ForKey Methods
1FilterKeep matching items.filter()
2TransformConvert items.map(), .mapToInt()
3ReduceCombine into one value.reduce(), .sum(), .average()
4CheckTest conditions.anyMatch(), .allMatch(), .noneMatch()
5Find/SortGet specific items.max(), .min(), .sorted(), .limit()

Why Streams Matter​

Before Streams (Java 7):

  • ❌ Verbose for loops
  • ❌ Error-prone manual state management
  • ❌ Hard to parallelize
  • ❌ Intent hidden in implementation

After Streams (Java 8+):

  • βœ… Concise, readable code
  • βœ… Functional, immutable approach
  • βœ… Easy parallelization
  • βœ… Intent expressed clearly

Practice Challenge πŸŽ―β€‹

Convert these for loops to Streams:

Challenge 1​

// Find all items over $50
List<CartItemDTO> expensive = new ArrayList<>();
for (CartItemDTO item : items) {
if (item.getPrice().compareTo(new BigDecimal("50")) > 0) {
expensive.add(item);
}
}
Solution
List<CartItemDTO> expensive = items.stream()
.filter(item -> item.getPrice().compareTo(new BigDecimal("50")) > 0)
.collect(Collectors.toList());

Challenge 2​

// Get product IDs of premium items
List<Long> premiumIds = new ArrayList<>();
for (CartItemDTO item : items) {
if (item.isPremium()) {
premiumIds.add(item.getProductId());
}
}
Solution
List<Long> premiumIds = items.stream()
.filter(CartItemDTO::isPremium)
.map(CartItemDTO::getProductId)
.collect(Collectors.toList());

Challenge 3​

// Calculate average price of electronics
double sum = 0;
int count = 0;
for (CartItemDTO item : items) {
if (item.getCategory().equals("Electronics")) {
sum += item.getPrice().doubleValue();
count++;
}
}
double avg = count > 0 ? sum / count : 0;
Solution
double avg = items.stream()
.filter(item -> item.getCategory().equals("Electronics"))
.mapToDouble(item -> item.getPrice().doubleValue())
.average()
.orElse(0.0);

What's Next?​

Now that you've mastered the 5 core patterns, explore:

  • Advanced collectors - Collectors.groupingBy(), partitioningBy()
  • Parallel streams - When and how to use .parallelStream()
  • Custom collectors - Create your own collection strategies
  • Optional API - Deep dive into handling null safely
  • Stream debugging - Using .peek() for debugging

Questions about Streams? Drop them in the comments! πŸ‘‡

Converted a gnarly for loop to Streams? Share your before/after! πŸš€

Found this helpful? Share it with developers still living in 2005! πŸ˜„