Wishgone
Wishgone

Reputation: 33

Collectors.groupingBy by two columns doesn't work

I want to group by two columns using Collectors.groupingBy.

I wrote the following:

public class TestGroupKey 
{
    public static void main(String... args) {
        List<Item> list = Arrays.asList(
            new Item(1, 1, 1),
            new Item(1, 1, 2),
            new Item(1, 1, 3),
            new Item(1, 2, 1),
            new Item(1, 2, 2),
            new Item(2, 1, 1),
            new Item(2, 2, 1),
            new Item(2, 3, 1),
            new Item(2, 4, 1),
            new Item(2, 4, 2),
            new Item(2, 4, 3),
            new Item(2, 4, 4),
            new Item(3, 1, 1),
            new Item(3, 1, 2),
            new Item(3, 1, 3),
            new Item(3, 1, 4),
            new Item(3, 2, 1),
            new Item(3, 2, 2)
        );

        Map<CustomKey, List<Item>> tupleGrouping = 
            list.stream().collect(Collectors.groupingBy(item -> item.customKey));
        tupleGrouping.entrySet().stream()
                .forEach(entry -> {
                    System.out.println(entry.getKey());
                });
    }

    public static class Item {
        public int topLevelId;
        public int secondLevelId;
        public int itemId;
        public CustomKey customKey;

        public Item(int topLevelId, int secondLevelId, int itemId) {
            this.topLevelId = topLevelId;
            this.secondLevelId = secondLevelId;
            this.itemId = itemId;
            this.customKey = new CustomKey(topLevelId, secondLevelId);
        }

        @Override
        public String toString() {
            return String.format("%d%d%d", this.topLevelId, this.secondLevelId, this.itemId);
        }
    }

    public static class CustomKey {
        public int topLevelId;
        public int secondLevelId;

        public CustomKey(int topLevelId, int secondLevelId) {
            this.topLevelId = topLevelId;
            this.secondLevelId = secondLevelId;
        }

        @Override
        public String toString() {
            return String.format("%d%d", this.topLevelId, this.secondLevelId);
        }
    }
}

The expected result is

11 
12 
21 
22 
23 
24 
31 
32

But actual result is

24
31
23
22
12
21
24
31
31
24
12
32
32
11
11
11
31

I think groupingBy is not working.

What's the problem with my use of the CustomKey Class?

Additionally, nested map key is working:

Map<Integer, Map<Integer, List<Item>>> entryGrouping = 
    list.stream()
        .collect(Collectors.groupingBy(atta1 -> atta1.topLevelId, 
                                       Collectors.groupingBy(atta2 -> atta2.secondLevelId)));

Upvotes: 3

Views: 1873

Answers (1)

Eran
Eran

Reputation: 393831

Your CustomKey class doesn't override Object's equals method, so groupingBy considers two CustomKeys to be equal only if they are the same object (which is never true in your example, since you create a new CustomKey instance for each Item).

public static class CustomKey {   
    ...
    @Override
    public int hashCode ()
    {
        // if you override equals, you should also override hashCode
    }

    @Override
    public boolean equals (Object other)
    {
        if (other == this)
            return true;
        if (!(other instanceof CustomKey))
            return false;
        CustomKey okey = (CustomKey) other;
        return this.topLevelId == okey.topLevelId && this.secondLevelId == okey.secondLevelId;
    }
    ...
}

Upvotes: 5

Related Questions