Documentation
The Java™ Tutorials
Download Ebooks
Download JDK
Search Java Tutorials
Trail: Collections
Lesson: Aggregate Operations
Home Page > Collections > Aggregate Operations
« Previous • TOC

# Answers to Questions and Exercises: Aggregate Operations

## Questions

1. Q: A sequence of aggregate operations is known as a ___ .
   A: Pipeline
2. Q: Each pipeline contains zero or more ___ operations.
   A: Intermediate
3. Q: Each pipeline ends with a ___ operation.
   A: Terminal
4. Q: What kind of operation produces another stream as its output?
   A: Intermediate
5. Q: Describe one way in which the `forEach` aggregate operation differs from the enhanced `for` statement or iterators.
   A: The `forEach` aggregate operation lets the system decide "how" the iteration takes place. Using aggregate operations lets you focus on "what" instead of "how."
6. Q: True or False: A stream is similar to a collection in that it is a data structure that stores elements.
   A: False. Unlike a collection, a stream is not a data structure. It instead carries values from a source through a pipeline.
7. Q: Identify the intermediate and terminal operations in this code:

       double average = roster
           .stream()
           .filter(p -> p.getGender() == Person.Sex.MALE)
           .mapToInt(Person::getAge)
           .average()
           .getAsDouble();

   A: Intermediate: `filter`, `mapToInt`
   Terminal: `average`
   The terminal operation `average` returns an `OptionalDouble`. The `getAsDouble` method is then invoked on that returned object. It is always a good idea to consult the API Specification for information about whether an operation is intermediate or terminal.
8. Q: The code `p -> p.getGender() == Person.Sex.MALE` is an example of what?
   A: A lambda expression.
9. Q: The code `Person::getAge` is an example of what?
   A: A method reference.
10. Q: Terminal operations that combine the contents of a stream and return one value are known as what?
    A: Reduction operations.
11. Q: Name one important difference between the `Stream.reduce` method and the `Stream.collect` method.
    A: `Stream.reduce` always creates a new value when it processes an element. `Stream.collect` modifies (or mutates) the existing value.
12. Q: If you wanted to process a stream of names, extract the male names, and store them in a new `List`, would `Stream.reduce` or `Stream.collect` be the most appropriate operation to use?
    A: The collect operation is most appropriate for collecting into a `List`.
    Example:

        List<String> namesOfMaleMembersCollect = roster
            .stream()
            .filter(p -> p.getGender() == Person.Sex.MALE)
            .map(p -> p.getName())
            .collect(Collectors.toList());

13. Q: True or False: Aggregate operations make it possible to implement parallelism with non-thread-safe collections.
    A: True, provided that you do not modify (mutate) the underlying collection while you are operating on it.
14. Q: Streams are always serial unless otherwise specified. How do you request that a stream be processed in parallel?
    A: Obtain the parallel stream by invoking `parallelStream()` instead of `stream()`.

## Exercises

1. Exercise: Write the following enhanced `for` statement as a pipeline with lambda expressions. Hint: Use the `filter` intermediate operation and the `forEach` terminal operation.

       for (Person p : roster) {
           if (p.getGender() == Person.Sex.MALE) {
               System.out.println(p.getName());
           }
       }

   Answer:

       roster
           .stream()
           .filter(e -> e.getGender() == Person.Sex.MALE)
           .forEach(e -> System.out.println(e.getName());

2. Convert the following code into a new implementation that uses lambda expressions and aggregate operations instead of nested `for` loops. Hint: Make a pipeline that invokes the `filter`, `sorted`, and `collect` operations, in that order.

       List<Album> favs = new ArrayList<>();
       for (Album a : albums) {
           boolean hasFavorite = false;
           for (Track t : a.tracks) {
               if (t.rating >= 4) {
                   hasFavorite = true;
                   break;
               }
           }
           if (hasFavorite)
               favs.add(a);
       }
       Collections.sort(favs, new Comparator<Album>() {
                                  public int compare(Album a1, Album a2) {
                                      return a1.name.compareTo(a2.name);
                                  }});

   Answer:

       List<Album> sortedFavs =
         albums.stream()
               .filter(a -> a.tracks.anyMatch(t -> (t.rating >= 4)))
               .sorted(Comparator.comparing(a -> a.name))
               .collect(Collectors.toList());

   Here we have used the stream operations to simplify each of the three major steps -- identification of whether any track in an album has a rating of at least 4 (`anyMatch`), the sorting, and the collection of albums matching our criteria into a `List`. The `Comparator.comparing()` method takes a function that extracts a `Comparable` sort key, and returns a `Comparator` that compares on that key.

« Previous • TOC

Previous page: Questions and Exercises: Aggregate Operations