Java 8中使用两个列表的流

13

我有一个方法,需要传入两个列表作为参数,可以看到在方法中我想要过滤一些内容并将结果返回给调用者。我想将这段代码转换为使用Java 8的流式API和lambda表达式,但是我无法理解如何实现。我最终创建了多个流来完成这个功能,这违背了重构的初衷(我的意见)。我想知道的是,如何以简单的方式将这段代码重构为只使用一个流?

public Set<CustomerTrack> getCustomerTracks(List<CusomerTrack> tracks, List<Customer> customers) {
    Set<CustomerTrack> tracksToSave = new HashSet<>();
    for (Customer customer : customers) {
        if (customer.getTrack() == null) {
            continue;
        }
        Long allowedTrackId = customer.getTrack().getId();
        for (CustomerTrack track : tracks) {
            if (Long.valueOf(track.getId()).equals(allowedTrackId)) {
                tracksToSave.add(track);
            }
        }
    }
    return tracksToSave;
}

我真的建议您将轨道放入id --> object的哈希映射中,现在您得到了O(n2)的复杂度,这根本不高效。 - maslan
@maslan,你有没有相关的资料可以让我了解一下你提到的问题?我想要深入地理解它 :) (我现在认为我已经理解了这个问题)。感谢你指出。 - gozluklu_marti
3
问题很简单,你有一个 customers 中的id列表;对于 每个 id,你都要遍历 tracks 列表以找到需要的内容。遍历列表的复杂度为 O(n),并且由于你对每个元素都这样做,所以总体复杂度为 O(n*m)。如果你先从 customer 创建一个id的 Set - 你将提高效率,因为 Set 中的 contains 复杂度为 O(1) - Eugene
@Eugene(Set -> HashSet与例如TreeSet和O(log n)的区别)- 我是对的吗? - LuCio
1
因此,您仍需要遍历一个Set(即O(n)),但在另一个Set中搜索现在是O(1);因此,总时间为O(n) + O(1),由于O(1)是常数,因此可以删除它,从而使您的总解决方案为O(n) - 比之前好得多。 - Eugene
显示剩余3条评论
7个回答

9
看起来这就是你想要的内容:
 customers.stream() 
          .filter(c -> c.getTrack() != null)
          .map(c -> c.getTrack().getId())
          .flatMap(id -> tracks.stream().filter(track -> Long.valueOf(track.getId()).equals(id)))
          .collect(Collectors.toSet());

请注意,对于每个id,您都在迭代整个tracks列表;这具有O(n*m)复杂度。通常认为这很糟糕,您可以改进它。
要使其更好,您首先需要从Customer创建一个id的HashSet;拥有该HashSet后,您现在可以使用您感兴趣的id调用其上的contains,因为contains的时间复杂度为O(1)(实际上称为O(1)的摊销复杂度)。因此,现在您的复杂度变成了O(n)+ O(1),但由于O(1)是一个常数,它实际上是O(n)-比以前好多了。在代码中:
Set<Long> set = customers.stream()
            .filter(c -> c.getTrack() != null)
            .map(c -> c.getTrack().getId())
            .collect(Collectors.toSet());

Set<CusomerTrack> tracksToSave = tracks.stream()
            .filter(track -> set.contains(track.getId())
            .collect(Collectors.toSet()));

嗨,尤金,你能展示给我最有效率的方法吗? - gozluklu_marti
@Yonetmen 刚刚完成了...(我还没有编译这段代码,真心希望我没有漏掉任何东西,比如逗号或括号) - Eugene

1
首先,您可以创建一个允许的 ID 集合:
  Set<Long> collect = customers.stream()
                .filter(customer -> customer.getTrack() != null)
                .map(customer -> customer.getTrack().getId())
                .collect(Collectors.toSet());

然后你可以填充你的音轨收藏。
 Set<CusomerTrack> tracksToSave = tracks.stream()
                .filter(track -> collect.contains(Long.valueOf(track.getId())))
                .collect(Collectors.toSet());

谢谢您的回答,但是 collect.contains(track.getId())) 应该替换为 collect.contains(Long.valueOf(track.getId()))) - gozluklu_marti
3
为什么简单明了的 collect.contains(track.getId()) 应该被替换成 collect.contains(Long.valueOf(track.getId())) - Holger
@Holger 在我的情况下,CustomerTrack 的 Id 字段是字符串类型。 - gozluklu_marti
1
@Yonetmen 这对于问题来说是非常有价值的信息。任何回答者怎么可能知道这个要求呢?而且为什么第一个 customer.getTrack().getId() 返回一个 Long 呢? - Holger
@Holger 好的,这是我的错!我不应该指出它,因为它与主题无关。但既然你问了,我可以解释一下。customer 对象中的 getTrack() 返回一个 Track 实体,而 CustomerTrack 只是用于消息传递(反序列化对象)。 - gozluklu_marti

1
一种更为便利的方法是使用方法引用:
Set<Track> tracks = 
customers.stream()
         .map(Customer::getTrack) // customer to track
         .filter(Objects::nonNull) // keep non null track
         .map(Track::getId)      // track to trackId
         .flatMap(trackId -> tracks.stream() // collect tracks matching with trackId
                                   .filter(t-> Long.valueOf(t.getId()).equals(trackId))
         )
         .collect(toSet());

0
你可以尝试这样做:
customers
    .stream()
    .map(Customer::getTrack)
    .filter(Objects::nonNull)
    .map(CustomerTrack::getId)
    .flatMap(trackId -> tracks
                        .stream()
                        .filter(track -> Long.valueOf(track.getId()).equals(trackId)))
    .collect(Collectors.toSet());

2
无法编译,flatMap 需要返回一个 Stream,而你返回了一个 Set - Eugene

0

这里的重要操作符是flatMap

Set<CustomerTrack> tracksToSave = customers.stream()
   .map(Customer::getTrack)
   .filter(track -> track != null)
   .flatMap(track -> {
      tracks.stream.filter(it -> Long.valueOf(it.getId()).equals(track.getId())))
   .collect(Collectors.toSet());

dehasi的回答可能更好。 - Nigel Nop
我对Lambda语法感到困惑 - 谢谢Eugene :D - Nigel Nop

0

试试这个

customers.stream()
          .filter(customer -> customer.getTrack() != null)
          .map(c -> c.getTrack().getId())
          .forEach(allowedTrackId -> { 
           tracks.stream()
          .filter(track -> Long.valueOf(track.getId()).equals(allowedTrackId))
          .forEach(tracksToSave::add);
});

0

您需要先过滤掉空值,然后再根据客户跟踪列表进行筛选。

希望这个回答对您有所帮助。

return customers.stream().map(cust-> cust.track).filter(track -> track != null).
            collect(Collectors.toList())
            .stream().filter(track-> customerTracks.stream()
                    .anyMatch(ele -> ele.getId() == 
track.getId())).collect(Collectors.toSet());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接