我是Java 8的新手,需要解决以下问题。
我有两个课程如下:
class Person {
String name;
int age;
List<Address> address;
}
class Address {
String street;
String city;
String country;
}
现在我有一个来自数据库的列表:
List<Person> findPerson;
adam
26
<123, xyz, yyy>
adam
26
<456, rrr, kkk>
bill
31
<666, uuu, hhh>
现在我需要将同一个人对象与不同的地址对象合并为一个,如下所示?
List<Person> findPerson;
adam
26
<123, xyz, 456>
<456, rrr, 123>
bill
31
<666, uuu, 999>
如何在Java 8流中完成此操作?
答案 0 :(得分:8)
我建议您在equals
课程中实施hashcode
和Person
。
示例:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
然后您可以使用Map<Person, List<Address>>
作为结果集的接收器类型。
Map<Person, List<Address>> resultSet = findPerson.stream()
.collect(
Collectors.groupingBy(
Function.identity(),
Collectors.flatMapping(
p -> p.getAddress().stream(),
Collectors.toList()
)
)
)
;
此解决方案使用flatMapping
收集器,该收集器仅在Java-9时可用。
如果你还在使用Java-8,那么你可以这样做:
Map<Person, List<Address>> resultSet = findPerson.stream()
.collect(
Collectors.groupingBy(
Function.identity(),
Collectors.collectingAndThen(
Collectors.mapping(
Person::getAddress,
Collectors.toList()
),
lists -> lists.stream()
.flatMap(List::stream)
.collect( Collectors.toList() )
)
)
)
;
note - 按原样,我目前假设基于name
和age
的两个或更多人被认为是平等的,但是,如果它仅基于{{1}或者只是name
,那么您需要稍微调整一下age
/ equals
方法。
答案 1 :(得分:5)
假设Person
一致地实施hashCode
和equals
,您可以收集到Map
:
Map<Person, Person> map = findPerson.stream()
.collect(Collectors.toMap(
p -> p,
p -> p,
(oldPerson, newPerson) -> {
oldPerson.getAddress().addAll(newPerson.getAddress());
return oldPerson;
}));
这使用Collectors.toMap
,其工作原理是合并新人与地图中已有的老人,合并我们的意思是添加地址。
现在,在地图的值中,您有合并的人:
Collection<Person> result = map.values();
如果您需要返回一个列表:
List<Person> result = new ArrayList<>(map.values());
修改强>
这假设每个人的List<Address>
可变(以便addAll
有效)。情况并非总是如此,特别是如果您不想破坏封装,您不应该从外部修改Person
个对象的地址。
在这种情况下,您可以在Person
中提供一个方法来负责合并地址而不破坏封装:
public Person merge(Person another) {
this.address.addAll(another.address);
return this;
}
或者如果地址列表是不可变的:
public Person merge(Person another) {
List<Address> merged = new ArrayList<>(this.address);
merged.addAll(another.address);
this.address = merged;
return this;
}
现在收集器中的代码可以重构为:
Map<Person, Person> map = findPerson.stream()
.collect(Collectors.toMap(
p -> p,
p -> p,
Person::merge));
答案 2 :(得分:2)
另一种方法,不需要覆盖equals
和hashCode
:
List<Person> merged = findPerson.stream()
.collect(
Collectors.collectingAndThen(
Collectors.toMap(
(p) -> new AbstractMap.SimpleEntry<>( p.getName(), p.getAge() ),
Function.identity(),
(left, right) -> {
left.getAddress().addAll( right.getAddress() );
return left;
}
),
ps -> new ArrayList<>( ps.values() )
)
)
;
答案 3 :(得分:1)
这个答案与最近删除的答案非常相似:
findPerson = findPerson.stream()
.collect(Collectors.groupingBy(Person::getName,
Collectors.reducing((p1, p2) -> {
p1.getAddress().addAll(p2.address);
return p1;
}))).values().stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
使用Java 9,可以用Optional::stream
的单个调用替换底部的两行。
使用您的示例,结果如下:
[Person[bill, 31, [Address[666, uuu, hhh]]], Person[adam, 26, [Address[123, xyz, yyy], Address[456, rrr, kkk]]]]
注意:请务必为equals
适当覆盖hashCode
和Person
。