有两个实体:
class GiftCertificate {
Long id;
List<Tag> tags;
}
class Tag {
Long id;
String name;
}
有一个列表
List<GiftCertificate>
,其中包含以下数据:
<1, [1, "Tag1"]>, <2, null>, <1, [2, "Tag2"]>
。 (它不包含一组标签,而只有一个标签或根本没有标签。)
我需要这样做,结果是这样的:
<1, {[1," Tag1 "], [2," Tag2 "]}>, <2, null>
。我的意思是,将第三个GiftCertificate中的标签添加到第一个对象的集合中,同时删除第三个对象。我想至少了解一些有关如何执行此操作的想法。最好使用流。
答案 0 :(得分:0)
可能不是最有效的方法,但可能会有所帮助
private List<GiftCertificate> joinCertificates(List<GiftCertificate> giftCertificates) {
return giftCertificates.stream()
.collect(Collectors.groupingBy(GiftCertificate::getId))
.entrySet().stream()
.map(entry -> new GiftCertificate(entry.getKey(), joinTags(entry.getValue()))).collect(Collectors.toList());
}
private List<Tag> joinTags(List<GiftCertificate> giftCertificates) {
return giftCertificates.stream()
.flatMap(giftCertificate -> Optional.ofNullable(giftCertificate.getTags()).stream().flatMap(Collection::stream))
.collect(Collectors.toList());
}
答案 1 :(得分:0)
这可以使用Java8来完成,
final List<GiftCertificate> mergedGiftCerts = giftCerts.stream()
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(GiftCertificate::getId,
Collectors.mapping(gc -> gc.getTags() != null ? gc.getTags().get(0) : null,
Collectors.toList())),
m -> m.entrySet().stream().map(e -> new GiftCertificate(e.getKey(), e.getValue()))
.collect(Collectors.toList())));
答案 2 :(得分:0)
您可以使用流以及GiftCertificate
中专用的自定义构造函数和几个帮助器方法来完成所需的操作。这是构造函数:
public GiftCertificate(GiftCertificate another) {
this.id = another.id;
this.tags = new ArrayList<>(another.tags);
}
这仅用作复制构造函数。我们正在创建一个新的标签列表,因此,如果GiftCertificate
实例之一的标签列表被修改,则另一个实例不会被修改。 (这只是基本的OO概念:封装)。
然后,为了将另一个GiftCertificate
的标签添加到此GiftCertificate
的标签列表中,可以将以下方法添加到GiftCertificate
:
public GiftCertificate addTagsFrom(GiftCertificate another) {
tags.addAll(another.tags);
return this;
}
而且,一个返回标签列表是否为空的助手方法将非常方便:
public boolean hasTags() {
return tags != null && !tags.isEmpty();
}
最后,有了这三种简单的方法,我们准备好使用流的所有功能以一种优雅的方式解决问题:
Collection<GiftCertificate> result = certificates.stream()
.filter(GiftCertificate::hasTags) // keep only gift certificates with tags
.collect(Collectors.toMap(
GiftCertificate::getId, // group by id
GiftCertificate::new, // use our dedicated constructor
GiftCertificate::addTagsFrom)) // merge the tags here
.values();
这使用Collectors.toMap
创建了一个地图,该地图通过id
将礼品券分组,合并了标签。然后,我们保留地图的值。
这是等效的解决方案,没有流:
Map<Long, GiftCertificate> map = new LinkedHashMap<>(); // preserves insertion order
certificates.forEach(cert -> {
if (cert.hasTags()) {
map.merge(
cert.getId(),
new GiftCertificate(cert),
GiftCertificate::addTagsFrom);
}
});
Collection<GiftCertificate> result = map.values();
这是一个性能稍有改善的变体:
Map<Long, GiftCertificate> map = new LinkedHashMap<>(); // preserves insertion order
certificates.forEach(cert -> {
if (cert.hasTags()) {
map.computeIfAbsent(
cert.getId(),
k -> new GiftCertificate(k)) // or GitCertificate::new
.addTagsFrom(cert);
}
});
Collection<GiftCertificate> result = map.values();
此解决方案需要以下构造函数:
public GiftCertificate(Long id) {
this.id = id;
this.tags = new ArrayList<>();
}
此方法的优势在于,仅当地图中没有其他具有相同ID的条目时,才会创建新的GiftCertificate
实例。
答案 3 :(得分:0)
Java 9引入了flatMapping
收集器,该收集器特别适合解决此类问题。将任务分为两个步骤。首先,建立礼品证书ID到标签列表的映射,然后组装GiftCertificate
对象的新列表:
import static java.util.stream.Collectors.flatMapping;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
......
Map<Long, List<Tag>> gcIdToTags = gcs.stream()
.collect(groupingBy(
GiftCertificate::getId,
flatMapping(
gc -> gc.getTags() == null ? Stream.empty() : gc.getTags().stream(),
toList()
)
));
List<GiftCertificate> r = gcIdToTags.entrySet().stream()
.map(e -> new GiftCertificate(e.getKey(), e.getValue()))
.collect(toList());
这假设GiftCertificate
的构造函数接受Long id
和List<Tag> tags
请注意,如果没有礼品券ID的标签,此代码将通过创建一个空列表而不是null
来偏离您的要求。使用null
代替空列表只是一个很糟糕的设计,它迫使您在各处使用null
检查来污染代码。
如果您更易读,则flatMapping
的第一个参数也可以写为gc -> Stream.ofNullable(gc.getTags()).flatMap(List::stream)
。