Java:使用流API查找嵌套列表中的公共项

Java:使用流API查找嵌套列表中的公共项,java,java-8,java-stream,Java,Java 8,Java Stream,假设我有一张动物名单。此嵌套列表表示一个位置列表,其中每个位置都包含一个动物列表 我需要找出至少出现在两个不同地方的动物种类列表。我知道我可以做正常的循环,也可以做到这一点。是否有任何方法可以通过流API实现这一点 例如: List<List<Animal>> animals = new ArrayList<>(); animals.add(Arrays.asList(new Dog(), new Cat())); animals.add(Arrays.asL

假设我有一张动物名单。此嵌套列表表示一个位置列表,其中每个位置都包含一个动物列表

我需要找出至少出现在两个不同地方的动物种类列表。我知道我可以做正常的循环,也可以做到这一点。是否有任何方法可以通过流API实现这一点

例如:

List<List<Animal>> animals = new ArrayList<>();
animals.add(Arrays.asList(new Dog(), new Cat()));
animals.add(Arrays.asList(new Dog(), new Bird()));
animals.add(Arrays.asList(new Bird()));
使现代化 在没有流API的情况下执行此操作的代码:

final List<List<Animal>> animals = new ArrayList<>();
animals.add(Arrays.asList(new Dog(), new Cat()));
animals.add(Arrays.asList(new Dog(), new Bird()));
animals.add(Arrays.asList(new Bird()));

final Map<Class<? extends Animal>, Integer> count = new HashMap<>();

for (final List<Animal> place : animals) {
    final Set<Class<? extends Animal>> uniqueTypes = new HashSet<>();

    for (final Animal animal : place) {
        uniqueTypes.add(animal.getClass());
    }

    for (final Class<? extends Animal> type : uniqueTypes) {
        if (!count.containsKey(type))
        {
            count.put(type, 1);
        }
        else
        {
            count.put(type, count.get(type).intValue() + 1);
        }
    }
}

final List<Class<? extends Animal>> typesAppearingAtLeastAtTwoPlaces = new ArrayList<>();

for (final Class<? extends Animal> type : count.keySet()) {
    if (count.get(type).intValue() >= 2) {
        typesAppearingAtLeastAtTwoPlaces.add(type);
    }
}

System.out.println(typesAppearingAtLeastAtTwoPlaces);

首先,数一数所有动物,然后选择出现多次的动物:

import static java.util.stream.Collectors.*;
.....

Map<Class<? extends Animal>, Long> animalCounts = animals.stream()
        .flatMap(
                lst -> lst.stream()
                    .map(a -> a.getClass())
                    .distinct()   // in case several of the same animal are in the same place
        )
        .collect(groupingBy(x -> x, counting()));

List<Class<? extends Animal>> animalTypes = animalCounts.entrySet().stream()
        .filter(e -> e.getValue() > 1)
        .map(Map.Entry::getKey)
        .collect(toList());

首先,数一数所有动物,然后选择出现多次的动物:

import static java.util.stream.Collectors.*;
.....

Map<Class<? extends Animal>, Long> animalCounts = animals.stream()
        .flatMap(
                lst -> lst.stream()
                    .map(a -> a.getClass())
                    .distinct()   // in case several of the same animal are in the same place
        )
        .collect(groupingBy(x -> x, counting()));

List<Class<? extends Animal>> animalTypes = animalCounts.entrySet().stream()
        .filter(e -> e.getValue() > 1)
        .map(Map.Entry::getKey)
        .collect(toList());

首先也是最重要的一点是,在您的尝试中,可能应该使用flatMap而不是map

animals.stream.mapplace->place.stream.mapanimal->animal.getClass.collectCollectors.toSet

第二,实际上我们可以使用外部ConcurrentHashMap来实现这一点,这将使我们能够在需要时使用parallel

    ConcurrentHashMap<Class, AtomicLong> theCounterMap = new ConcurrentHashMap<>();
    animals.stream().flatMap(list -> list.stream().map(animal -> animal.getClass()).distinct())
        .forEach(clazz -> theCounterMap.computeIfAbsent(clazz, k -> new AtomicLong()).getAndIncrement());
    List<Class> classList = theCounterMap.entrySet().stream()
            .filter(entry -> entry.getValue().get() > 1)
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());
但是如果您需要跟踪源列表作为两个不同的位置,那么您需要进一步修改上面的解决方案

更新 根据@shmosel的建议,您可以直接使用一种更简单的方法来实现以下目的:

    Map<Class, Long> theCounterMap = animals.stream().flatMap(list -> list.stream().map(animal -> animal.getClass()).distinct())
        .collect(Collectors.groupingBy(e -> e, Collectors.counting()));
    List<Class> classList = theCounterMap.entrySet().stream()
            .filter(entry -> entry.getValue() > 1)
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());

首先也是最重要的一点是,在您的尝试中,可能应该使用flatMap而不是map

animals.stream.mapplace->place.stream.mapanimal->animal.getClass.collectCollectors.toSet

第二,实际上我们可以使用外部ConcurrentHashMap来实现这一点,这将使我们能够在需要时使用parallel

    ConcurrentHashMap<Class, AtomicLong> theCounterMap = new ConcurrentHashMap<>();
    animals.stream().flatMap(list -> list.stream().map(animal -> animal.getClass()).distinct())
        .forEach(clazz -> theCounterMap.computeIfAbsent(clazz, k -> new AtomicLong()).getAndIncrement());
    List<Class> classList = theCounterMap.entrySet().stream()
            .filter(entry -> entry.getValue().get() > 1)
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());
但是如果您需要跟踪源列表作为两个不同的位置,那么您需要进一步修改上面的解决方案

更新 根据@shmosel的建议,您可以直接使用一种更简单的方法来实现以下目的:

    Map<Class, Long> theCounterMap = animals.stream().flatMap(list -> list.stream().map(animal -> animal.getClass()).distinct())
        .collect(Collectors.groupingBy(e -> e, Collectors.counting()));
    List<Class> classList = theCounterMap.entrySet().stream()
            .filter(entry -> entry.getValue() > 1)
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());
我想你也可以试试。它使您有机会编写更简洁、可读性更好的代码:

StreamEx.of(animals)
    .flatMap(e -> e.stream().map(Animal::getClass).distinct())
    .distinct(2).toList();
我想你也可以试试。它使您有机会编写更简洁、可读性更好的代码:

StreamEx.of(animals)
    .flatMap(e -> e.stream().map(Animal::getClass).distinct())
    .distinct(2).toList();

为什么要修改外部映射而不是使用收集器?你为什么不使用普通的Long?@shmosel使计数器更便于携带,因为我并不确切知道OP的用途——只是两次或更多。作为另一种方式,Long是可以的,但我个人更喜欢AtomicLong。什么计数器?如何携带?两次什么?你还在不必要地使用forEach。Just.collectCollectors.toList.现在它基本上是@Misha答案的复制品:为什么要修改外部地图而不是使用收集器?你为什么不使用普通的Long?@shmosel使计数器更便于携带,因为我并不确切知道OP的用途——只是两次或更多。作为另一种方式,Long是可以的,但我个人更喜欢AtomicLong。什么计数器?如何携带?两次什么?你还在不必要地使用forEach。Just.collectCollectors.toList.现在它基本上是@Misha答案的副本:@Radiodef Updated.@Radiodef Updated.谢谢。看起来我需要看看类中的所有这些方法。谢谢。看起来我需要查看Collectors类中的所有方法。您需要使用Animal::getClass来获取类您需要使用Animal::getClass来获取类