[英]Group objects by two properties in using groupBy() in Java 8
我有一个人 class 如下所示:
public class Person {
private int id;
private String name;
private int score;
// constructor, getters, etc.
}
我有一个Persone
对象列表,我想将它们分组到Map
中。
主class:
public class Main {
public static void main(String[] args) {
Person person1 = new Person(1, "John", 4);
Person person2 = new Person(2, "John", 3);
Person person3 = new Person(2, "John", 4);
List<Person> personList = new ArrayList<>();
personList.add(person1);
personList.add(person2);
personList.add(person3);
Map<String, List<Person>> personByName = personList.stream()
.collect(groupingBy(Person::getName));
System.out.println(personByName);
}
}
它给出了结果:
{John=[Person{id=1, name='John', score=4},
Person{id=2, name='John', score=3},
Person{id=2, name='John', score=4}]}
如何按name
分组,但仍然通过将两个人分别保存在id
中来区分两个人?
我希望结果如下所示:
{John=[Person{id=1, name='John', score=4}],
John=[Person{id=2, name='John', score=3}, Person{id=2, name='John', score=4}]}
groupingBy()将List转换为Map<String,List> , key = John,Value = List。
toMap()将List转换为Map<String,Person> , Key = John, value = Person, Map中的key不能重复,所以必须改名字。
Person person1 = new Person(1, "John1", 4);
Person person2 = new Person(2, "John2", 3);
Person person3 = new Person(2, "John3", 4);
List<Person> personList = new ArrayList<>();
personList.add(person1);
personList.add(person2);
personList.add(person3);
Map<String, Person> personByName = personList.stream().collect(Collectors.toMap(Person::getName,Function.identity()));
System.out.println(personByName);
Output:
{John1=Person(id=1, name=John1, score=4), John2=Person(id=2, name=John2, score=3), John3=Person(id=2, name=John3, score=4)}
如何按
name
分组,但仍然通过将两个人分别保存在id
中来区分两个人?
在Map
中本质上不可能有两个相同的密钥。
但是有一些选项可以以这种方式对数据进行分组。
注意: ID 通常应该是唯一的,但在您的示例中它不是,所以我会对此视而不见。 as the second property for grouping, I'll proceed with grouping person objects by ID尽管我们可能会使用作为分组的第二个属性,但我将继续按 ID 对 person 对象进行分组
要按name
然后按id
对人员进行分组,您可以创建一个嵌套的 map Map<String,Map<Integer,List<Person>>>
,它将每个唯一name
与 map 相关联,允许检索具有特定id
的(和显然是同一个名字)。
这就是它的实现方式:
List<Person> personList = new ArrayList<>();
Collections.addAll(personList, // with Java 9+ use List.of() instead
new Person(1, "John", 4),
new Person(2, "John", 3),
new Person(2, "John", 4)
);
Map<String, Map<Integer, List<Person>>> personByNameAndId = personList.stream()
.collect(Collectors.groupingBy(
Person::getName,
Collectors.groupingBy(Person::getId)
));
personByNameAndId.forEach((name, personById) -> {
System.out.println(name);
personById.forEach((id, people) -> people.forEach(p -> System.out.println("id " + id + " -> " + p)));
});
Output:
John
id 1 -> Person{id=1, name='John', score=4}
id 2 -> Person{id=2, name='John', score=3}
id 2 -> Person{id=2, name='John', score=4}
值得一提的是,建议避免使用嵌套的 collections,因为它们很麻烦,如果您在拨号时不够专心,可能会引入错误。 例如,使用映射的 map 时,您可能会在调用get()
时意外交换键的顺序,并且因为Map.get()
需要一个类型为Object
的参数,所以编译代码仍然会
另一种选择是引入一个 object 来保存一个人的name
和id
。 它可以实现为class (这将是 Java 8 的唯一选项):
public class IdName {
private final int id;
private final String name;
public IdName(Person p) {
this(p.getId(), p.getName());
}
// getters, all-args-constructor, equals/hashcode
}
或作为 Java 16记录:
public record IdName(int id, String name) {
public IdName(Person p) {
this(p.getId(), p.getName());
}
}
为了便于将Person
转换为IdName
,您可以在Person
class 中引入需要Person
实例的IdName
构造函数(如上所示),或toIdName()
的便捷方法。
如何使用此自定义 object 对人员进行分组,一个键并从 map 检索与给定人员共享name
和id
的List<Person>
:
Map<IdName, List<Person>> personByNameAndId = personList.stream()
.collect(Collectors.groupingBy(IdName::new));
Person john = new Person(2, "John", 0);
personByNameAndId.get(new IdName(john))
.forEach(System.out::println);
Output:
Person{id=2, name='John', score=3}
Person{id=2, name='John', score=4}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.