在大多数情况下,当您发现自己在 Stream 上使用 forEach 时,您应该重新考虑是否为您的工作使用了正确的工具,或者您是否以正确的方式使用它。
通常,您应该寻找合适的终端操作来完成您想要实现的目标,或者寻找合适的收集器。现在,有用于生成 Maps 和 Lists 的收集器,但没有用于基于谓词组合两个不同收集器的开箱即用收集器。
现在,this answer 包含一个用于组合两个收集器的收集器。使用此收集器,您可以完成任务
Pair<Map<KeyType, Animal>, List<KeyType>> pair = animalMap.entrySet().stream()
.collect(conditional(entry -> entry.getValue() != null,
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue),
Collectors.mapping(Map.Entry::getKey, Collectors.toList()) ));
Map<KeyType,Animal> myMap = pair.a;
List<KeyType> myList = pair.b;
但也许,您可以以更简单的方式解决此特定任务。其中一个结果与输入类型匹配;这是同一张地图,只是去掉了映射到null 的条目。如果您的原始地图是可变的并且您之后不需要它,您可以只收集列表并从原始地图中删除这些键,因为它们是互斥的:
List<KeyType> myList=animalMap.entrySet().stream()
.filter(pair -> pair.getValue() == null)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
animalMap.keySet().removeAll(myList);
请注意,即使没有其他键的列表,您也可以删除到 null 的映射:
animalMap.values().removeIf(Objects::isNull);
或
animalMap.values().removeAll(Collections.singleton(null));
如果您不能(或不想)修改原始地图,仍然有没有自定义收集器的解决方案。正如Alexis C.’s answer 中所暗示的,partitioningBy 正朝着正确的方向前进,但您可以简化它:
Map<Boolean,Map<KeyType,Animal>> tmp = animalMap.entrySet().stream()
.collect(Collectors.partitioningBy(pair -> pair.getValue() != null,
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
Map<KeyType,Animal> myMap = tmp.get(true);
List<KeyType> myList = new ArrayList<>(tmp.get(false).keySet());
底线是,不要忘记普通的 Collection 操作,您不必使用新的 Stream API 做所有事情。