[英]Java stream collect to Map<String, Map<Integer, MyObject>>
我正在使用 Java 11,并且我有一个名为myList
的List<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()
通过查看地图类型,我可以假设personalId
和rowNumber
的组合不是唯一的,即每个组合可能多次出现(否则您不需要将对象分组到列表中)。 并且每个 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
。
现在让我们绕个小弯,提出一个问题,如果一个属性被认为是重要的(您将使用personalId
和rowNumber
来访问数据,因此它们绝对重要)和null
首先意味着什么?
null
表示没有数据,没有别的。 如果在您的应用程序null
具有额外的特殊含义,那就是设计缺陷。 如果出于某种原因对管理数据很重要的属性显示为null
,则需要修复它。
您可能会声称您对null
值非常满意。 如果是这样,让我们暂停片刻,想象一个情况:一个人进入一家餐馆,点了一份汤,并要求服务员给他们拿叉子而不是勺子(因为他们对勺子有负面体验,而且他们对叉子)。
null
不是数据,它是数据缺失的指标,存储null
是一种反模式。 如果您存储null
它会获得特殊含义,因为您被迫单独对待它。
要将等于null
的personalId
和rowNumber
替换为默认值,我们只需要一行代码。
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.