Patrick C.
Patrick C.

Reputation: 1429

Java8: About order of Map elements returned by Collectors.groupingBy

would like to seek for advice on the following piece of code modified from an example came across.

I was wondering whether the output sequence in the resulting Map would change when the input List element sequence is changed. However, it seems the output is the same in all cases (I have run the program repeatedly on IDE). May I seek for advice on whether the order of the resulting Map elements can be expected? Will there be any difference in sequence (alphabetical/non-alphbetical?) when using enum or String, or the element sequence is simply random?

public class TestCountry {

    public enum Continent {ASIA, EUROPE}

    String name;
    Continent region;

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

    public String getName () {
        return name;
    }

    public Continent getRegion () {
        return region;
    }

    public static void main(String[] args) {
        List<TestCountry> couList1 = Arrays.asList (
                new TestCountry ("Japan", TestCountry.Continent.ASIA),
                new TestCountry ("Italy", TestCountry.Continent.EUROPE),
                new TestCountry ("Germany", TestCountry.Continent.EUROPE));

        List<TestCountry> couList2 = Arrays.asList (
                new TestCountry ("Italy", TestCountry.Continent.EUROPE),
                new TestCountry ("Japan", TestCountry.Continent.ASIA),
                new TestCountry ("Germany", TestCountry.Continent.EUROPE));

        Map<TestCountry.Continent, List<String>> mapWithRegionAsKey1 = couList1.stream ()
                    .collect(Collectors.groupingBy (TestCountry ::getRegion,
                        Collectors.mapping(TestCountry::getName, Collectors.toList())));

        Map<String, List<Continent>> mapWithNameAsKey1 = couList1.stream ()
                    .collect(Collectors.groupingBy (TestCountry::getName,
                        Collectors.mapping(TestCountry::getRegion, Collectors.toList())));

        Map<TestCountry.Continent, List<String>> mapWithRegionAsKey2 = couList2.stream ()
                    .collect(Collectors.groupingBy (TestCountry ::getRegion,
                        Collectors.mapping(TestCountry::getName, Collectors.toList())));

        Map<String, List<Continent>> mapWithNameAsKey2 = couList2.stream ()
                    .collect(Collectors.groupingBy (TestCountry::getName,
                        Collectors.mapping(TestCountry::getRegion, Collectors.toList())));

        System.out.println("Value of mapWithRegionAsKey1 in couList1: " + mapWithRegionAsKey1);
        System.out.println("Value of mapWithNameAsKey1 in couList1: " + mapWithNameAsKey1);

        System.out.println("Value of mapWithRegionAsKey2 in couList2: " + mapWithRegionAsKey2);
        System.out.println("Value of mapWithNameAsKey2 in couList2: " + mapWithNameAsKey2);
    }
}

/*
 * Output:
Value of mapWithRegionAsKey1 in couList1: {ASIA=[Japan], EUROPE=[Italy, 
Germany]}
Value of mapWithNameAsKey1 in couList1: {Japan=[ASIA], Italy=[EUROPE], 
Germany=[EUROPE]}
Value of mapWithRegionAsKey2 in couList2: {ASIA=[Japan], EUROPE=[Italy, 
Germany]}
Value of mapWithNameAsKey2 in couList2: {Japan=[ASIA], Italy=[EUROPE], 
Germany=[EUROPE]}
 */

Upvotes: 2

Views: 949

Answers (2)

Eran
Eran

Reputation: 393781

Collectors.groupingBy() currently returns a HashMap. This is an implementation detail that can change in future versions.

The entries of a HashMap are not sorted, but the order in which they are iterated (when printing the HashMap) is deterministic, and doesn't depend on insertion order.

Therefore you see the same output, regardless of the order of the elements in the input List. You shouldn't rely on that order, though, since it's an implementation detail.

If you care about the order of the entries in the output Map, you can modify the code to produce a LinkedHashMap (where the default order is insertion order) or a TreeMap (where the keys are sorted).

BTW, if you change your input to

    List<TestCountry> couList1 = Arrays.asList (
            new TestCountry ("Japan", TestCountry.Continent.ASIA),
            new TestCountry ("Italy", TestCountry.Continent.EUROPE),
            new TestCountry ("Germany", TestCountry.Continent.EUROPE));

    List<TestCountry> couList2 = Arrays.asList (
            new TestCountry ("Germany", TestCountry.Continent.EUROPE),
            new TestCountry ("Italy", TestCountry.Continent.EUROPE),
            new TestCountry ("Japan", TestCountry.Continent.ASIA)
            );

you will get a different order in the output:

Value of mapWithRegionAsKey1 in couList1: {ASIA=[Japan], EUROPE=[Italy, Germany]}
Value of mapWithNameAsKey1 in couList1: {Japan=[ASIA], Italy=[EUROPE], Germany=[EUROPE]}
Value of mapWithRegionAsKey2 in couList2: {ASIA=[Japan], EUROPE=[Germany, Italy]}
Value of mapWithNameAsKey2 in couList2: {Japan=[ASIA], Italy=[EUROPE], Germany=[EUROPE]}

This results from the fact that the elements of the individual output Lists (which are ArrayLists in the current implementation) are ordered according to insertion order, which depends on the ordering of your input List.

Upvotes: 2

Eugene
Eugene

Reputation: 120848

Before questioning this, read the documentation which says:

There are no guarantees on the type, mutability, serializability, or thread-safety of the Map or List objects returned.

So if you can't tell what type of the Map is returned, you can't really tell what order (if any) there will be. Well at the moment, As Eran points out it is a HashMap, but that is not guaranteed.

The question is why would you care abut this? If it for academic purposes, it makes sense, if not, collect to something that does guarantee the order.

Collectors.groupingBy take a parameter that allows you to specify the actual Map implementation you might need.

Upvotes: 2

Related Questions