Creating Streams
Creating Streams
Streams can be created from collections, arrays, values, or generators.
import java.util.*;
import java.util.stream.*;
public class CreatingStreamsDemo {
public static void main(String[] args) {
// From Collection
List<String> list = Arrays.asList("a", "b", "c", "d");
Stream<String> listStream = list.stream();
Stream<String> parallelStream = list.parallelStream();
// From Array
int[] nums = {1, 2, 3, 4, 5};
IntStream arrayStream = Arrays.stream(nums);
Stream<Integer> boxed = Arrays.stream(nums).boxed();
// From Values
Stream<String> valueStream = Stream.of("x", "y", "z");
Stream<Integer> intStream = Stream.of(1, 2, 3, 4, 5);
// From Generator (infinite)
Stream<Double> randomStream = Stream.generate(Math::random).limit(5);
Stream<Integer> ones = Stream.iterate(1, n -> n).limit(5);
// From String chars
IntStream chars = "hello".chars();
.chars().forEach(c -> System.out.print((char) c + " "));
System.out.println();
// Range
IntStream range = IntStream.range(1, 6); // 1,2,3,4,5
IntStream rangeClosed = IntStream.rangeClosed(1, 5); // 1,2,3,4,5
// Print streams
System.out.println("List stream: " + listStream.collect(Collectors.toList()));
System.out.println("Array stream: " + arrayStream.collect(Collectors.toList()));
System.out.println("Values: " + valueStream.collect(Collectors.toList()));
System.out.println("Random: " + randomStream.collect(Collectors.toList()));
System.out.println("Range: " + range.collect(Collectors.toList()));
}
}
Stream creation summary:
| Source | Method |
|---|---|
| Collection | collection.stream() or collection.parallelStream() |
| Array | Arrays.stream(array) |
| Values | Stream.of(values...) |
| Infinite | Stream.generate(supplier) |
| Sequential | Stream.iterate(seed, unaryOp) |
| Range | IntStream.range(start, end) |
| String | string.chars() |
Intermediate Operations
Intermediate Operations
Intermediate operations are lazy — they are not executed until a terminal operation is invoked. They return a new Stream.
import java.util.*;
import java.util.stream.*;
public class IntermediateOpsDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList(
"Alice", "Bob", "Charlie", "David", "Eve",
"Alice", "Frank", "Grace"
);
// filter - select elements matching predicate
List<String> longNames = names.stream()
.filter(name -> name.length() > 4)
.collect(Collectors.toList());
System.out.println("Long names: " + longNames); // [Alice, Charlie, David, Frank, Grace]
// map - transform each element
List<Integer> lengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println("Lengths: " + lengths);
// sorted - natural or custom order
List<String> sorted = names.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("Sorted: " + sorted);
List<String> sortedByLength = names.stream()
.sorted(Comparator.comparingInt(String::length))
.collect(Collectors.toList());
System.out.println("By length: " + sortedByLength);
// distinct - remove duplicates
List<String> unique = names.stream()
.distinct()
.collect(Collectors.toList());
System.out.println("Unique: " + unique);
// flatMap - flatten nested structures
List<List<Integer>> nested = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5),
Arrays.asList(6, 7, 8, 9)
);
List<Integer> flat = nested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println("Flat: " + flat); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
// peek - debug/inspect without modifying
List<String> peeked = names.stream()
.filter(name -> name.length() > 3)
.peek(name -> System.out.println("Filtered: " + name))
.map(String::toUpperCase)
.peek(name -> System.out.println("Mapped: " + name))
.collect(Collectors.toList());
// limit and skip
List<String> first3 = names.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println("First 3: " + first3); // [Alice, Bob, Charlie]
List<String> skip3 = names.stream()
.skip(3)
.collect(Collectors.toList());
System.out.println("Skip 3: " + skip3); // [David, Eve, Alice, Frank, Grace]
// takeWhile and dropWhile (Java 9+)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 1, 2);
List<Integer> taken = numbers.stream()
.takeWhile(n -> n < 4)
.collect(Collectors.toList());
System.out.println("Take while < 4: " + taken); // [1, 2, 3]
}
}
Key intermediate operations:
| Operation | Description |
|---|---|
filter(Predicate) |
Select elements matching predicate |
map(Function) |
Transform each element |
flatMap(Function) |
Flatten nested streams |
sorted() / sorted(Comparator) |
Sort elements |
distinct() |
Remove duplicates |
limit(n) |
Take first n elements |
skip(n) |
Skip first n elements |
peek(Consumer) |
Inspect without modifying |
takeWhile(Predicate) |
Take while condition true (Java 9+) |
dropWhile(Predicate) |
Drop while condition true (Java 9+) |
Terminal Operations
Terminal Operations
Terminal operations trigger the processing of the stream pipeline and produce a result or side effect.
import java.util.*;
import java.util.stream.*;
public class TerminalOpsDemo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// forEach - perform action on each element
System.out.print("forEach: ");
numbers.stream().filter(n -> n % 2 == 0).forEach(n -> System.out.print(n + " "));
System.out.println(); // 2 4 6 8 10
// collect - accumulate into collection
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Evens: " + evens);
// reduce - combine elements
int sum = numbers.stream().reduce(0, Integer::sum);
System.out.println("Sum: " + sum); // 55
Optional<Integer> max = numbers.stream().reduce(Integer::max);
System.out.println("Max: " + max.orElse(0)); // 10
// count - count elements
long evenCount = numbers.stream().filter(n -> n % 2 == 0).count();
System.out.println("Even count: " + evenCount); // 5
// anyMatch - does any element match?
boolean hasOver10 = numbers.stream().anyMatch(n -> n > 10);
System.out.println("Any > 10: " + hasOver10); // false
// allMatch - do all elements match?
boolean allPositive = numbers.stream().allMatch(n -> n > 0);
System.out.println("All positive: " + allPositive); // true
// noneMatch - do no elements match?
boolean noneNegative = numbers.stream().noneMatch(n -> n < 0);
System.out.println("None negative: " + noneNegative); // true
// findFirst - find first element
Optional<Integer> first = numbers.stream().filter(n -> n > 5).findFirst();
System.out.println("First > 5: " + first.orElse(0)); // 6
// findAny - find any element (useful in parallel streams)
Optional<Integer> any = numbers.parallelStream().filter(n -> n > 5).findAny();
System.out.println("Any > 5: " + any.orElse(0));
// min and max
Optional<Integer> minVal = numbers.stream().min(Integer::compareTo);
Optional<Integer> maxVal = numbers.stream().max(Integer::compareTo);
System.out.println("Min: " + minVal.orElse(0)); // 1
System.out.println("Max: " + maxVal.orElse(0)); // 10
// toArray
Integer[] array = numbers.stream().toArray(Integer[]::new);
System.out.println("Array: " + Arrays.toString(array));
// forEachOrdered - maintains encounter order
System.out.print("forEachOrdered: ");
numbers.parallelStream().forEachOrdered(n -> System.out.print(n + " "));
System.out.println();
}
}
Terminal operations:
| Operation | Returns | Description |
|---|---|---|
forEach |
void | Perform action on each element |
collect |
R | Accumulate into collection |
reduce |
T or Optional |
Combine elements |
count |
long | Count elements |
anyMatch |
boolean | Any element matches? |
allMatch |
boolean | All elements match? |
noneMatch |
boolean | No elements match? |
findFirst |
Optional |
First element |
findAny |
Optional |
Any element |
min / max |
Optional |
Min/max element |
toArray |
T[] | Convert to array |
Collectors
Collectors
Collectors are pre-built reduction operations for the collect() terminal operation.
import java.util.*;
import java.util.stream.*;
public class CollectorsDemo {
public static void main(String[] args) {
List<String> names = Arrays.asList(
"Alice", "Bob", "Charlie", "David", "Eve", "Frank"
);
// toList / toSet / toCollection
List<String> list = names.stream().collect(Collectors.toList());
Set<String> set = names.stream().collect(Collectors.toSet());
TreeSet<String> treeSet = names.stream()
.collect(Collectors.toCollection(TreeSet::new));
// joining
String joined = names.stream().collect(Collectors.joining(", "));
System.out.println("Joined: " + joined); // Alice, Bob, Charlie, David, Eve, Frank
String joinedWithPrefix = names.stream()
.collect(Collectors.joining(", ", "[", "]"));
System.out.println("Bracketed: " + joinedWithPrefix); // [Alice, Bob, Charlie, David, Eve, Frank]
// counting
long count = names.stream().collect(Collectors.counting());
System.out.println("Count: " + count); // 6
// groupingBy
Map<Integer, List<String>> byLength = names.stream()
.collect(Collectors.groupingBy(String::length));
System.out.println("By length: " + byLength);
// {3=[Bob, Eve], 5=[Alice, David, Frank], 7=[Charlie]}
Map<Character, List<String>> byFirst = names.stream()
.collect(Collectors.groupingBy(name -> name.charAt(0)));
System.out.println("By first char: " + byFirst);
// groupingBy with downstream collector
Map<Integer, Long> countByLength = names.stream()
.collect(Collectors.groupingBy(String::length, Collectors.counting()));
System.out.println("Count by length: " + countByLength);
// {3=2, 5=3, 7=1}
Map<Integer, String> joinedByLength = names.stream()
.collect(Collectors.groupingBy(
String::length,
Collectors.joining(", ")
));
System.out.println("Joined by length: " + joinedByLength);
// partitioningBy
Map<Boolean, List<String>> partitioned = names.stream()
.collect(Collectors.partitioningBy(name -> name.length() > 4));
System.out.println("Partitioned: " + partitioned);
// {false=[Bob, Eve], true=[Alice, Charlie, David, Frank]}
// summarizingInt / summarizingDouble
IntSummaryStatistics stats = names.stream()
.collect(Collectors.summarizingInt(String::length));
System.out.println("Stats: " + stats);
// count=6, sum=34, min=3, average=5.67, max=7
// reducing
String reduced = names.stream()
.collect(Collectors.reducing("", (a, b) -> a + b + " "));
System.out.println("Reduced: " + reduced.trim());
// toMap
Map<String, Integer> nameLengths = names.stream()
.collect(Collectors.toMap(
name -> name,
String::length
));
System.out.println("Name lengths: " + nameLengths);
}
}
Key Collectors:
| Collector | Description |
|---|---|
toList() |
Collect to List |
toSet() |
Collect to Set |
joining(delimiter) |
Join strings |
counting() |
Count elements |
groupingBy(classifier) |
Group by classifier |
partitioningBy(predicate) |
Partition into true/false groups |
summarizingInt(extractor) |
Compute count, sum, min, max, avg |
toMap(keyMapper, valueMapper) |
Collect to Map |
Common Patterns
Common Stream Patterns
Frequently used stream patterns for common tasks.
import java.util.*;
import java.util.stream.*;
public class CommonPatternsDemo {
public static void main(String[] args) {
// Pattern 1: Filter and collect
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> longNames = names.stream()
.filter(n -> n.length() > 4)
.collect(Collectors.toList());
// Pattern 2: Transform and collect
List<String> upper = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// Pattern 3: Sum/Aggregate
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
int sum = nums.stream().mapToInt(Integer::intValue).sum();
double avg = nums.stream().mapToInt(Integer::intValue).average().orElse(0);
// Pattern 4: Find first match
Optional<String> first = names.stream()
.filter(n -> n.startsWith("C"))
.findFirst();
first.ifPresent(n -> System.out.println("Found: " + n));
// Pattern 5: Check if any/all match
boolean hasDavid = names.stream().anyMatch(n -> n.equals("David"));
boolean allStartWithA = names.stream().allMatch(n -> n.startsWith("A"));
// Pattern 6: Grouping
List<Employee> employees = Arrays.asList(
new Employee("Alice", "Engineering", 95000),
new Employee("Bob", "Marketing", 70000),
new Employee("Charlie", "Engineering", 110000),
new Employee("Diana", "Marketing", 80000)
);
Map<String, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(e -> e.department));
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(
e -> e.department,
Collectors.averagingDouble(e -> e.salary)
));
// Pattern 7: FlatMap - flatten nested
List<List<Integer>> nested = Arrays.asList(
Arrays.asList(1, 2), Arrays.asList(3, 4, 5), Arrays.asList(6)
);
List<Integer> flat = nested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
// Pattern 8: Chained operations
String result = employees.stream()
.filter(e -> e.salary > 80000)
.sorted(Comparator.comparingDouble((Employee e) -> e.salary).reversed())
.map(e -> e.name)
.collect(Collectors.joining(", "));
System.out.println("High earners: " + result); // Charlie, Alice
// Pattern 9: Collect to Map
Map<String, Double> salaryMap = employees.stream()
.collect(Collectors.toMap(e -> e.name, e -> e.salary));
// Pattern 10: Parallel stream for large datasets
long count = IntStream.range(0, 1000000)
.parallel()
.filter(n -> n % 2 == 0)
.count();
System.out.println("Even numbers: " + count); // 500000
}
static class Employee {
String name;
String department;
double salary;
Employee(String name, String dept, double salary) {
this.name = name; this.department = dept; this.salary = salary;
}
}
}
Key patterns:
- Filter + Collect:
stream().filter(pred).collect(toList()) - Map + Collect:
stream().map(func).collect(toList()) - Group:
stream().collect(groupingBy(classifier)) - Join:
stream().collect(joining(", ")) - Find:
stream().filter(pred).findFirst() - Count:
stream().filter(pred).count()
Practice Problems
Given a paragraph of text, use streams to find the 3 most frequent words (case-insensitive). Return a map of word to count.
Solution
import java.util.*;
import java.util.stream.*;
public class WordFrequency {
public static Map<String, Long> topWords(String text, int n) {
return Arrays.stream(text.toLowerCase().split("\\\W+"))
.filter(w -> !w.isEmpty())
.collect(Collectors.groupingBy(w -> w, Collectors.counting()))
.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(n)
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(a, b) -> a,
LinkedHashMap::new
));
}
}Given a list of integers, use streams to: remove duplicates, filter only even numbers, square them, sort them, and return as a list.
Solution
import java.util.*;
import java.util.stream.*;
public class StreamPipeline {
public static List<Integer> process(List<Integer> numbers) {
return numbers.stream()
.distinct()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.sorted()
.collect(Collectors.toList());
}
}Given a list of Product objects (name, category, price), use streams to group by category and calculate the total price per category.
Solution
import java.util.*;
import java.util.stream.*;
public class ProductAnalytics {
static class Product {
String name;
String category;
double price;
Product(String name, String category, double price) {
this.name = name; this.category = category; this.price = price;
}
}
public static Map<String, Double> totalPriceByCategory(List<Product> products) {
return products.stream()
.collect(Collectors.groupingBy(
p -> p.category,
Collectors.summingDouble(p -> p.price)
));
}
}Quiz
1. What is the difference between intermediate and terminal operations?
2. What does Collectors.groupingBy() return?
3. What does flatMap do that map does not?
4. Which collector joins strings with a delimiter?
Flashcards
Question
What are the 3 types of stream operations?
Click to reveal answer
Answer
1) Creating (stream(), of(), generate()) 2) Intermediate (filter, map, sorted, distinct, flatMap) 3) Terminal (collect, forEach, reduce, count, anyMatch). Intermediate are lazy, terminal trigger processing.
Question
How do you convert a Stream to a List?
Click to reveal answer
Answer
Use collect(Collectors.toList()). This is a terminal operation that accumulates stream elements into a List. For unmodifiable list: Collectors.toUnmodifiableList().
Question
What is the difference between map and flatMap?
Click to reveal answer
Answer
map transforms each element to one value (1-to-1). flatMap transforms each element to a stream and flattens all streams (1-to-many). flatMap is used for nested structures.
Question
When should you use parallel streams?
Click to reveal answer
Answer
For large datasets with CPU-intensive operations where ordering doesn't matter. Avoid for small datasets (overhead) or I/O-bound operations. Use parallelStream() or .parallel().
Question
What is Java Streams?
Click to reveal answer
Answer
Java Streams is a key concept in Java programming.
Revision Notes
Key Takeaways
- 1.Streams are lazy — intermediate ops don't execute until a terminal op
- 2.filter selects, map transforms, flatMap flattens
- 3.Collectors.groupingBy groups, joining concatenates, counting counts
- 4.Use parallel streams for large datasets with CPU-intensive work
Interview Tips
- •Explain the difference between intermediate and terminal operations
- •Demonstrate common stream pipelines: filter+map+collect
- •Discuss when to use parallel streams vs sequential
- •Know the key Collectors: toList, groupingBy, joining, counting
Cheat Sheet
Java Streams Cheat Sheet
Creating
- collection.stream()
- Arrays.stream(array)
- Stream.of(values...)
- Stream.generate(supplier)
- Stream.iterate(seed, func)
- IntStream.range(start, end)
Intermediate (Lazy)
- filter(Predicate)
- map(Function)
- flatMap(Function)
- sorted() / sorted(Comparator)
- distinct()
- limit(n) / skip(n)
- peek(Consumer)
Terminal
- collect(Collector)
- forEach(Consumer)
- reduce(identity, accumulator)
- count()
- anyMatch / allMatch / noneMatch
- findFirst / findAny
- min / max
Collectors
- toList() / toSet()
- joining(delimiter)
- groupingBy(classifier)
- partitioningBy(predicate)
- summingDouble(extractor)
- counting()