Streams and Lambda Expressions Flashcards
6 cards from real OCP practice questions. Tap to flip, then mark Knew It or Still Learning โ missed cards come back until you master them.
Read the first 6 Streams and Lambda Expressions flashcards as text
Which of the following best describes the core difference between intermediate and terminal operations in the Java Stream API?
Answer: Intermediate operations are lazy and return a new stream, while terminal operations are eager and produce a result or side-effect.
The key distinction lies in their execution behavior. Intermediate operations (e.g., `filter`, `map`, `sorted`) are lazy; they don't execute until a terminal operation is invoked. They build up a pipeline of transformations, each returning a new `Stream`. Terminal operations (e.g., `forEach`, `collect`, `reduce`) are eager, triggering the processing of the entire pipeline and producing a final result, such as a collection, a single value, or a side-effect. Once a terminal operation is called, the stream is considered consumed and cannot be reused.
A developer is working with a `Stream` and wants to produce a `Map>` where the keys are the lengths of the strings and the values are lists of strings of that length. Which `Collector` should be used?
Answer: Collectors.groupingBy(String::length)
The `Collectors.groupingBy()` collector is specifically designed for this purpose. It takes a classifier function (in this case, `String::length`) and groups the elements of the stream into a `Map`. The keys of the map are the results of applying the classifier function, and the values are `List`s containing the elements that mapped to that key. `Collectors.partitioningBy` only separates elements into two groups based on a `Predicate`. The `toMap` collectors would throw an `IllegalStateException` on duplicate keys (strings with the same length) without a merge function, and the provided merge function in the incorrect option is flawed.
Given the following code snippet, what is the output? ```java List list = Arrays.asList("a", "b", "c"); Optional result = list.stream() .filter(s -> s.equals("d")) .findFirst(); System.out.println(result.orElse("Not Found")); ```
Answer: Not Found
The stream is filtered for the string "d", which does not exist in the list. Therefore, the `filter` operation results in an empty stream. The `findFirst()` terminal operation on an empty stream returns an empty `Optional`. The `orElse("Not Found")` method is then called on this empty `Optional`, which causes it to return the provided default value, "Not Found". A `NoSuchElementException` would only be thrown if `get()` were called on an empty `Optional` without checking for presence.
Why would a developer choose to use a primitive stream like `IntStream` over a `Stream`?
Answer: To improve performance by avoiding the overhead of boxing and unboxing primitive values into wrapper objects.
Primitive streams (`IntStream`, `LongStream`, `DoubleStream`) are specialized versions of `Stream` that work directly with primitive data types. This avoids the automatic conversion (boxing) of a primitive (e.g., `int`) into its corresponding wrapper class (e.g., `Integer`) and the reverse process (unboxing). For large datasets, this can lead to significant performance improvements and reduced memory usage because it eliminates the creation of many wrapper objects.
Which of the following is a valid and most concise lambda expression for a `java.util.function.Predicate` that tests if a string is empty?
Answer: String::isEmpty
`String::isEmpty` is a method reference, which is a compact form of a lambda expression used to refer to a method without invoking it. In this case, it refers to the `isEmpty()` method of the `String` class. It is functionally equivalent to the lambda `s -> s.isEmpty()` but is generally preferred for its conciseness and clarity when the lambda simply calls an existing method. The other options are also functionally correct but are not the most concise syntax available.
What is the result of the following stream pipeline? ```java long count = Stream.of("apple", "banana", "apricot", "cherry") .filter(s -> s.startsWith("a")) .peek(System.out::println) .count(); ```
Answer: The code will print "apple" and "apricot", and `count` will be 2.
The stream pipeline first filters the elements, keeping only those that start with "a" ("apple", "apricot"). The `peek()` operation is an intermediate operation that performs an action on each element as it passes through the stream; in this case, it prints the element. Since `peek` is an intermediate operation, it doesn't terminate the stream. Finally, the `count()` terminal operation is called, which consumes the stream and returns the number of elements remaining after the filter, which is 2. The `peek` operation will execute for each of those 2 elements.