优雅地组合两个列表的元素,使它们在某个属性值中是唯一的?

时间:2018-04-20 15:04:53

标签: java java-8

假设我有这个Java 8代码:

public class Foo {
    private long id;
    public getId() {
        return id;
    }

    //--snip--
}


//Somewhere else...

List<Foo> listA = getListA();
List<Foo> listB = getListB();

List<Foo> uniqueFoos = ???;

List<Foo> uniqueFoos我想添加listAlistB的所有元素,以便所有Foo都有唯一的ID。即如果Foo中已有uniqueFoos具有特定ID,则不要添加具有相同ID的其他Foo,而是跳过它。

当然有一个简单的旧迭代,但我认为应该有更优雅的东西(可能涉及流,但不是强制性的),但我无法弄明白......

我可以想出一个好的解决方案,涉及覆盖equals()方法基本上return id == other.id;并使用Setdistinct()。不幸的是,我无法覆盖equals()因为对象相等不能改变。

实现这一目标的有效方法是什么?

3 个答案:

答案 0 :(得分:6)

您可以使用Collectors.toMap

执行此操作
Collection<Foo> uniqueFoos = Stream.concat(listA.stream(), listB.stream())
    .collect(Collectors.toMap(
        Foo::getId,
        f -> f,
        (oldFoo, newFoo) -> oldFoo))
    .values();

如果您需要List而不是Collection,请执行以下操作:

List<Foo> listUniqueFoos = new ArrayList<>(uniqueFoos);

如果您还需要保留元素的遭遇顺序,则可以使用接受Supplier的重载版Collectors.toMap作为返回的地图:

Collection<Foo> uniqueFoos = Stream.concat(listA.stream(), listB.stream())
    .collect(Collectors.toMap(
        Foo::getId,
        f -> f,
        (oldFoo, newFoo) -> oldFoo,
        LinkedHashMap::new))
    .values();

我认为值得添加非流式变体:

Map<Long, Foo> map = new LinkedHashMap<>();
listA.forEach(f -> map.merge(g.getId(), f, (oldFoo, newFoo) -> oldFoo));
listB.forEach(f -> map.merge(g.getId(), f, (oldFoo, newFoo) -> oldFoo));

Collection<Foo> uniqueFoos = map.values();

这可以重构为通用方法,不重复代码:

static <T, K> Collection<T> uniqueBy(Function<T, K> groupBy, List<T>... lists) {
    Map<K, T> map = new LinkedHashMap<>();
    for (List<T> l : lists) {
        l.forEach(e -> map.merge(groupBy.apply(e), e, (o, n) -> o));
    }
    return map.values();
}

您可以使用以下内容:

Collection<Foo> uniqueFoos = uniqueBy(Foo::getId, listA, listB);

此方法使用Map.merge方法。

答案 1 :(得分:2)

这样的事情会做。

 List<Foo> uniqueFoos = Stream.concat(listA.stream(), listB.stream())
                              .filter(distinctByKey(Foo::getId))
                              .collect(Collectors.toList());


 public <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Set<Object> seen = ConcurrentHashMap.newKeySet();
    return t -> seen.add(keyExtractor.apply(t));
  }

答案 2 :(得分:1)

你可以写这个。由于filter()和使用存储遇到的ID的Set,跳过具有相同id的第二个和下一个元素:

    Set<Long> ids = new HashSet<>();
    List<Foo> uniqueFoos = Stream.concat(getListA().stream(), getListB().stream())
                                 .filter(f -> ids.add(f.getId()))
                                 .collect(Collectors.toList());

它不是一个完整的流解决方案,但它是相当直接和可读的。