使用Stream API将两个集合与谓词组合在一起

时间:2017-12-05 12:10:13

标签: java java-8 java-stream

我有两个类型Entity的集合。

class Entity {
    long id;
    // other fields
}

Collection<Entity> first = ...
Collection<Entity> second = ...

我需要将它们组合在一起,first中的所有元素都应该被second中的元素替换,如果它们的ID相等。

所以,例如first包含[{"id": 1}, {"id": 2}, {"id": 3}]second包含[{"id": 3}, {"id": 4}, {"id": 5}]

结果应如下所示:[{"id": 1}, {"id": 2}, {"id": 3}, {"id": 4}, {"id": 5}] {"id": 3} second{"id": 3}替换 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.10</version> <executions> <execution> <id>unpack</id> <phase>process-resources</phase> <goals> <goal>unpack</goal> </goals> <configuration> <artifactItems> <artifactItem> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-ie-driver</artifactId> <version>3.0.1</version> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin>

有没有办法如何使用Stream API以Java 8方式实现它。

4 个答案:

答案 0 :(得分:2)

虽然可以使用一个流操作来执行整个操作,但由于对集合的重复迭代,结果将不是非常有效。强烈建议在这里使用两个操作

Map<Long,Entity> secondById = second.stream()
    .collect(Collectors.toMap(Entity::getId, Function.identity()));

Collection<Entity> result = first.stream()
    .map(e -> secondById.getOrDefault(e.getId(), e))
    .collect(Collectors.toList());

请注意,我执行了您的任务说明“first中的元素应该替换为second中的元素,如果它们的ID相等”,例如它不会插入第二个没有匹配元素的元素。如果first具有已定义的遭遇订单,则会保留该订单。

请注意,如果first是一个可变列表,您可以就地替换这些元素:

List<Entity> first = ...
Collection<Entity> second = ...

Map<Long,Entity> secondById = second.stream()
    .collect(Collectors.toMap(Entity::getId, Function.identity()));

first.replaceAll(e -> secondById.getOrDefault(e.getId(), e));

将其更改为不只是替换first的元素,而是添加second的所有元素并不是那么难:

Map<Long,Entity> secondById = second.stream()
    .collect(Collectors.toMap(Entity::getId, Function.identity()));

Collection<Entity> result = Stream.concat(
    first.stream().filter(e -> !secondById.containsKey(e.getId())),
    second.stream()
  ).collect(Collectors.toList());

答案 1 :(得分:1)

也许一些老式的,简单的Collection方法可以帮到你:

first.removeAll(second);
first.addAll(second);

请注意,您必须实施hashCodeequals才能将id用作实体类中的参数。

Ideone demo

修改

由于您无法更改equals,因此您可以使用Collection.removeIf复制removeAll的效果:

second.forEach(entity2 -> first.removeIf(entity1 -> entity1.getId() == entity2.getId()));
first.addAll(second);

Ideone demo 2

答案 2 :(得分:1)

color的流式表示:

first.removeAll(second).addAll(second)

这假设Stream<Entity> result = Stream.concat( first.stream().filter(entity -> !second.contains(entity)), second.stream()); 中的相等实体与second的区别在于非id字段值,与相等无关。

答案 3 :(得分:0)

您可以使用Map.merge(..)选择较新的记录。在下面的示例中,它在功能上等同于Map.put(..),但允许您比较其他实体属性,例如创建时间戳或其他。

// Extract Entity::getId as a Map key, and keep the Entity object as value
Map<Long, Entity> result = first.stream()
               .collect(Collectors.toMap(Entity::getId, Function.identity()));

// Merge second collection into the result map, overwriting existing records  
second.forEach(entity -> result.merge(entity.getId(), entity, (oldVal, newVal) -> newVal));

// Print result
result.values().forEach(System.out::println);

如果您只想使用Stream API,那么您可以使用Stream.concat(...)将两个集合连接成一个Stream,然后编写一个自定义收集器

Map<Long, Entity> result2 = Stream.concat(first.stream(), second.stream())
        .collect(LinkedHashMap::new, // the placeholder type
                (map, entity) -> map.merge(entity.getId(), entity, (oldVal, newVal) -> newVal),
                (map1, map2) -> {}); // parallel processing will reorder the concat stream so ignore