4

I'm preparing for a Java exam and have one question that got me lots of tough time. Despite studying it hard I'm not able to find out what determines the order of the result.

Have a look, please:

class Country {

    public enum Continent {
        ASIA, EUROPE
    }
    String name;
    Continent region;

    public Country(String na, Continent reg) {
        name = na;
        region = reg;
    }

    public String getName() {
        return name;
    }

    public Continent getRegion() {
        return region;
    }
}

public class OrderQuestion {

    public static void main(String[] args) {
        List<Country> couList = Arrays.asList(
                new Country("Japan", Country.Continent.ASIA),
                new Country("Italy", Country.Continent.EUROPE),
                new Country("Germany", Country.Continent.EUROPE));
        Map<Country.Continent, List<String>> regionNames = couList.stream()
                .collect(Collectors.groupingBy(Country::getRegion,
                        Collectors.mapping(Country::getName, Collectors.toList())));
        System.out.println(regionNames);
    }
}

What is the result?

A. {EUROPE = [Italy, Germany], ASIA = [Japan]}
B. {ASIA = [Japan], EUROPE = [Italy, Germany]}
C. {EUROPE = [Germany, Italy], ASIA = [Japan]}
D. {EUROPE = [Germany], EUROPE = [Italy], ASIA = [Japan]}

and what most important what determines the specific result and not another?

Pshemo
  • 122,468
  • 25
  • 185
  • 269
  • 3
    Look at the API documentation for `Map` and see what it says about the order of elements https://docs.oracle.com/javase/8/docs/api/java/util/Map.html – Tesseract Feb 11 '17 at 22:27
  • Proposed answers are: A. {EUROPE = [Italy, Germany], ASIA = [Japan]} B. {ASIA = [Japan], EUROPE = [Italy, Germany]} C. {EUROPE = [Germany, Italy], ASIA = [Japan]} D. {EUROPE = [Germany], EUROPE = [Italy], ASIA = [Japan]} I know what produces Java Runtime executing this code (A) but have no idea why this and not something different. I'm looking for rationale. – Fred Filozof Feb 11 '17 at 22:29
  • Using to streams you can do many operations in one line of code. To make the question more clear, you can split the stream line, assign each step to a variable and describe what exactly surprise you and what you except to see. – elirandav Feb 11 '17 at 22:44
  • This exam question to me seems rather absurd. If I'm not mistaken, `Arrays.asList` and `Collectors.groupingBy` don't make guarantees about the underlying implementation and hence, the order of elements. Even if they did I wouldn't assume anything without using a sort – IcedDante Feb 11 '17 at 22:53
  • {EUROPE=[Italy, Germany], ASIA=[Japan]} in my IDE – Stéphane GRILLON Feb 11 '17 at 22:53
  • 3
    @IcedDante [`Arrays.asList()`](https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList-T...-) guarantees order, and much more: *Returns a **fixed-size** list **backed by** the specified **array**. (Changes to the returned list "**write through**" to the array.) [...] The returned list is **serializable** and **implements `RandomAccess`**.* – Andreas Feb 11 '17 at 23:26

1 Answers1

6

We can eliminate D because keys in Map need to be unique which fails for EUROPE.

We can eliminate C because of order in [Germany, Italy]. Italy was placed before Germany in list, so it also has to be stored in that order in result list.

But how we should decide if we should eliminate B or A? Well, we cant.

Map doesn't guarantee specific order of key-value pairs. Some maps allow remembering order of placing key-value pairs like LinkedHashMap, some allow to order entries by keys like TreeMap, but this behaviour is not specified for Collectors.groupingBy.

It is confirmed by fact that this method is using HashMap, which orders key-value pairs based on hashCode() of key (Country.Continent enum here) and amount of pairs already held. Implementation of hashCode() for Enum is inherited from Object class which means it is based on memory location which can change for each time when we run JVM, so it is random value which prevents us from assuming any order (which confirms that it is unspecified).

So based on lack of specification about Map returned by groupingBy both orders of entries is possible so both A and B are possible answers.

Pshemo
  • 122,468
  • 25
  • 185
  • 269
  • 4
    Good analysis, +1. The question described by the OP is a poor exam question, in my estimation. Getting the "correct" answer relies on implementation-specific behavior (HashMap ordering) and not on the specification. – Stuart Marks Feb 12 '17 at 00:29
  • Pshemo, thank you for the analysis but: a) The question suggests only one answer is correct b) Why changing the order of entries in couList affects result? HashMap by definition doesn't gurantee any specific order but in practice the order is determined by hashCode(?). For instance, despite modyifing the order of entries for the following the result is the same: Map hm = new HashMap<>(); hm.put(0, "a"); hm.put(1, "b"); hm.put(2, "c"); hm.forEach((k, v) -> out.println(k + ":" + v)); that is: 0:a 1:b 2:c – Fred Filozof Feb 12 '17 at 09:31
  • 1
    @FredFilozof (a) I suspect that author of that question simply run his code few times and got same answer so he assumed that it has to be right always. But I managed to get both answers by creating additional objects before enum was used, so they would occupy different memory then previously granted so their hash also chanced. (b) order of elements in your stream here depends on order of elements in source (in `couList`). It is true that HashMap doesn't guarantee any specific order, but there is *some* order based on hashCodes of keys and amount of pairs. – Pshemo Feb 12 '17 at 14:20
  • 1
    If hashcode for key is calculated using properties which can change in each run, like memory address then HashMap using such keys in each run may have semi-random order. But hashCode for many classes is not based on properties with can be random for each run. For instance Integer returns as hashCode int value which it is holding, so hashcode of `new Integer(1)` is `1`, hashCode for `new Integer(2)` is `2. HashCode for String is also calculated only based on characters it is holding. So if you use Integer as hey in HashMap you should get same order for same key combination. – Pshemo Feb 12 '17 at 14:20
  • 1
    BTW, Order in HashMap may depend on order of placing elements. HashMap is backed up by array of structure similar to linked list (we call them buckets). Each bucket will hold elements for specific range of hashcodes (like if amount of buckets is 4 then first bucket can gather elements with hash 0, 4, 8, and so on, second with 1, 5, 9, you get the idea). So order of elements in bucket depends on order of insertions. So if two keys can have same hashCode like `"Aa"` and `"BB"` strings (which is `2112`) they will always be placed in same bucket. Demo: https://ideone.com/GqI9hd – Pshemo Feb 12 '17 at 21:49
  • 1
    Actually what I described is HashMap before Java 8 update. Previously it worked like this: http://stackoverflow.com/a/18492835/1393766, but if I am not mistaken later it was adapted to use tree instead of linked list structure. I can't say more now. Will need to learn it someday. – Pshemo Feb 12 '17 at 21:54
  • 1
    It should be emphasized that it is irrelevant, how the hash code is calculated, the order of the map is unspecified and that’s all that counts. A particular `HashMap` implementation might exhibit a reproducible order for fixed hash codes, but the `groupingBy` collector doesn’t even guaranty to return the specific implementation type “`java.util.HashMap`”. The order of the list elements, on the other hand, is guaranteed to be in the right order, regardless of whether the stream is parallel or not. (It does not guaranty to return an `ArrayList`, however) – Holger Feb 13 '17 at 14:19