Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

java

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));
like image 528
jonesy Avatar asked Dec 18 '18 11:12

jonesy


People also ask

How do I convert one object to another object in Java 8?

In Java 8, we have the ability to convert an object to another type using a map() method of Stream object with a lambda expression. The map() method is an intermediate operation in a stream object, so we need a terminal method to complete the stream.

What is groupingBy in Java?

The groupingBy() method of Collectors class in Java are used for grouping objects by some property and storing results in a Map instance.

What is flatMap in Java stream?

Stream flatMap(Function mapper) returns a stream consisting of the results of replacing each element of this stream with the contents of a mapped stream produced by applying the provided mapping function to each element. Stream flatMap(Function mapper) is an intermediate operation. These operations are always lazy.

What is flat map?

flatMap , as it can be guessed by its name, is the combination of a map and a flat operation. That means that you first apply a function to your elements, and then flatten it. Stream. map only applies a function to the stream without flattening the stream.


2 Answers

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.

like image 200
Eran Avatar answered Nov 14 '22 23:11

Eran


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.

like image 29
Ousmane D. Avatar answered Nov 14 '22 21:11

Ousmane D.