Java 8 Stream - 合并两个集合并保持特定字段的唯一性

10

我有两个狗的列表 - dogsList1,dogsList2
我想创建一个包含所有唯一名称字段的狗的单个列表。
(也就是说,如果我遇到了具有相同名称的第二只狗,我不会将其添加到结果列表中)

这是我在Java中能做到的最好的事情,但它收集唯一的名称,而不是狗:
(Dog包含名称以外的其他字段)

// collect all dogs names from first list
List<String> dogNames1 = dogsList1.stream()
     .map(x -> x.getName()).collect(Collectors.toList()); 

dogList2.stream()
     .filter(x->!dogNames1.contains(x.getName()))
     .forEach( x->
            dogsList1.add(x);
     );

它能被改进吗?是否有其他更好的解决方案或优化方法?


如果你担心在大于 100,000 元素列表的情况下会影响性能,有一些方法可以解决。否则我建议保持现状。 - Mark Jeronimus
2
当两只狗有相同的名字时,你是随机选择一只还是选择第一只? - marstran
@marstran 例如,假设默认值将从第一个列表中获取。 - ran632
@Pshemo 狗对象列表 - ran632
2个回答

17
您可以使用合并多个流并删除重复项。
对于给定名称的第一只狗,您可以执行
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class A {

    public static void main(String[] args) {
        List<Dog> dogList1 = Arrays.asList(new Dog("a", 1), new Dog("b", 2), new Dog("f", 3));
        List<Dog> dogList2 = Arrays.asList(new Dog("b", 4), new Dog("c", 5), new Dog("f", 6));
        List<Dog> dogList3 = Arrays.asList(new Dog("b", 7), new Dog("d", 8), new Dog("e", 9));
        List<Dog> dogs = new ArrayList<>(
                Stream.of(dogList1, dogList2, dogList3)
                        .flatMap(List::stream)
                        .collect(Collectors.toMap(Dog::getName,
                                d -> d,
                                (Dog x, Dog y) -> x == null ? y : x))
                        .values());
        dogs.forEach(System.out::println);
    }
}

class Dog {
    String name;
    int id;

    public Dog(String name, int id) {
        this.name = name;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }
}

打印

Dog{name='a', id=1}
Dog{name='b', id=2}
Dog{name='c', id=5}
Dog{name='d', id=8}
Dog{name='e', id=9}
Dog{name='f', id=3}

在每种情况下,您都可以看到第一个名称实例被保留。

对于唯一的名称

Set<String> names = Stream.of(dogList1, dogList2, dogList3)
                          .flatMap(List::stream)
                          .map(Dog::getName)
                          .collect(Collectors.toSet());

1
如果OP确实想要你在两个示例中展示的内容,那么这是一个重复。然而,如果不是这样,那么你的实现仍然不完全正确,因为如果你看一下OP的当前代码,他们并没有通过属性删除所有重复项,而是防止将重复项添加到累加器中,所以dogNames1可能包含任意数量的重复项,但关键点在于_如果第二个列表中的对象已经存在于第一个列表中,则不要添加该对象_,而你的代码似乎没有考虑到这一点。 - Ousmane D.
@Aominè,我理解你的观点,第一个列表中的重复似乎是允许的,但我不明白你所说的粗体语句的含义。你能举个例子吗? - Peter Lawrey
@Aominè,这可能是一个重复的问题,但我并不确定它是否是。 - Peter Lawrey
1
如果且仅当 OP 表示第一个列表中允许重复项(我相信这是他想要的)时,这可能实际上不是重复。 我的粗体声明可能不太清楚,但它只是意味着“第一个列表中允许重复项”,而您的示例似乎没有考虑到这一点。 - Ousmane D.

7
那可能是一个选择。
  List<String> dogNames = Stream.concat(dogsList1.stream(),dogsList2.stream())
                          .map(x -> x.getName())
                          .distinct()
                          .collect(Collectors.toList());

不幸的是,OP想要一个List<Dog>而不是List<String> - Ousmane D.
没错,是我的错。在这种情况下,在Dog类中实现equal和hashcode就足够了吗? - Frablamo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接