Java 8中是否可以按属性分组并映射到新对象?

6

你好,我有一个对象数组,其中类别和子类别的详细信息在同一个对象中。所以它看起来像这样:

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"));

您是否可以按类别代码分组,同时将其映射到一个包括描述的对象中。例如,如果我有两个类似于...

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;
    }
}

我希望将集合列表分组为一个Map<Category,List<SubCategory>>。我可以按类别代码进行分组,但我不知道如何创建新的类别实例作为映射键。这可能无法在一行中完成。

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

1
Collectors.mapping 又将再次发威... - Eugene
2个回答

9

如果您将一个 mapping 收集器链接到 groupingBy,则可以完成此操作。

您可以使用 mapping()MyObject 实例转换为 SubCategory 实例。

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())));

请注意,为了使这个分组功能起作用,Category 必须重写 equalshashCode 方法。

使用相同的实现,我在SubCategory中获得了重复的值。我已经覆盖了Category中一个字段(在我的情况下是id)的equals和hashcode方法。 - DeskToDevelop
经过进一步思考,@DeskToDevelop 发现这段代码无法防止重复的 SubCategory 值。OP 的示例中没有重复的 SubCategories。 - Eran
@DeskToDevelop,你也可以在SubCategory类中重写equals和hashCode方法,并通过将输出更改为Map<Category,Set<SubCategory>>并将toList()更改为toSet()来消除重复项。但是你仍然可能会得到与两个不同的Categories相关联的相同SubCategory。 - Eran

4
您可以通过使用具有合并函数的toMap收集器来完成此操作:
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; }));

这里假设您的Category类已经覆盖了equals和hashcode方法,如下所示:
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);
    }

}

为了更好的可读性,你可以将keyMappervalueMappermerge函数作为以下不同函数提取出来:
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());
}

然后,您可以执行以下操作:

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

在包含 keyMapper, valueMappermerge 方法的类 Main 中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接