在某些情况下翻拍清单

时间:2018-10-03 18:22:30

标签: java collections java-8 java-stream

有两个实体:

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中的标签添加到第一个对象的集合中,同时删除第三个对象。我想至少了解一些有关如何执行此操作的想法。最好使用流。

4 个答案:

答案 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 idList<Tag> tags

请注意,如果没有礼品券ID的标签,此代码将通过创建一个空列表而不是null来偏离您的要求。使用null代替空列表只是一个很糟糕的设计,它迫使您在各处使用null检查来污染代码。

如果您更易读,则flatMapping的第一个参数也可以写为gc -> Stream.ofNullable(gc.getTags()).flatMap(List::stream)