繁体   English   中英

Java 流收集到 Map <String, Map<Integer, MyObject> &gt;

[英]Java stream collect to Map<String, Map<Integer, MyObject>>

我正在使用 Java 11,并且我有一个名为myListList<MyObject>以下对象:

public class MyObject {
  private final String personalId;
  private final Integer rowNumber;
  private final String description;
<...>
}

我想使用流将这些对象收集到Map<String, Map<Integer, List<MyObject>>> (使用以下语法: Map<personalId, Map<rowNumber, List<MyObject>>> ),我不想要使用Collectors.groupBy() ,因为它存在null值问题。

我尝试使用Collectors.toMap()来做到这一点,但似乎不可能做到这一点

myList
.stream()
.Collectors.toMap(s -> s.getPersonalId(), s -> Collectors.toMap(t-> s.getRowNumber(), ArrayList::new))

我的问题是可以在不使用 Collectors.groupBy()的情况下使用流制作Map<String, Map<Integer, List<MyObject>>>对象,还是我应该自己编写一个完整的方法?

在您的情况下,我将首先创建地图,然后遍历此列表中的元素,如下所示:

Map<String, List<MyObject>> rows = new HashMap<>(); 
list.forEach(element -> rows.computeIfAbsent(element.personalId, s -> new ArrayList<>()).add(element));

您可以使用computeIfAbsent来创建一个新的列表/地图作为地图的值,然后才能放入数据。


您创建的第二种数据类型也是如此:

Map<String, Map<Integer, MyObject>> persons = new HashMap<>();
list.forEach(element -> persons.computeIfAbsent(element.personalId, s -> new HashMap<>()).put(element.rowNumber, element));

这是一种使用流解决此问题的方法。 但请注意,对象必须具有唯一的personId / rowNumber

Map<String, List<MyObject>> rows = list.stream().collect(
        Collectors.toMap(element -> element.personalId, 
        element -> new ArrayList<MyObject>(Arrays.asList(element))));

以及其他地图:

    Map<String, Map<Integer, MyObject>> persons = list.stream().collect(
            Collectors.toMap(e -> e.personalId, 
            e -> new HashMap<>(Map.of(e.rowNumber, e))));

Map<String, Map<Integer, List<MyObject>>>对象使用流而不使用Collectors.groupingBy()

通过查看地图类型,我可以假设personalIdrowNumber的组合不是唯一的,即每个组合可能多次出现(否则您不需要将对象分组到列表中)。 并且每个 personId 可能有不同personalId rowNumber关联。 只有当这些结论正确时,这个嵌套集合才可能有一个非常模糊的存在理由。

否则,您可能可以将它替换为不同用例的多个集合,例如Map<String, MyObject> - id的对象(如果每个 id 都是唯一的):

Map<String, MyObject> objById = myList.stream()
    .collect(Collectors.toMap(
        MyObject::getPersonalId,
        Function.identity()
    ));

我将继续假设您确实需要这样的嵌套集合。

现在,让我们解决groupingBy()的问题。 此收集器在内部使用Objects.requireNonNull()来确保分类器函数生成的是非空的

如果您尝试使用但由于对null-keys personalId敌意而失败,这意味着 personId 或rowNumber或两者都可以为null

现在让我们绕个小弯,提出一个问题,如果一个属性被认为是重要的(您将使用personalIdrowNumber来访问数据,因此它们绝对重要)和null首先意味着什么?

null表示没有数据,没有别的。 如果在您的应用程序null具有额外的特殊含义,那就是设计缺陷。 如果出于某种原因对管理数据很重要的属性显示为null ,则需要修复它。

您可能会声称您对null值非常满意。 如果是这样,让我们​​暂停片刻,想象一个情况:一个人进入一家餐馆,点了一份汤,并要求服务员给他们拿叉子而不是勺子(因为他们对勺子有负面体验,而且他们对叉子)。

null不是数据,它是数据缺失的指标,存储null是一种反模式。 如果您存储null它会获得特殊含义,因为您被迫单独对待它。

要将等于nullpersonalIdrowNumber替换为默认值,我们只需要一行代码。

public static void replaceNullWithDefault(List<MyObject> list,
                                          String defaultId,
                                          Integer defaultNum) {
    
    list.replaceAll(obj -> obj.getPersonalId() != null && obj.getRowNumber() != null ? obj :
        new MyObject(Objects.requireNonNullElse(obj.getPersonalId(), defaultId),
            Objects.requireNonNullElse(obj.getRowNumber(), defaultNum),
            obj.getDescription()));
}

之后可以使用适当的工具而不是用叉子吃汤,我的意思是我们可以使用groupingBy()处理列表数据:

public static void main(String[] args) {
    List<MyObject> myList = new ArrayList<>(
        List.of(
            new MyObject("id1", 1, "desc1"),
            new MyObject("id1", 1, "desc2"),
            new MyObject("id1", 2, "desc3"),
            new MyObject("id1", 2, "desc4"),
            new MyObject("id2", 1, "desc5"),
            new MyObject("id2", 1, "desc6"),
            new MyObject("id2", 1, "desc7"),
            new MyObject(null, null, "desc8")
        ));
    
    replaceNullWithDefault(myList, "id0", 0); // replacing null values
    
    Map<String, Map<Integer, List<MyObject>>> byIdAndRow = myList // generating a map
        .stream()
        .collect(Collectors.groupingBy(
            MyObject::getPersonalId,
            Collectors.groupingBy(MyObject::getRowNumber)
        ));
    
    byIdAndRow.forEach((k, v) -> { // printing the map
        System.out.println(k);
        v.forEach((k1, v1) -> System.out.println(k1 + " -> " + v1));
    });
}

输出:

id0
0 -> [MyObject{'id0', 0, 'desc8'}]
id2
1 -> [MyObject{'id2', 1, 'desc5'}, MyObject{'id2', 1, 'desc6'}, MyObject{'id2', 1, 'desc7'}]
id1
1 -> [MyObject{'id1', 1, 'desc1'}, MyObject{'id1', 1, 'desc2'}]
2 -> [MyObject{'id1', 2, 'desc3'}, MyObject{'id1', 2, 'desc4'}]

在线演示的链接

现在,请注意groupingBy()的用法,您是否注意到它的简洁性。 这是一个正确的工具,它甚至可以生成如此笨拙的嵌套地图。

现在我们要用叉子吃汤! 所有null属性都将按原样使用:

public static void main(String[] args) {
    List<MyObject> myList = new ArrayList<>(
        List.of(
            new MyObject("id1", 1, "desc1"),
            new MyObject("id1", 1, "desc2"),
            new MyObject("id1", 2, "desc3"),
            new MyObject("id1", 2, "desc4"),
            new MyObject("id2", 1, "desc5"),
            new MyObject("id2", 1, "desc6"),
            new MyObject("id2", 1, "desc7"),
            new MyObject(null, null, "desc8")
        ));
    
    Map<String, Map<Integer, List<MyObject>>> byIdAndRow = myList // generating a map
        .stream()
        .collect(
            HashMap::new,
            (Map<String, Map<Integer, List<MyObject>>> mapMap, MyObject next) ->
                mapMap.computeIfAbsent(next.getPersonalId(), k -> new HashMap<>())
                    .computeIfAbsent(next.getRowNumber(), k -> new ArrayList<>())
                    .add(next),
            (left, right) -> right.forEach((k, v) -> left.merge(k, v,
                (oldV, newV) -> {
                    newV.forEach((k1, v1) -> oldV.merge(k1, v1,
                        (listOld, listNew) -> {
                            listOld.addAll(listNew);
                            return listOld;
                        }));
                    return oldV;
                }))
        );
    
    byIdAndRow.forEach((k, v) -> { // printing the map
        System.out.println(k);
        v.forEach((k1, v1) -> System.out.println(k1 + " -> " + v1));
    });
}

输出:

null
null -> [MyObject{'null', null, 'desc8'}]
id2
1 -> [MyObject{'id2', 1, 'desc5'}, MyObject{'id2', 1, 'desc6'}, MyObject{'id2', 1, 'desc7'}]
id1
1 -> [MyObject{'id1', 1, 'desc1'}, MyObject{'id1', 1, 'desc2'}]
2 -> [MyObject{'id1', 2, 'desc3'}, MyObject{'id1', 2, 'desc4'}]

在线演示的链接

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM