OCP Question 43, Explanation

Given:

class Band {
    String name, style, country;
    public Band (String name, String style, String country) {
        this.style = style;
        this.name = name;
        this.country = country;
    }
    public String getStyle() {
        return style;
    }
    public String toString() {
        return style + " : " + name + " : " + country;
    }
}

and the code fragment:

List<Band> bands = Arrays.asList(
    new Band("Yes", "Prog Rock", "UK"),
    new Band("Boney M", "Euro Disco", "Germany"),
    new Band("ELP", "Prog Rock", "UK"));
bands.stream()
     .collect(Collectors.groupingBy(Band::getStyle))
     .forEach( (x, y) -> System.out.println(x) );

What is the result?

A. [Euro Disco : Boney M : Germany]
[Prog Rock : Yes : UK, Prog Rock : ELP : UK]
B. Euro Disco
Prog Rock

C. [Prog Rock : Yes : UK, Prog Rock : ELP : UK]
[Euro Disco : Boney M : Germany]
D. A compilation error occurs

 

The correct answer is B.

 

We have already seen the collect() method in action (Problem 11) and even touched upon its ideology (Problem 33). In short, Stream’s collect() performs the so-called mutable reduction and needs either a Collector or Supplier as its argument. In the current Problem we obviously have the first version, the one with a Collector arg on which the code invokes the groupingBy() method.

The Collectors class has several overloaded versions of this method but they all return a Collector that in one way or another is built around Map, which is only natural as what else can you use when asked to sort something into groups? After all, you’ll need a container that holds key-value pairs, and such a data structure is called Map:

Apparently, we have the very first version, which uses a Function as its arg, and the Band:: getStyle metref provides the classification function by giving the criterion (i.e., style) to sort the elements of the List object bands into groups. As a result, collect() produces a stream of Map-type pairs.

forEach() then looks at each combo that comes its way and prints just the first element of the pair, which serves as Map’s key. Since the method doesn’t even touch values, there’s no way we can see lists (whose telltale sign is a pair of square brackets). Therefore, what is left is just option B.

Well, that was all too simple so here are additional exercises:

1) How would you go about changing the forEach() invocation to specify its paramlist types explicitly instead of forcing the compiler to use type inference?

2) Re-write collect()’s invocation to use the three-arg version of the method. Use HashMap.

3) Finally, invert output’s order by building a TreeMap on a reversed Comparator.

Click here for suggested answers.
1)   .forEach( (String x, List<Band> y) -> System.out.println(x) );

2)   .collect(Collectors.groupingBy(
                   Band::getStyle,
                   HashMap<String, List<Band>>::new,
                   Collectors.toList()
             ))

3)   .collect(Collectors.groupingBy(
                   Band::getStyle,
                   () -> new TreeMap<String, List<Band>>(
                           Comparator.<String>reverseOrder()),
                   Collectors.toList()
             ))
    .forEach( (String x, List<Band>y) -> System.out.println(x) );  // Prog Rock  \n  Euro Disco

Even though the suggested solution to the 3rd exercise goes somewhat beyond the 1Z0-809’s scope, we can still profit from it as it reminds us that metrefs never accept args. Never. If you ba-adly need to pass an arg to a metref, write an appropriate method first, otherwise just ride lambdas.

Please also note that the type var for the static Comparator.reversedOrder() method wasn’t actually necessary due to type inference but leaving TreeMap’s ctor un-parameterized would flag a comperr on the forEach() with a full-form paramlist. Sometimes javac isn’t as smart as we’d like it to be, and needs a gentle nudge from the coder…

Leave Comment

Your email address will not be published. Required fields are marked *