0

假设我们有一个带有字段的 person 类:

Class Person {
  private String name;
  private Integer id (this one is unique);
}

然后我们有一个List<Person> people这样的:

['Jerry', 993]
['Tom', 3]
['Neal', 443]
['Jerry', 112]
['Shannon', 259]
['Shannon', 533]

如何创建一个新的List<Person> uniqueNames,以便它仅过滤唯一名称并保留该名称的最高 ID。

所以最终列表看起来像:

['Jerry', 993]
['Tom', 3]
['Neal', 443]
['Shannon', 533]
4

4 回答 4

5

Collectors.groupingBy+Collectors.maxBy应该可以构建按名称分组的人员地图,然后选择最大值:

List<Person> persons = Arrays.asList(
    new Person("Jerry", 123),
    new Person("Tom", 234),
    new Person("Jerry", 456),
    new Person("Jake", 789)
);

List<Person> maxById = persons
    .stream()
    .collect(Collectors.groupingBy(
        Person::getName, 
        Collectors.maxBy(Comparator.comparingInt(Person::getID))
    ))
    .values() // Collection<Optional<Person>>
    .stream() // Stream<Optional<Person>>
    .map(opt -> opt.orElse(null))
    .collect(Collectors.toList());

System.out.println(maxById);

输出:

[789: Jake, 234: Tom, 456: Jerry]

更新

有没有办法获得一个单独的 Person 对象列表,因为它们在这个 stream() 中是重复的而被删除?

最好将分组项目收集在一个列表中,然后在一些包装类中进行转换,以提供有关maxById人员和重复数据删除人员列表的信息:

class PersonList {
    private final Person max;
    private final List<Person> deduped;
    
    public PersonList(List<Person> group) {
        this.max = Collections.max(group, Comparator.comparingInt(Person::getID));
        this.deduped = new ArrayList<>(group);
        this.deduped.removeIf(p -> p.getID() == max.getID());
    }
    
    @Override
    public String toString() {
        return "{max: " + max + "; deduped: " + deduped + "}";
    }
}

然后应该像这样收集人员:

List<PersonList> maxByIdDetails = new ArrayList<>(persons
    .stream()
    .collect(Collectors.groupingBy(
        Person::getName, 
        LinkedHashMap::new,
        Collectors.collectingAndThen(
            Collectors.toList(), PersonList::new
        )
    ))
    .values()); // Collection<PersonList>

maxByIdDetails.forEach(System.out::println);

输出:

{max: 456: Jerry; deduped: [123: Jerry]}
{max: 234: Tom; deduped: []}
{max: 789: Jake; deduped: []}

更新 2

获取重复人员列表:

List<Person> duplicates = persons
    .stream()
    .collect(Collectors.groupingBy(Person::getName))
    .values() // Collection<List<Person>>
    .stream() // Stream<List<Person>>
    .map(MyClass::removeMax)
    .flatMap(List::stream) // Stream<Person>
    .collect(Collectors.toList()); // List<Person>

System.out.println(duplicates);

输出:

[123: Jerry]

whereremoveMax可以这样实现:

private static List<Person> removeMax(List<Person> group) {
    List<Person> dupes = new ArrayList<>();
    Person max = null;

    for (Person p : group) {
        Person duped = null;
        if (null == max) {
            max = p;
        } else if (p.getID() > max.getID()) {
            duped = max;
            max = p;
        } else {
            duped = p;
        }
        if (null != duped) {
            dupes.add(duped);
        }
    }
    return dupes;
}

或者,如果hashCodeequals在 class 中正确实现Person,则可以使用以下方法计算两个列表之间的差异removeAll

List<Person> duplicates2 = new ArrayList<>(persons);
duplicates2.removeAll(maxById);
System.out.println(duplicates2);
于 2021-10-14T21:24:18.277 回答
3

你可以Collectors#toMap这样使用。

record Person(String name, Integer id) {}

public static void main(String[] args) {
    List<Person> input = List.of(
        new Person("Jerry", 993),
        new Person("Tom", 3),
        new Person("Neal", 443),
        new Person("Jerry", 112),
        new Person("Shannon", 259),
        new Person("Shannon", 533));

    List<Person> output = input.stream()
        .collect(Collectors.toMap(Person::name, Function.identity(),
            (a, b) -> a.id() > b.id() ? a : b, LinkedHashMap::new))
        .values().stream().toList();

    for (Person e : output)
        System.out.println(e);
}

输出:

Person[name=Jerry, id=993]
Person[name=Tom, id=3]
Person[name=Neal, id=443]
Person[name=Shannon, id=533]

, LinkedHashMap::new如果您不关心订单,则可以省略。

于 2021-10-14T21:47:15.057 回答
2

你可以试试:

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

persons.stream()
       .collect(
          groupingBy(
            Person::getName, 
            collectingAndThen(
              maxBy(comparingInt(Person::getId)), 
              Optional::get
            )
          )
       )
       .values()
       ;
  • 您按名称分组
  • 然后你请求分组人员的最大值(每个名字)
  • 然后返回值(因为groupingBy返回 a Map<String, Optional<Person>>collectAndThen调用的Optional::get)。

请注意,这将列出唯一名称,但不会列出重复名称。

于 2021-10-14T21:26:36.133 回答
0

有没有办法获得一个单独的 Person 对象列表,因为它们在这个 stream() 中是重复的而被删除?

private static final Map<String, Person> highestIds = new HashMap<>();
private static final List<Person> duplicates = new ArrayList<>();

public static void main(String[] args) {
    for (Person person : people) {
        Person result = highestIds.get(person.name);
        if (isPresent(result) && person.id > result.id) {
            duplicates.add(result);
            highestIds.put(person.name, person);
        } else if (result == null) {
            highestIds.put(person.name, person);
        } else {
            duplicates.add(person);
        }
    }
    System.out.println("Highest ids:");
    highestIds.values().forEach(System.out::println);
    System.out.println("Duplicates:");
    duplicates.forEach(System.out::println);
}

private static boolean isPresent(Person result) {
    return result != null;
}
于 2021-10-14T22:16:02.147 回答