jonesy
jonesy

Reputation: 656

Can I group by property and map to new object in java 8

Hi I have an array of objects where the category and sub category details are in the same object. So its like,

public class MyObject {
    String categoryCode;
    String categeoryCodeDescription;
    String subCategoryCode;
    String subCategoryCodeDescription;

    public MyObject(String categoryCode, String categeoryCodeDescription, String subCategoryCode, String subCategoryCodeDescription) {
        this.categoryCode = categoryCode;
        this.categeoryCodeDescription = categeoryCodeDescription;
        this.subCategoryCode = subCategoryCode;
        this.subCategoryCodeDescription = subCategoryCodeDescription;
    }
}

List<MyObject> collection = new ArrayList<MyObject>;
collection.add(new My Object("A1", "descA1", "A1A", "descA1A"));
collection.add(new My Object("A1", "descA1", "A1B", "descA1B"));
collection.add(new My Object("A1", "descA1", "A1C", "descA1C"));
collection.add(new My Object("A2", "descA1", "A2A", "descA2A"));
collection.add(new My Object("A2", "descA1", "A2B", "descA2B"));

Can you group by category code but map to an object including the description at the same time. So if I have two classes like..

public class Category {
    String categoryCode;
    String categoryDesc;
    public Category (String categoryCode, String categoryDesc) {
        this.categoryCode = categoryCode;
        this.categoryDesc = categoryDesc;
    }
}

public class SubCategory {
    String subCategoryCode;
    String subCategoryDesc;
    public SubCategory (String subCategoryCode, String subCategoryDesc) {
        this.subCategoryCode = subCategoryCode;
        this.subCategoryDesc = subCategoryDesc;
    }
}

..and I want to group the collection list into a Map<Category,List<SubCategory>>. I can group on the category code but I can't see how to create a new category instance as the map key. This may not be possible in a one liner.

Map<String, List<MyObject>> map = collection.stream().collect(Collectors.groupingBy(MyObject::getCategoryCode));

Upvotes: 6

Views: 7787

Answers (2)

Ousmane D.
Ousmane D.

Reputation: 56469

You can do this via the toMap collector taking a merge function:

Map<Category, List<SubCategory>> result = collection.stream()
                .collect(toMap(e -> new Category(e.getCategoryCode(), e.getCategeoryCodeDescription()),
                        v -> new ArrayList<>(singletonList(new SubCategory(v.getSubCategoryCode(), v.getSubCategoryCodeDescription()))),
                        (l, r) -> { l.addAll(r); return l; }));

This assumes you Category class has overriden the equals and hashcode methods as follows:

class Category {
    ...
    ... // properties + getters + constructors etc..
    ...

    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Category category = (Category) o;
        return Objects.equals(categoryCode, category.categoryCode);
    }

    @Override
    public int hashCode() {
        return Objects.hash(categoryCode);
    }

}

to better readability you could extract the keyMapper, valueMapper and the merge function to different functions as follows:

private static List<SubCategory> valueMapper(MyObject v) {
        return new ArrayList<>(singletonList(new SubCategory(v.getSubCategoryCode(), v.getSubCategoryCodeDescription())));
}

private static List<SubCategory> merge(List<SubCategory> l, List<SubCategory> r) {
    l.addAll(r);
    return l;
}

private static Category keyMapper(MyObject e) {
     return new Category(e.getCategoryCode(), e.getCategeoryCodeDescription());
}

Then you can do:

 Map<Category, List<SubCategory>> result = collection.stream()
                .collect(toMap(Main::keyMapper,
                        Main::valueMapper,
                        Main::merge));

Where Main is the class containing the keyMapper, valueMapper and merge methods.

Upvotes: 4

Eran
Eran

Reputation: 394086

It can be done if you chain a mapping collector to groupingBy.

You use mapping() to transform the MyObject instances to SubCategory instances.

Map<Category,List<SubCategory>> map =
    collection.stream().collect(Collectors.groupingBy(mo -> new Category(mo.getCategoryCode(),mo.getCategoryDesc()),
                                                      Collectors.mapping(mo->new SubCategory(mo.getSubCategoryCode(),mo.getSubCategoryDesc()),
                                                                         Collectors.toList())));

Note that Category will have to override equals and hashCode in order for this grouping to work.

Upvotes: 9

Related Questions