Java-Stream - 使用 Collector groupingBy 创建聚合对象列表

3
我有一个域类 View:
public class View {
    private String id;
    private String docId;
    private String name;
    
    // constructor, getters, etc.
}

这里有一个 View 对象列表。

具有相同 id 的元素,只在一个字段 docId(第二个属性)上有所不同,例如:

List<View> viewList = new ArrayList<>();
viewList.add(new View("1234", "ab123", "john"));
viewList.add(new View("1234", "cd456", "john"));
viewList.add(new View("1234", "ef789", "john"));
viewList.add(new View("5678", "jh987", "jack"));
viewList.add(new View("5678", "ij654", "jack"));
viewList.add(new View("5678", "kl321", "jack"));
viewList.add(new View("9876", "mn123", "ben"));
viewList.add(new View("9876", "op456", "ben"));
}

A和我想将它们转换为聚合对象列表NewViewNewView类的样子是这样的:
public static class NewView {
    private String id;
    private String name;
    private List<String> docId = new ArrayList<>();
}

提供的样本数据的期望输出如下:
{
  "id": "1234",
  "name": "john",
  "docIds": ["ab123", "cd456", "ef789"]
},
{
  "id": "5678",
  "name": "jack",
  "docIds": ["jh987", "ij654", "kl321"]
},
{
  "id": "9876",
  "name": "ben",
  "docIds": ["mn123", "op456"]
}

我尝试了类似这样的东西:
Map<String, List<String>> docIdsById = viewList.stream()
    .collect(groupingBy(
        View::getId,
        Collectors.mapping(View::getDocId, Collectors.toList())
    ));

Map<String, List<View>> views = viewList.stream()
    .collect(groupingBy(View::getId));

List<NewView> newViewList = new ArrayList<>();

for (Map.Entry<String, List<View>> stringListEntry : views.entrySet()) {
    View view = stringListEntry.getValue().get(0);
    newViewList.add(new NewView(
            view.getId(),
            view.getName(),
            docIdsById.get(stringListEntry.getKey()))
    );
}

我可以在一个流中创建一个NewView列表吗?
3个回答

1

可以通过单个流语句完成。

为此,我们可以通过静态方法Collector.of()定义一个自定义收集器,该收集器将用作groupingBy()的下游,以对具有相同idView实例执行可变减少操作(因此映射到相同的键)。

还需要创建一种自定义累积类型,该类型将用作可变减少的手段,并最终转换为NewView

请注意,如果NewView是可变的(我会做出安全的假设,即它不是),则NewView也可以用作累积类型,可以为此目的创建一个单独的类。

这就是生成结果列表的流可能看起来像的样子:

List<View> viewList = // initialing the list
    
List<NewView> newViewList = viewList.stream()
    .collect(Collectors.groupingBy(
        View::getId,
        Collector.of(
            ViewMerger::new,
            ViewMerger::accept,
            ViewMerger::merge,
            ViewMerger::toNewView
        )
    ))
    .values().stream().toList();

以下是累加器可能看起来的样子。 为了方便起见,我已经实现了Consumer接口的合同:

public static class ViewMerger implements Consumer<View> {
    private String id;
    private String name;
    private List<String> docIds = new ArrayList<>();
    
    // no args-constructor

    @Override
    public void accept(View view) {
        if (id == null) id = view.getId();
        if (name == null) name = view.getName();
        
        docIds.add(view.getDocId());
    }
    
    public ViewMerger merge(ViewMerger other) {
        this.docIds.addAll(other.docIds);
        return this;
    }
    
    public NewView toNewView() {
        return new NewView(id, name, docIds);
    }
}

1

使用多个字段进行分组

您可以尝试使用groupingBy方法来使用多个字段进行分组。

在这里,

我已经按idname进行了分组,然后迭代它以准备一个NewView Objects列表,如下所示:

List<NewView> list = new ArrayList<>();

    viewList.stream()
                .collect(Collectors.groupingBy(View::getId,
                         Collectors.groupingBy(View::getName,
                  Collectors.mapping(View::getDocId,Collectors.toList()))))
                            .forEach((k,v) -> 
                       list.add(new NewView(k, (String) v.keySet().toArray()[0], 
                              (List<String>) v.values().toArray()[0])));

        System.out.println(list);

输出::

 [NewView{id='9876', name='ben', docIds=[mn123, op456]}, 
  NewView{id='1234', name='john', docIds=[ab123, cd456, ef789]},
  NewView{id='5678', name='jack', docIds=[jh987, ij654, kl321]}]

0

你可以使用流(Stream)完成它,但可能需要进行一些内部迭代或其他流(Stream)操作,或者需要编写自定义收集器(Custom Collector)。我向您保证,这并不像下面这么容易:

在此示例中,我使用记录(Records)来实现演示。它们的行为类似于具有自动生成getter方法的不可变类(Immutable Class)。我修改了NewViewtoString()方法以供显示。

record View(String getId, String getDocId, String getName) {
}

record NewView(String getId, String getName, List<String> getDocIds) {
    @Override
    public String toString() {
        return getName + ", " + getId + ", " + getDocIds;
    }
}

该过程创建了一个Map来保存具有通用键的NewView类的结果。我选择了名称,但Id也可以使用。如果现有的key不存在,则会创建一个新的NewValue实例。然后返回该实例,并检索列表并将关联的docId添加到列表中。
List<View> viewList = new ArrayList<>();
viewList.add(new View("1234", "ab123", "john"));
viewList.add(new View("1234", "cd456", "john"));
viewList.add(new View("1234", "ef789", "john"));
viewList.add(new View("5678", "jh987", "jack"));
viewList.add(new View("5678", "ij654", "jack"));
viewList.add(new View("5678", "kl321", "jack"));
viewList.add(new View("9876", "mn123", "ben"));
viewList.add(new View("9876", "op456", "ben"));

Map<String, NewView> results = new HashMap<>();

for (View view : viewList) {
    results.computeIfAbsent(view.getName(),
        v -> new NewView(view.getId(), view.getName(),
            new ArrayList<>()))
        .getDocIds().add(view.getDocId());
}

值被存储在一个 Collection 中,可以像这样进行迭代并打印。
for (NewView v : results.values()) {
    System.out.println(v);
}

打印

ben, 9876, [mn123, op456]
john, 1234, [ab123, cd456, ef789]
jack, 5678, [jh987, ij654, kl321]

要进行索引检索,您需要将它们添加到List<NewView>

List<NewView> newViewList = new ArrayList<>(results.values());
System.out.println(newViewList.get(1));

打印

john, 1234, [ab123, cd456, ef789]

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