| Stream Creation | Collection.stream(), Stream.of(), Arrays.stream(), Stream.generate(), Stream.iterate() |
| Intermediate Ops | filter, map, flatMap, distinct, sorted, peek, limit, skip, mapToInt/Long/Double |
| Terminal Ops | collect, forEach, reduce, count, findFirst, findAny, anyMatch, allMatch, noneMatch, min, max, toArray |
| Lazy Evaluation | Intermediate ops execute only when terminal op is invoked |
| Short-circuit | limit, findFirst, findAny, anyMatch, allMatch, noneMatch stop early |
| Parallel Stream | collection.parallelStream() or stream().parallel() — uses ForkJoinPool |
| Collectors | toList, toSet, toMap, groupingBy, partitioningBy, joining, counting, summarizing |
| One-shot | A stream can only be consumed once — calling terminal op twice throws IllegalStateException |
Collection / Array / I/O
filter/map/flatMap
sorted/distinct/peek
limit/skip
mapToInt
collect/reduce
forEach/count
Java Stream API Interview Questions & Answers
Q1. What is the Java Stream API?
A: Introduced in Java 8, Stream API provides a functional approach to processing sequences of elements. Streams support lazy evaluation, pipeline operations, and optional parallel execution. A stream is NOT a data structure — it carries values from a source, processes them through a pipeline, and produces a result without modifying the source.
Q2. How do you create a Stream?
A: Multiple creation methods:
List<String> list = List.of("a","b","c");
Stream<String> s1 = list.stream(); // from collection
Stream<String> s2 = Stream.of("x","y","z"); // varargs
Stream<String> s3 = Arrays.stream(arr); // from array
Stream<String> s4 = Stream.generate(() -> "a"); // infinite, same value
Stream<Integer> s5 = Stream.iterate(0, n -> n+2); // infinite, 0,2,4,6...
Stream<Integer> s6 = Stream.iterate(0, n -> n < 100, n -> n+2); // Java 9, finite
Stream<String> s7 = Files.lines(Path.of("file.txt")); // from file
Q3. What is the difference between intermediate and terminal operations?
A: Intermediate operations return a new Stream and are lazy — they don't execute until a terminal operation is invoked. Terminal operations produce a result or side effect and consume the stream. After a terminal operation, the stream is exhausted and cannot be reused.
| Intermediate | Terminal |
|---|---|
| filter, map, flatMap | collect, forEach, reduce |
| sorted, distinct, peek | count, min, max, sum |
| limit, skip, mapToInt | findFirst, findAny, anyMatch |
Q4. What is lazy evaluation in Stream?
A: Stream intermediate operations are lazy — they don't process any element until a terminal operation is invoked. This enables short-circuiting: if findFirst() is the terminal op, the stream stops processing after finding the first match, without applying operations to all remaining elements.
Stream.of("a","b","c","d")
.filter(s -> { System.out.println("filter: "+s); return !s.equals("b"); })
.map(s -> { System.out.println("map: "+s); return s.toUpperCase(); })
.findFirst();
// Only processes until first match found — prints filter:a, map:a
// b is filtered out (not mapped), c and d never processed
Q5. What is the difference between map() and flatMap()?
A: map(Function<T,R>) transforms each element T to R — one-to-one mapping. flatMap(Function<T, Stream<R>>) transforms each T to a Stream<R> and then flattens all those streams into one — one-to-many mapping (or zero). Use flatMap when each element maps to multiple results.
// map: each sentence to its length
Stream.of("hello world", "java streams").map(String::length); // 11, 12
// flatMap: each sentence to its words (multiple)
Stream.of("hello world", "java streams")
.flatMap(s -> Arrays.stream(s.split(" ")));
// "hello", "world", "java", "streams"
Q6. What is the difference between findFirst() and findAny()?
A: findFirst() returns the first element of the stream (in encounter order) — deterministic. findAny() returns any element — non-deterministic in parallel streams, may return any element. In sequential streams, findAny() behaves like findFirst(). Use findAny() in parallel streams when you don't need the first specifically — may be faster.
Q7. What is the difference between anyMatch, allMatch, and noneMatch?
A: All accept Predicate and return boolean. anyMatch: true if at least one element matches (short-circuits on first match). allMatch: true if ALL elements match (short-circuits on first non-match). noneMatch: true if NO elements match (short-circuits on first match).
List<Integer> nums = List.of(2, 4, 6, 8);
nums.stream().anyMatch(n -> n > 5); // true (6,8)
nums.stream().allMatch(n -> n % 2 == 0); // true (all even)
nums.stream().noneMatch(n -> n < 0); // true (none negative)
Q8. How does the reduce() operation work?
A: reduce combines elements into a single result using an associative function. Three forms: 1) reduce(identity, BinaryOperator) — always returns T. 2) reduce(BinaryOperator) — returns Optional<T> (empty stream returns empty). 3) reduce(identity, BiFunction<U,T,U>, BinaryOperator<U>) — for parallel with type transformation.
// Sum with identity 0
int sum = Stream.of(1,2,3,4).reduce(0, Integer::sum); // 10
// Max without identity (Optional)
Optional<Integer> max = Stream.of(3,1,4,1,5).reduce(Integer::max); // Optional[5]
// String concatenation
String s = Stream.of("a","b","c").reduce("", String::concat); // "abc"
Q9. What is collect() and how do you use Collectors?
A: collect(Collector) is a mutable reduction — collects elements into a container like List, Set, Map, or String. Collectors class provides common implementations.
// To list, set
List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());
// To map (key: name, value: age)
Map<String,Integer> map = stream.collect(Collectors.toMap(Person::getName, Person::getAge));
// Joining strings
String joined = stream.collect(Collectors.joining(", ", "[", "]"));
// Grouping
Map<String, List<Person>> byDept = stream.collect(Collectors.groupingBy(Person::getDept));
// Counting
Map<String, Long> deptCount = stream.collect(Collectors.groupingBy(Person::getDept, Collectors.counting()));
Q10. What is the difference between Collectors.toList() and Stream.toList() (Java 16+)?
A: Collectors.toList() returns a mutable ArrayList. Stream.toList() (Java 16) returns an unmodifiable List. Collectors.toUnmodifiableList() also returns an unmodifiable List (Java 10). For modern code, prefer Stream.toList() for read-only results.
List<String> mutable = stream.collect(Collectors.toList()); // modifiable
List<String> immutable = stream.toList(); // Java 16, unmodifiable
List<String> immutable2 = stream.collect(Collectors.toUnmodifiableList()); // Java 10
Q11. What is Collectors.groupingBy() and how does it work?
A: groupingBy(classifier) groups stream elements by a classifier function, returning Map<K, List<V>>. With downstream: groupingBy(classifier, downstream) applies another Collector to each group. groupingByConcurrent() is the parallel-safe version.
// Simple grouping
Map<String, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept));
// Grouping with downstream counting
Map<String, Long> countByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept, Collectors.counting()));
// Grouping with downstream mapping
Map<String, List<String>> namesByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept,
Collectors.mapping(Employee::getName, Collectors.toList())));
Q12. What is Collectors.partitioningBy()?
A: partitioningBy(Predicate) divides elements into two groups: true and false, returning Map<Boolean, List<T>>. More efficient than groupingBy with boolean classifier because it always creates exactly two partitions.
Map<Boolean, List<Integer>> evenOdd = Stream.of(1,2,3,4,5,6)
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// {true=[2,4,6], false=[1,3,5]}
Q13. What is the difference between sequential and parallel streams?
A: Sequential streams process elements one at a time in the current thread. Parallel streams split the source, process chunks in parallel using ForkJoinPool.commonPool(), then combine results. Parallel streams can be faster for CPU-bound operations on large data but introduce overhead for small datasets, and require thread-safe, stateless operations.
Q14. When should you use parallel streams?
A: Use parallel streams when: data is large (thousands+ elements), operations are CPU-intensive and stateless, the source supports efficient splitting (ArrayList, array; not LinkedList), ordering is not required. Avoid for: small datasets, I/O-bound operations, stateful operations, shared mutable state.
Q15. What is the difference between forEach() and forEachOrdered()?
A: forEach(Consumer) processes elements, order not guaranteed in parallel streams. forEachOrdered(Consumer) processes elements in encounter order, even in parallel streams — slower but deterministic. For sequential streams, both are equivalent.
Q16. What is a stateful vs stateless intermediate operation?
A: Stateless: filter, map, flatMap, peek — don't need information from other elements. Stateful: distinct, sorted, limit, skip — need to see multiple/all elements before producing output. Stateful operations hurt parallel performance and require buffering.
Q17. What is the short-circuit behavior of limit() and skip()?
A: limit(n) stops processing after n elements are collected — short-circuits with infinite streams. skip(n) discards the first n elements. Both are stateful. Together they enable pagination: .skip(page * size).limit(size).
// Infinite stream, take first 10 even numbers:
Stream.iterate(0, n -> n + 1)
.filter(n -> n % 2 == 0)
.limit(10)
.collect(Collectors.toList()); // [0,2,4,6,8,10,12,14,16,18]
// Pagination: page 2, size 5:
list.stream().skip(5).limit(5).collect(Collectors.toList());
Q18. What is the difference between distinct() and sorted()?
A: distinct() removes duplicate elements (uses equals/hashCode) — stateful, requires buffering seen elements. sorted() sorts elements (uses natural order or Comparator) — stateful, must buffer ALL elements before passing any downstream. Both have O(n) memory impact for sequential streams.
Q19. How do you convert a Stream to an IntStream/LongStream/DoubleStream?
A: Use mapToInt(), mapToLong(), mapToDouble() which accept the respective ToXxxFunction. The resulting primitive stream avoids boxing and provides sum(), average(), min(), max(), summaryStatistics() directly.
IntStream ages = employees.stream().mapToInt(Employee::getAge);
int totalAge = ages.sum(); // no boxing
OptionalDouble avg = employees.stream().mapToDouble(Employee::getSalary).average();
IntSummaryStatistics stats = intStream.summaryStatistics(); // count+sum+min+max+average
Q20. What is IntStream.range() and rangeClosed()?
A: IntStream.range(start, end) generates integers from start (inclusive) to end (exclusive). rangeClosed(start, end) includes end. Equivalent to for(int i=start; i<end; i++) but as a stream.
IntStream.range(0, 5).forEach(System.out::println); // 0,1,2,3,4
IntStream.rangeClosed(1, 5).sum(); // 15 (1+2+3+4+5)
// Generate indices for list:
IntStream.range(0, list.size()).mapToObj(i -> list.get(i) + " at " + i);
Q21. What is Collectors.joining() and its overloads?
A: joining() concatenates string stream elements. joining(delimiter) adds delimiter between elements. joining(delimiter, prefix, suffix) wraps the result. Uses StringBuilder internally for efficiency.
Stream.of("Java","Spring","Kafka").collect(Collectors.joining()); // "JavaSpringKafka"
Stream.of("Java","Spring","Kafka").collect(Collectors.joining(", ")); // "Java, Spring, Kafka"
Stream.of("Java","Spring","Kafka").collect(Collectors.joining(", ","[","]")); // "[Java, Spring, Kafka]"
Q22. What is Collectors.counting()?
A: counting() is a downstream Collector that counts elements in each group. Equivalent to Collectors.summingInt(e -> 1). Commonly used as downstream in groupingBy.
Map<String, Long> wordFreq = words.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
Q23. What is Collectors.summarizingInt/Long/Double()?
A: Returns a Collector that produces IntSummaryStatistics/LongSummaryStatistics/DoubleSummaryStatistics, providing count, sum, min, max, and average in one pass.
IntSummaryStatistics stats = employees.stream()
.collect(Collectors.summarizingInt(Employee::getAge));
System.out.println("Count: " + stats.getCount());
System.out.println("Average: " + stats.getAverage());
System.out.println("Max: " + stats.getMax());
Q24. What is Collectors.toMap() and common pitfalls?
A: toMap(keyMapper, valueMapper) creates a Map. Pitfalls: 1) Duplicate keys throw IllegalStateException — use toMap(k, v, mergeFunction) to handle. 2) Null values throw NullPointerException in the default Map — use toMap with mergeFunction and explicit map supplier. 3) No ordering guarantee with default HashMap — use toMap(k, v, merge, LinkedHashMap::new) for insertion order.
// Handle duplicate keys (keep last):
Map<String, Employee> byName = employees.stream()
.collect(Collectors.toMap(Employee::getName, e -> e, (e1, e2) -> e2));
// Ordered map:
Map<String, Employee> ordered = employees.stream()
.collect(Collectors.toMap(Employee::getName, e -> e, (e1,e2) -> e1, LinkedHashMap::new));
Q25. What is Collectors.mapping()?
A: mapping(mapper, downstream) applies mapper to each element before passing to the downstream Collector. Used as a downstream in groupingBy to transform grouped values.
// Group by dept, collect only names:
Map<String, List<String>> deptNames = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDept,
Collectors.mapping(Employee::getName, Collectors.toList())
));
Q26. What is Collectors.teeing() introduced in Java 12?
A: teeing(downstream1, downstream2, merger) collects with two collectors simultaneously and merges their results. Useful when you need two different aggregations in one pass.
// Get sum and count in one pass:
record SumCount(long sum, long count) {}
SumCount result = IntStream.rangeClosed(1, 10).boxed()
.collect(Collectors.teeing(
Collectors.summingLong(Integer::longValue),
Collectors.counting(),
SumCount::new
));
Q27. What is Stream.takeWhile() and dropWhile() from Java 9?
A: takeWhile(predicate): takes elements while predicate is true, stops at first false (ordered streams). dropWhile(predicate): drops elements while predicate is true, passes the rest. Different from filter — they work on ordered prefix/suffix, not scattered matching.
List.of(1,2,3,4,5,1,2).stream()
.takeWhile(n -> n < 4)
.collect(toList()); // [1, 2, 3] — stops at 4, ignores 5,1,2
List.of(1,2,3,4,5).stream()
.dropWhile(n -> n < 4)
.collect(toList()); // [4, 5]
Q28. What is Stream.iterate() with predicate from Java 9?
A: Java 9 adds Stream.iterate(seed, hasNext, next) — generates a finite stream. hasNext is a Predicate; when it returns false, the stream ends. Equivalent to a traditional for loop as a stream.
// Java 8: infinite iterate + limit
Stream.iterate(0, n -> n + 2).limit(10).collect(toList());
// Java 9: bounded iterate with predicate
Stream.iterate(0, n -> n < 20, n -> n + 2).collect(toList()); // 0,2,4,...,18
Q29. What is Stream.ofNullable() from Java 9?
A: Stream.ofNullable(value) returns a stream of one element if value is non-null, or an empty stream if null. Replaces the pattern: value != null ? Stream.of(value) : Stream.empty().
String value = getValue(); // might return null
Stream.ofNullable(value).map(String::toUpperCase).findFirst(); // safe
Q30. Can a stream be reused?
A: No. A stream can only be consumed once. Calling a terminal operation or a second terminal operation on a consumed stream throws IllegalStateException. To reuse, create a new stream from the source or store intermediate results in a collection.
Stream<String> s = list.stream();
long count = s.count(); // terminal — stream consumed
// s.findFirst(); // IllegalStateException!
// Solution: use a Supplier:
Supplier<Stream<String>> ss = list::stream;
long count2 = ss.get().count();
String first = ss.get().findFirst().orElse("");
Q31. How does Stream.peek() work and when to use it?
A: peek(Consumer) is an intermediate operation that applies a Consumer to each element without changing the stream. Used primarily for debugging. Because of lazy evaluation, peek only fires when elements are pulled by a terminal operation. Don't use peek for side effects in production code.
Q32. What is the difference between Stream.count() and Collectors.counting()?
A: Stream.count() is a terminal operation on a stream, returning long. Collectors.counting() is a Collector used as a downstream in groupingBy, returning Long in the resulting map. Functionally: count() on the whole stream vs counting() per group.
Q33. How do you find the maximum element in a stream?
A: Use max(Comparator) or IntStream.max(). Returns Optional since stream may be empty.
Optional<Employee> highest = employees.stream()
.max(Comparator.comparingDouble(Employee::getSalary));
OptionalInt max = IntStream.of(3,1,4,1,5,9).max(); // 9
// With reduction:
Optional<Integer> max2 = Stream.of(3,1,4).reduce(Integer::max);
Q34. How do you flatten a list of lists using streams?
A: Use flatMap to convert each inner list to a stream, which gets flattened into one stream.
List<List<Integer>> nested = List.of(List.of(1,2), List.of(3,4), List.of(5));
List<Integer> flat = nested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList()); // [1,2,3,4,5]
Q35. What is the encounter order of a stream?
A: Encounter order is the order in which elements appear in the source. ArrayList, arrays, and I/O streams have defined encounter order. HashSet and HashMap do NOT. Ordered operations like findFirst(), limit(), sorted() respect encounter order. Unordered hint (stream.unordered()) allows parallel optimizations.
Q36. How do you collect to a specific Map implementation (e.g., TreeMap)?
A: Use toMap with a Map supplier as the 4th argument, or groupingBy with a Map supplier as the 2nd argument.
TreeMap<String, Employee> sorted = employees.stream()
.collect(Collectors.toMap(
Employee::getName, e -> e, (e1,e2) -> e1, TreeMap::new));
TreeMap<String, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept, TreeMap::new, Collectors.toList()));
Q37. What is the difference between Collectors.toSet() and using distinct() + collect(toList())?
A: toSet() produces a HashSet (unordered, no duplicates). distinct().collect(toList()) produces a List with no duplicates but preserving encounter order (ordered distinct). distinct() uses hashCode/equals. For order-preserving deduplication, use distinct().collect(toList()). For just uniqueness, toSet() is simpler.
Q38. How do you perform a parallel reduce correctly?
A: For parallel reduce: 1) identity must be an identity for the combiner. 2) The accumulator must be associative. 3) For parallel reduce with type change, the combiner must be able to combine two U results. String concatenation with "" identity is NOT suitable for parallel (order-sensitive) — use joining() Collector instead.
// Correct parallel reduce (associative, identity=0):
int sum = list.parallelStream().reduce(0, Integer::sum);
// Wrong: string concat is order-sensitive in parallel
// Use joining() Collector instead:
String joined = list.parallelStream().collect(Collectors.joining(",")); // correct
Q39. What is the Optional.stream() method from Java 9?
A: Optional.stream() returns a Stream of one element if present, or empty stream if absent. Enables Optional to participate in Stream pipelines cleanly, especially useful for flatMap.
List<Optional<String>> optionals = List.of(Optional.of("a"), Optional.empty(), Optional.of("b"));
// Java 9: flatten optionals to non-null values:
List<String> values = optionals.stream()
.flatMap(Optional::stream)
.collect(Collectors.toList()); // ["a", "b"]
Q40. What is the ForkJoinPool used by parallel streams?
A: Parallel streams use ForkJoinPool.commonPool() by default. The parallelism level equals Runtime.getRuntime().availableProcessors() - 1 (leaves one for main thread). To use a custom pool: submit stream work as a task to a custom ForkJoinPool.
ForkJoinPool customPool = new ForkJoinPool(4);
List<String> result = customPool.submit(
() -> list.parallelStream().filter(s -> s.length() > 3).collect(toList())
).get();
Q41. How do you count elements by group efficiently?
// Frequency map:
Map<String, Long> freq = words.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
// Top 5 words by frequency:
freq.entrySet().stream()
.sorted(Map.Entry.<String,Long>comparingByValue().reversed())
.limit(5)
.forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));
Q42. What is the difference between Stream.of(null) and Stream.ofNullable(null)?
A: Stream.of(null) creates a stream containing one null element — will cause NullPointerException when operations try to process null. Stream.ofNullable(null) creates an empty stream — safe. Always prefer ofNullable when the value might be null.
Q43. How does sorted() work without a comparator?
A: sorted() uses the natural ordering — elements must implement Comparable. Throws ClassCastException at runtime if elements don't implement Comparable. For custom ordering, use sorted(Comparator).
Stream.of(3,1,4,1,5).sorted().collect(toList()); // [1,1,3,4,5]
Stream.of("banana","apple","cherry").sorted().collect(toList()); // alphabetical
Q44. What is Collectors.collectingAndThen()?
A: collectingAndThen(downstream, finisher) applies a downstream Collector and then applies a finisher Function to the result. Useful for wrapping the result (e.g., making it unmodifiable) or transforming the aggregated value.
// Collect to unmodifiable list:
List<String> immutable = stream.collect(
Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
// Collect then get max:
Optional<String> max = stream.collect(
Collectors.collectingAndThen(Collectors.toList(),
list -> list.stream().max(Comparator.naturalOrder())));
Q45. How do you convert a Map to a Stream?
Map<String,Integer> map = Map.of("a",1,"b",2,"c",3);
// Stream of entries:
map.entrySet().stream().filter(e -> e.getValue() > 1)
.forEach(e -> System.out.println(e.getKey()+"="+e.getValue()));
// Stream of keys:
map.keySet().stream().sorted().collect(toList());
// Stream of values:
map.values().stream().mapToInt(Integer::intValue).sum();
Q46. What is the difference between Stream and Iterator?
A: Iterator: external iteration (caller controls loop), sequential, pull-based, no pipeline. Stream: internal iteration (stream controls traversal), supports lazy evaluation, pipelines, parallel execution, and rich terminal operations. Streams are higher-level than iterators.
Q47. How do you handle empty streams in reduce and collect?
A: reduce(BinaryOperator) returns Optional.empty() for empty streams. reduce(identity, BinaryOperator) returns identity for empty streams. collect() returns an empty container (empty List, empty Map, etc.) for empty streams — no special handling needed.
Q48. What is the performance impact of boxing in streams?
A: Using Stream<Integer> instead of IntStream boxes every int to Integer object. For large numerical datasets, boxing has significant overhead: memory allocation per element, GC pressure, slower arithmetic. Always use IntStream/LongStream/DoubleStream for primitive numeric operations.
Q49. How do you generate pairs or indexes with streams?
// Index-element pairs:
IntStream.range(0, list.size())
.mapToObj(i -> Map.entry(i, list.get(i)))
.forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));
// Zip two lists:
IntStream.range(0, Math.min(list1.size(), list2.size()))
.mapToObj(i -> list1.get(i) + ", " + list2.get(i))
.collect(toList());
Q50. How do you use Collectors.toUnmodifiableMap()?
A: Java 10: Collectors.toUnmodifiableMap(keyMapper, valueMapper) returns an unmodifiable Map. Throws NullPointerException for null keys or values. Use when the resulting map should not be mutated.
Q51. What is the difference between Stream.generate() and Stream.iterate()?
A: generate(Supplier) calls the supplier for each element — suitable for random values or constants. iterate(seed, UnaryOperator) computes each element from the previous — suitable for sequences. Both create infinite streams; use limit() or iterate with predicate (Java 9) to make them finite.
Stream.generate(Math::random).limit(5); // 5 random doubles
Stream.generate(() -> UUID.randomUUID().toString()).limit(3); // 3 UUIDs
Stream.iterate(1, n -> n * 2).limit(10); // 1,2,4,8,16,32,64,128,256,512
Q52. What is the char stream from String?
A: String.chars() returns IntStream of char values. String.codePoints() returns IntStream of Unicode code points (handles surrogate pairs). Map back to chars with (char) cast.
// Count vowels:
long vowels = "Hello World".chars()
.filter(c -> "aeiouAEIOU".indexOf(c) >= 0)
.count();
// Collect characters:
String upper = "hello".chars()
.map(Character::toUpperCase)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
Q53. How do you implement custom Collectors?
A: Implement Collector<T, A, R> interface with supplier (creates accumulator), accumulator (folds element into accumulator), combiner (merges two accumulators for parallel), finisher (transforms accumulator to result), characteristics (IDENTITY_FINISH, CONCURRENT, UNORDERED).
Collector<String, StringBuilder, String> myJoiner = Collector.of(
StringBuilder::new,
(sb, s) -> sb.append(s).append("|"),
StringBuilder::append,
sb -> sb.toString().replaceAll("\\|$", "")
);
Q54. What is Collectors.averagingInt/Long/Double()?
A: Returns the arithmetic mean as double. More convenient than mapToInt().average() when used as a downstream collector in groupingBy.
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept,
Collectors.averagingDouble(Employee::getSalary)));
Q55. How do you remove duplicates from a list preserving order?
List<String> unique = list.stream()
.distinct()
.collect(Collectors.toList()); // preserves first encounter order
Q56. What happens if filter predicate throws an exception?
A: The exception propagates to the terminal operation's caller (sequential stream). In parallel streams, the exception may be wrapped in a CompletionException. Always handle exceptions inside the lambda or use try-catch wrapping utilities.
Q57. What is Spliterator and its role in streams?
A: Spliterator (Splittable Iterator) is the underlying mechanism for stream splitting and traversal. It supports tryAdvance() (one element at a time) and trySplit() (splits into two for parallel). Custom Spliterators enable efficient parallel processing of custom data sources.
Q58. How do you build a stream from a Scanner or BufferedReader?
// Lines from file:
try (Stream<String> lines = Files.lines(Path.of("data.txt"))) {
lines.filter(l -> !l.isBlank()).count();
}
// Lines from BufferedReader:
new BufferedReader(new FileReader("file.txt")).lines().collect(toList());
// Scanner tokens:
new Scanner("a b c d").tokens().collect(toList());
Q59. What is the difference between toArray() and collect(toList())?
A: toArray() returns Object[] (or T[] with generator). collect(toList()) returns a typed List. Use toArray when you need an array for an API that requires one. Use collect(toList()) for general collection operations.
Object[] arr1 = stream.toArray();
String[] arr2 = stream.toArray(String[]::new); // typed array
Q60. What is Collectors.minBy() and maxBy()?
A: minBy(Comparator) and maxBy(Comparator) return Optional<T> containing the min/max element per group. Used as downstream in groupingBy to find extremes per category.
Map<String, Optional<Employee>> topEarnerByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept,
Collectors.maxBy(Comparator.comparingDouble(Employee::getSalary))));
Q61. How do streams handle null elements?
A: Streams allow null elements unless operations explicitly reject them. However, some operations throw NullPointerException: sorted() on null (Comparable comparison), distinct() requires hashCode/equals (nulls work for distinct), Stream.of(null) works. Best practice: use filter(Objects::nonNull) before processing, or Stream.ofNullable().
Q62. What is the difference between reduce and collect?
A: reduce: immutable reduction — combines elements using a function, suitable for single-value results (sum, max, string). collect: mutable reduction — accumulates elements into a mutable container (List, Map). collect is more efficient for building collections; reduce is functional and supports parallel without worry about container mutability.
Q63. How do you implement groupBy then summing?
// Total salary per department:
Map<String, Double> totalSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDept,
Collectors.summingDouble(Employee::getSalary)
));
Q64. What is the impact of sorted() on parallel stream performance?
A: sorted() is a stateful operation that requires all elements to be collected before any can be output. In parallel streams, this creates a barrier — parallel processing continues, but all results must be merged and sorted before downstream operations continue. This negates parallelism benefits for downstream operations. Avoid sorted() in parallel streams unless absolutely needed.
Q65. How do you count distinct elements?
long distinctCount = stream.distinct().count();
// Or collect to set:
long uniqueCount = stream.collect(Collectors.toSet()).size();
Q66. What is the difference between Stream.empty() and Optional.empty()?
A: Stream.empty() returns an empty Stream — no elements, used as return value from methods that might return no elements in a stream context. Optional.empty() represents absence of a single optional value. Stream.empty() is typically used in flatMap when a single element maps to zero results.
Q67. How do you implement a sliding window with streams?
// Sliding window of size 3 over a list:
int size = 3;
IntStream.range(0, list.size() - size + 1)
.mapToObj(i -> list.subList(i, i + size))
.collect(toList());
Q68. What is a Collector characteristic?
A: Characteristics are hints to the stream framework: CONCURRENT (accumulator can be called from multiple threads), UNORDERED (collection doesn't need to maintain order), IDENTITY_FINISH (finisher is identity, no transformation needed). These enable optimizations in parallel streams.
Q69. How do you chain multiple stream pipelines?
A: Collect intermediate results then create new streams. Streams are pipeline-based — you can't split one stream into two. Use collect() to materialize, then create two streams from the result, or use Collectors.teeing() for parallel collection.
Q70. What is the purpose of Collectors.summingInt/Long/Double()?
A: Returns a Collector that sums elements by applying a numeric mapper. More convenient than mapToInt().sum() when used as a downstream Collector in groupingBy for per-group sums.
int totalAge = people.stream().collect(Collectors.summingInt(Person::getAge));
Map<String, Integer> ageSumByCity = people.stream()
.collect(Collectors.groupingBy(Person::getCity, Collectors.summingInt(Person::getAge)));
Q71. How does stream handle ConcurrentModificationException?
A: Streams from fail-fast collections (ArrayList, HashMap) will throw ConcurrentModificationException if the source is modified during stream processing. Don't modify the source collection inside stream operations. Use a copy or collect-then-remove pattern.
Q72. What is the difference between map(Function) and mapToObj(IntFunction) on IntStream?
A: IntStream.map(IntUnaryOperator) maps int to int, returns IntStream. IntStream.mapToObj(IntFunction<R>) maps int to an object, returns Stream<R>. IntStream.mapToLong(IntToLongFunction) maps int to long. Use mapToObj when you need to box or convert to object type.
Q73. How do you get a running sum (prefix sum) with streams?
// Running sum isn't directly supported in pure streams (requires state)
// Workaround with AtomicInteger:
AtomicInteger running = new AtomicInteger(0);
List<Integer> prefixSums = IntStream.of(1,2,3,4,5)
.mapToObj(n -> running.addAndGet(n))
.collect(toList()); // [1,3,6,10,15]
Q74. What is the difference between Files.lines() and Files.readAllLines()?
A: Files.lines() returns a lazy Stream<String> — reads line by line on demand (memory efficient for large files). Must close the stream (use try-with-resources). Files.readAllLines() reads all lines eagerly into a List<String> — loads entire file into memory. Use lines() for large files; readAllLines() for small ones.
Q75. How do you remove elements from a list while iterating using streams?
// Stream-based: create new list without removed elements
List<String> filtered = list.stream()
.filter(s -> !s.startsWith("X"))
.collect(Collectors.toList());
// Or use Collection.removeIf() (cleaner):
list.removeIf(s -> s.startsWith("X")); // modifies in-place
Q76. What is Collectors.reducing()?
A: Collectors.reducing(identity, mapper, BinaryOperator) performs a reduction as a Collector — useful as downstream in groupingBy. Similar to reduce() but as a Collector.
Map<String, Integer> maxSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept,
Collectors.reducing(0, Employee::getSalary, Integer::max)));
Q77. How do parallel streams split work?
A: The Spliterator's trySplit() method divides the stream source into two halves recursively until chunks are small enough for individual threads. ArrayList and arrays split efficiently (random access). LinkedList splits poorly (must traverse). The ForkJoinPool manages the thread scheduling and result combining.
Q78. What is Stream.Builder?
A: Stream.Builder<T> is a mutable builder for Stream. Use when you can't determine all elements upfront. Call accept() to add elements, build() to create the stream (after which no more elements can be added).
Stream.Builder<String> builder = Stream.builder();
builder.accept("a");
if (condition) builder.accept("b");
builder.accept("c");
Stream<String> stream = builder.build();
Q79. How do you use streams with Jackson/JSON processing?
A: Jackson 2.8+: ObjectReader.readValues() returns MappingIterator which is Closeable. Convert to stream with StreamSupport.stream(iterator.spliterator(), false). Or use Jackson's streaming API (JsonParser) with custom Spliterator for memory-efficient JSON processing.
Q80. What is the best way to find second largest element?
OptionalInt secondLargest = IntStream.of(5,3,8,1,9,2)
.boxed()
.sorted(Comparator.reverseOrder())
.distinct()
.skip(1)
.mapToInt(Integer::intValue)
.findFirst();
Q81. What is the difference between anyMatch and contains?
A: List.contains(element) uses equals() to check membership — O(n) linear scan. anyMatch(Predicate) applies a custom condition — more flexible (can check substrings, ranges, etc.). For exact membership test, contains() is more readable; anyMatch() for custom conditions.
Q82. How do you create a frequency map of characters in a string?
Map<Character, Long> freq = "hello world".chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
Q83. What is Collectors.toUnmodifiableSet() and toUnmodifiableList()?
A: Java 10. Returns unmodifiable Set/List — add/remove throws UnsupportedOperationException. Equivalent to wrapping with Collections.unmodifiableList(). Use when the consumer should not modify the collection.
Q84. How do you sum salaries grouped by department and sorted by sum?
employees.stream()
.collect(Collectors.groupingBy(Employee::getDept,
Collectors.summingDouble(Employee::getSalary)))
.entrySet().stream()
.sorted(Map.Entry.<String,Double>comparingByValue().reversed())
.forEach(e -> System.out.printf("%s: %.2f%n", e.getKey(), e.getValue()));
Q85. What is the difference between stream().map() and stream().flatMap() with Optional?
A: Optional.map(f) where f returns T wraps result in Optional. Optional.flatMap(f) where f returns Optional<T> doesn't double-wrap. In stream context: Stream.flatMap(e -> Optional.of(x).stream()) is the Java 9 pattern for safe element expansion.
Q86. How do you find all elements matching a condition and transform them?
// Find all employees earning > 50000 and get their names:
List<String> highEarnerNames = employees.stream()
.filter(e -> e.getSalary() > 50000)
.map(Employee::getName)
.sorted()
.collect(Collectors.toList());
Q87. What is the purpose of Collectors.mapping() vs map() + collect()?
A: Collectors.mapping() is a downstream Collector — applied after groupingBy to transform values within each group. You cannot use stream().map() after groupingBy since you're inside a Collector. mapping() fills this gap for downstream transformations.
Q88. How do you perform a two-level grouping?
// Group by department, then by city:
Map<String, Map<String, List<Employee>>> byDeptThenCity = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept,
Collectors.groupingBy(Employee::getCity)));
Q89. What is the Stream.concat() method?
A: Stream.concat(a, b) creates a lazily concatenated stream of two streams. The resulting stream is sequential/parallel based on the first stream. If either stream is ordered, the result is ordered.
Stream<String> combined = Stream.concat(list1.stream(), list2.stream());
Q90. How do streams interact with Spring Data's JPA query results?
A: Spring Data JPA supports Stream<T> as a repository return type using @QueryHints with scrollable result sets. The stream must be closed after use (try-with-resources) to release the underlying connection. This enables lazy processing of large result sets without loading all into memory.
Q91. What is a terminal operation that doesn't return a value?
A: forEach(Consumer) and forEachOrdered(Consumer) are terminal operations that return void. They process each element for side effects. Not recommended in parallel streams if order matters. Prefer collect() over forEach() when building collections.
Q92. How do you check if a stream is parallel?
A: Stream does not expose a direct isParallel() in the public API from BaseStream. Use isParallel() from BaseStream interface (Stream extends BaseStream). Or use stream.parallel() always — if already parallel, it's a no-op.
Stream<String> s = list.stream();
System.out.println(s.isParallel()); // false
s = s.parallel();
System.out.println(s.isParallel()); // true
Q93. What is the collect(Collectors.joining()) with a delimiter on a non-String stream?
A: Collectors.joining() only works on Stream<String>. For non-String streams, first map to String: stream.map(Object::toString).collect(Collectors.joining(", ")).
Q94. What is StreamSupport and when do you use it?
A: StreamSupport.stream(spliterator, parallel) creates a Stream from a custom Spliterator. Used when you have a custom data source that isn't a Collection or when bridging legacy iterators to Streams.
// Convert Iterator to Stream:
Iterator<String> it = someLibrary.getIterator();
Spliterator<String> split = Spliterators.spliteratorUnknownSize(it, Spliterator.ORDERED);
Stream<String> stream = StreamSupport.stream(split, false);
Q95. How do you calculate standard deviation using streams?
double[] values = {2,4,4,4,5,5,7,9};
double mean = Arrays.stream(values).average().orElse(0);
double stdDev = Math.sqrt(Arrays.stream(values)
.map(v -> (v - mean) * (v - mean))
.average().orElse(0));
Q96. What happens when you call parallel() on an already parallel stream?
A: Calling parallel() on a parallel stream is a no-op — it returns the same stream unchanged. Similarly, sequential() on a sequential stream is a no-op. You can switch between parallel/sequential at any point in the pipeline; the last call wins for the entire pipeline.
Q97. What is the difference between map and peek in stream debugging?
A: map(Function) transforms elements and MUST return a value — it changes the stream data. peek(Consumer) doesn't transform elements — it's a pass-through for side effects. Use peek for logging/debugging only; map for actual transformations.
Q98. How do you find all duplicates in a list using streams?
Set<String> seen = new HashSet<>();
Set<String> duplicates = list.stream()
.filter(e -> !seen.add(e)) // add returns false if already present
.collect(Collectors.toSet());
Q99. What is the best practice for stream exception handling?
A: 1) Wrap checked exceptions in RuntimeException inside the lambda. 2) Create a ThrowingFunction wrapper. 3) Use Try monad (Vavr library). 4) For forEach with exceptions, use a traditional loop instead. 5) In parallel streams, all thread exceptions are collected — only one is thrown, others are suppressed.
Q100. How do you implement a functional data processing pipeline with streams?
// Complete ETL pipeline:
Map<String, DoubleSummaryStatistics> report = Files.lines(Path.of("sales.csv"))
.skip(1) // skip header
.map(line -> line.split(",")) // parse CSV
.filter(cols -> cols.length == 3) // validate
.collect(Collectors.groupingBy(
cols -> cols[0], // group by department
Collectors.summarizingDouble(cols -> Double.parseDouble(cols[2])) // summarize sales
));
Q101. What is Collectors.filtering() introduced in Java 9?
A: Collectors.filtering(predicate, downstream) is like filter() but as a Collector — used downstream in groupingBy to filter elements within each group before collecting.
// For each dept, only senior employees (salary > 80000):
Map<String, List<Employee>> seniorByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept,
Collectors.filtering(e -> e.getSalary() > 80000, Collectors.toList())));
Q102. What is Collectors.flatMapping() introduced in Java 9?
A: Collectors.flatMapping(mapper, downstream) applies flatMap logic within a Collector — useful for flattening collections nested within grouped elements.
// Collect all skills per department (employees have List<String> skills):
Map<String, Set<String>> skillsByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDept,
Collectors.flatMapping(e -> e.getSkills().stream(), Collectors.toSet())));
Post a Comment
Add