Collections and Stream API
Java Stream API vs TypeScript array methods — map/filter/reduce/groupBy
Introduction
In this lesson, you'll learn about collections and stream api in Java. Coming from TypeScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In TypeScript, you're familiar with java stream api vs typescript array methods — map/filter/reduce/groupby.
Java has its own approach to java stream api vs typescript array methods — map/filter/reduce/groupby, which we'll explore step by step.
The Java Way
Let's see how Java handles this concept. Here's a typical example:
import java.util.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
// map / filter / reduce
List<Integer> doubled = numbers.stream()
.map(x -> x * 2)
.collect(toList()); // [2,4,6,8,10,12]
List<Integer> evens = numbers.stream()
.filter(x -> x % 2 == 0)
.collect(toList()); // [2,4,6]
int sum = numbers.stream()
.reduce(0, Integer::sum); // 21
// Chaining
int result = numbers.stream()
.filter(x -> x > 2)
.mapToInt(x -> x * x)
.sum(); // 86
// findFirst / anyMatch / allMatch
Optional<Integer> first = numbers.stream().filter(x -> x > 3).findFirst();
boolean any = numbers.stream().anyMatch(x -> x > 5);
boolean all = numbers.stream().allMatch(x -> x > 0);
// flatMap
List<List<Integer>> nested = List.of(List.of(1,2), List.of(3,4));
List<Integer> flat = nested.stream()
.flatMap(Collection::stream)
.collect(toList());
// groupingBy (like lodash groupBy)
Map<String, List<Integer>> grouped = numbers.stream()
.collect(groupingBy(n -> n % 2 == 0 ? "even" : "odd"));Comparing to TypeScript
Here's how you might have written similar code in TypeScript:
const numbers = [1, 2, 3, 4, 5, 6];
// map / filter / reduce
const doubled = numbers.map(x => x * 2); // [2,4,6,8,10,12]
const evens = numbers.filter(x => x % 2 === 0); // [2,4,6]
const sum = numbers.reduce((acc, x) => acc + x, 0); // 21
// Chaining
const result = numbers
.filter(x => x > 2)
.map(x => x * x)
.reduce((acc, x) => acc + x, 0); // 9+16+25+36 = 86
// find / some / every
const first = numbers.find(x => x > 3); // 4
const any = numbers.some(x => x > 5); // true
const all = numbers.every(x => x > 0); // true
// flatMap
const nested = [[1,2],[3,4]];
const flat = nested.flatMap(x => x); // [1,2,3,4]
// groupBy (ES2024 / lodash)
const grouped = Object.groupBy(numbers, n => n % 2 === 0 ? "even" : "odd");You may be used to different syntax or behavior.
Java streams are lazy — operations run only when a terminal op (collect/sum/findFirst) is called
You may be used to different syntax or behavior.
stream() creates a stream; collect(toList()) materializes it back to a List
You may be used to different syntax or behavior.
Optional<T> instead of undefined for findFirst/findAny
You may be used to different syntax or behavior.
Collectors.groupingBy replaces lodash groupBy or Object.groupBy
You may be used to different syntax or behavior.
mapToInt/mapToLong/mapToDouble return primitive streams with sum()/avg()/max()
Step-by-Step Breakdown
1. Stream Pipeline
Java streams are lazy pipelines: source → intermediate ops (map/filter) → terminal op (collect/sum). The chain doesn't run until terminal op.
numbers.filter(x => x > 2).map(x => x * 2)numbers.stream() // create stream
.filter(x -> x > 2) // intermediate (lazy)
.map(x -> x * 2) // intermediate (lazy)
.collect(toList()); // terminal — triggers evaluation2. Primitive Streams
mapToInt/Long/Double return specialized streams with numeric terminals (sum, average, stats) — avoids Integer boxing.
numbers.map(x => x * x).reduce((a,b) => a + b, 0)numbers.stream()
.mapToInt(x -> x * x) // IntStream (unboxed)
.sum(); // IntStream.sum()3. Collectors
Collectors are the terminal ops that materialize streams. joining, groupingBy, counting, toMap are the most useful.
items.map(x=>x.name).join(", ")
Object.groupBy(items, x=>x.category)// join strings
stream.collect(joining(", "))
// group by key
stream.collect(groupingBy(Item::getCategory))
// count per group
stream.collect(groupingBy(Item::getCategory, counting()))4. Optional from Streams
findFirst/findAny return Optional<T>. Use orElse, orElseGet, or map to handle the absent case.
const first = items.find(x => x.active);
const name = first?.name ?? "unknown";items.stream()
.filter(Item::isActive)
.findFirst()
.map(Item::getName)
.orElse("unknown");Common Mistakes
When coming from TypeScript, developers often make these mistakes:
- Java streams are lazy — operations run only when a terminal op (collect/sum/findFirst) is called
- stream() creates a stream; collect(toList()) materializes it back to a List
- Optional<T> instead of undefined for findFirst/findAny
Key Takeaways
- Streams are lazy — nothing runs until a terminal operation (collect/sum/findFirst)
- mapToInt/Double return primitive streams; use .sum()/.average() for numeric aggregates
- Collectors: toList(), joining(","), groupingBy(key), counting(), toMap(k,v)
- findFirst returns Optional<T>; chain with .map().orElse() to handle absent case