Stream API Coding Problems¶
Modern Java functional programming with Stream API - filter, map, reduce, and complex stream operations.
1. Convert a list of strings to uppercase and sort them¶
View Solution
Problem
Given a list of words, convert all to uppercase and return sorted. Handle case-insensitive sorting.
Approach 1: Stream API (Recommended)
List<String> words = Arrays.asList("banana", "apple", "Cherry", "date");
// Convert to uppercase, sort, and collect
List<String> result = words.stream()
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
System.out.println(result); // [APPLE, BANANA, CHERRY, DATE]
Approach 2: Case-Insensitive Sort
List<String> result = words.stream()
.map(String::toUpperCase)
.sorted(String::compareToIgnoreCase) // Case-insensitive
.collect(Collectors.toList());
// Or with explicit comparator
List<String> result = words.stream()
.map(String::toUpperCase)
.sorted(Comparator.comparing(String::toString, String.CASE_INSENSITIVE_ORDER))
.collect(Collectors.toList());
Approach 3: With Duplicates Removal
List<String> result = words.stream()
.map(String::toUpperCase)
.distinct() // Remove duplicates
.sorted()
.collect(Collectors.toList());
Complexity Comparison
| Approach | Time | Space | Use Case |
|---|---|---|---|
| Stream map + sort | O(n log n) | O(n) | Simple, clean code |
| Stream + distinct + sort | O(n log n) | O(n) | Remove duplicates first |
| Traditional loop | O(n log n) | O(n) | When stream unavailable |
2. Filter a list of integers and collect even numbers greater than 10¶
View Solution
Problem
Given a list of integers, filter for even numbers greater than 10, and return as a list.
Approach 1: Stream Filter (Recommended)
List<Integer> numbers = Arrays.asList(5, 12, 8, 15, 20, 3, 18, 7, 25);
// Filter even numbers > 10
List<Integer> result = numbers.stream()
.filter(n -> n > 10) // Greater than 10
.filter(n -> n % 2 == 0) // Even numbers
.collect(Collectors.toList());
System.out.println(result); // [12, 20, 18]
Approach 2: Combined Filter Condition
List<Integer> result = numbers.stream()
.filter(n -> n > 10 && n % 2 == 0) // Single filter
.collect(Collectors.toList());
Approach 3: Filter to Set (Remove Duplicates)
Set<Integer> result = numbers.stream()
.filter(n -> n > 10 && n % 2 == 0)
.collect(Collectors.toSet());
Approach 4: Sorted and Limited
List<Integer> result = numbers.stream()
.filter(n -> n > 10 && n % 2 == 0)
.sorted(Comparator.reverseOrder()) // Descending
.limit(2) // Top 2
.collect(Collectors.toList());
// Result: [20, 18]
Complexity Comparison
| Approach | Time | Space | Best For |
|---|---|---|---|
| Two filters | O(n) | O(k) | Readability |
| Combined filter | O(n) | O(k) | Performance |
| Filter to Set | O(n) | O(k) | Uniqueness |
| Filter + sort + limit | O(n log n) | O(k) | Top-K results |
3. Use Stream to count occurrences of each string in a list¶
View Solution
Problem
Given a list of words, count how many times each word appears and return a map of wordβcount.
Approach 1: groupingBy with Counting (Recommended)
List<String> words = Arrays.asList("apple", "banana", "apple", "cherry", "banana", "apple");
// Group by word and count
Map<String, Long> wordCount = words.stream()
.collect(Collectors.groupingBy(
w -> w, // Group by word itself
Collectors.counting() // Count occurrences
));
System.out.println(wordCount);
// {apple=3, banana=2, cherry=1}
Approach 2: Case-Insensitive Counting
Map<String, Long> wordCount = words.stream()
.map(String::toLowerCase) // Normalize
.collect(Collectors.groupingBy(
w -> w,
Collectors.counting()
));
// "Apple" and "apple" counted together
Approach 3: Sort by Count (Descending)
Map<String, Long> wordCount = words.stream()
.collect(Collectors.groupingBy(
w -> w,
Collectors.counting()
));
// Sort by count descending
List<Map.Entry<String, Long>> sorted = wordCount.entrySet()
.stream()
.sorted((e1, e2) -> Long.compare(e2.getValue(), e1.getValue()))
.collect(Collectors.toList());
// or using comparingByValue
List<Map.Entry<String, Long>> sorted = wordCount.entrySet()
.stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.collect(Collectors.toList());
sorted.forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));
// apple: 3
// banana: 2
// cherry: 1
Approach 4: Traditional HashMap (for comparison)
Map<String, Integer> wordCount = new HashMap<>();
for (String word : words) {
wordCount.put(word, wordCount.getOrDefault(word, 0) + 1);
}
Complexity Comparison
| Approach | Time | Space | Best For |
|---|---|---|---|
| groupingBy().counting() | O(n) | O(k) | Clean, functional |
| HashMap loop | O(n) | O(k) | Performance critical |
| groupingBy + sort | O(n + k log k) | O(k) | Need sorted results |
4. Use Stream reduce() to calculate sum, product, and concatenation¶
View Solution
Problem
Use Stream.reduce() to compute sum, product, and string concatenation of collection elements.
Approach 1: Sum with reduce()
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Sum using reduce
int sum = numbers.stream()
.reduce(0, Integer::sum); // 0 is identity/initial value
System.out.println(sum); // 15
// Or equivalent
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
Approach 2: Product with reduce()
int product = numbers.stream()
.reduce(1, (a, b) -> a * b); // 1 is identity for multiplication
System.out.println(product); // 120 (1*2*3*4*5)
Approach 3: String Concatenation
List<String> words = Arrays.asList("Hello", " ", "World");
// Concatenate strings
String result = words.stream()
.reduce("", String::concat); // "" is identity for strings
System.out.println(result); // "Hello World"
// Or with custom separator
String result = words.stream()
.reduce((a, b) -> a + "-" + b)
.orElse(""); // Handle empty stream
System.out.println(result); // "Hello- -World"
Approach 4: Optional reduce (no identity)
Optional<Integer> sum = numbers.stream()
.reduce((a, b) -> a + b); // No identity value
if (sum.isPresent()) {
System.out.println(sum.get()); // 15
} else {
System.out.println("Empty stream");
}
// Shorthand
numbers.stream()
.reduce((a, b) -> a + b)
.ifPresent(System.out::println);
Approach 5: Complex Reduction - Find Max
Optional<Integer> max = numbers.stream()
.reduce((a, b) -> a > b ? a : b);
System.out.println(max.get()); // 5
// Equivalent but clearer
Optional<Integer> max = numbers.stream()
.max(Integer::compare);
Approach 6: Collect vs Reduce
// DON'T: Inefficient string concat with reduce
String result = words.stream()
.reduce("", (a, b) -> a + b); // Creates many intermediate strings β
// DO: Use Collectors.joining (optimized)
String result = words.stream()
.collect(Collectors.joining("")); // β
Uses StringBuilder internally
reduce() Summary Table
| Function | Identity | Example | Result |
|---|---|---|---|
| Sum | 0 | reduce(0, Integer::sum) | 15 |
| Product | 1 | reduce(1, (a,b) β a*b) | 120 |
| Max | N/A | reduce((a,b) β max(a,b)) | 5 |
| Concat | "" | reduce("", String::concat) | "12345" |
5. Combine multiple Stream operations: filter, map, and collect¶
View Solution
Problem
Given a list of employees with salary, filter by salary > 50k, extract names, and collect as comma-separated string.
Approach 1: Chained Stream Operations (Recommended)
class Employee {
String name;
double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public double getSalary() { return salary; }
}
List<Employee> employees = Arrays.asList(
new Employee("Alice", 75000),
new Employee("Bob", 45000),
new Employee("Charlie", 95000),
new Employee("Diana", 55000),
new Employee("Eve", 40000)
);
// Filter salary > 50k, map to names, collect as CSV
String result = employees.stream()
.filter(e -> e.getSalary() > 50000) // Filter
.map(Employee::getName) // Transform
.collect(Collectors.joining(", ")); // Collect as string
System.out.println(result); // "Alice, Charlie, Diana"
Approach 2: Collect to List First
List<String> highEarners = employees.stream()
.filter(e -> e.getSalary() > 50000)
.map(Employee::getName)
.collect(Collectors.toList());
System.out.println(highEarners); // [Alice, Charlie, Diana]
Approach 3: Multiple Filters
String result = employees.stream()
.filter(e -> e.getSalary() > 50000) // Salary filter
.filter(e -> e.getName().length() > 3) // Name length filter
.map(Employee::getName)
.collect(Collectors.joining(", "));
// Result: "Alice, Charlie, Diana"
Approach 4: Map to Different Object
class EmployeeDTO {
String name;
double salary;
public EmployeeDTO(String name, double salary) {
this.name = name;
this.salary = salary;
}
}
List<EmployeeDTO> dtos = employees.stream()
.filter(e -> e.getSalary() > 50000)
.map(e -> new EmployeeDTO(e.getName(), e.getSalary()))
.collect(Collectors.toList());
Approach 5: Sorted Collection
String result = employees.stream()
.filter(e -> e.getSalary() > 50000)
.sorted((e1, e2) -> Double.compare(e2.getSalary(), e1.getSalary())) // Descending
.map(Employee::getName)
.collect(Collectors.joining(", "));
// Result: "Charlie, Alice, Diana" (sorted by salary desc)
Approach 6: Group and Collect
Map<Boolean, List<Employee>> grouped = employees.stream()
.collect(Collectors.partitioningBy(e -> e.getSalary() > 50000));
List<Employee> highEarners = grouped.get(true);
List<Employee> lowEarners = grouped.get(false);
System.out.println("High: " + highEarners.size()); // 3
System.out.println("Low: " + lowEarners.size()); // 2
Stream Pipeline Performance
| Operation | Type | Lazy? | Impact |
|---|---|---|---|
| filter() | Intermediate | β Yes | Processes only needed elements |
| map() | Intermediate | β Yes | Transforms elements |
| sorted() | Intermediate | β No | Must consume entire stream |
| distinct() | Intermediate | β No | Requires buffering |
| collect() | Terminal | N/A | Triggers execution |
See also: Collections Framework, Interview Questions Overview