【问题标题】:Java 8 Stream to find element in listJava 8 Stream 在列表中查找元素
【发布时间】:2016-03-10 22:37:17
【问题描述】:

我有以下课程:

public class Item {
    int id;
    String name;
    // few other fields, contructor, getters and setters
}

我有一个项目列表。我想遍历列表并找到具有特定 id 的实例。我正在尝试通过流来实现。

public void foobar() {

    List<Item> items = getItemList();
    List<Integer> ids = getIdsToLookup();
    int id, i = ids.size() - 1;

    while (i >= 0) {
        id = ids.get(i);
        Optional<Item> item = items
            .stream()
            .filter(a -> a.getId() == id)
            .findFirst();
        // do stuff
        i--;
    }
}

这是遍历列表并获得我需要的元素的最佳方式吗?此外,我在 id 的过滤器行上收到一个错误,它说 lambda 表达式中使用的变量必须是最终的或有效的最终的。也许我可以在 while 循环中定义 id ,这应该可以消除异常。谢谢。

【问题讨论】:

  • 您对列表中的索引和 lambda 中的当前项目使用相同的变量 i。选择一个不同的名称。该代码很好,但效率很低。如果你有多个 id 并且需要为所有找到对应的项目,首先创建一个 HashMap,然后使用 HashMap。
  • 我在我的代码中使用了不同的变量,我试图在这里简化代码。我会改的。
  • 在循环中声明变量idinside,它实际上是最终的。通过在外面,您在每次迭代时重新初始化它,因此它不是最终的。在尽可能小的范围内声明变量通常是最佳实践。
  • 是的,我也是这么想的。感谢您的建议。
  • BTW 循环也一样,使用for (int i = 0; i &lt; ids.size(); i++),或者更好更简单:for (Integer id : ids)

标签: java lambda java-8 java-stream


【解决方案1】:

你可以试试这样的:

ids.forEach(id -> 
    list.stream()
    .filter(p -> p.getId() == id)
    .findFirst()
    .ifPresent(p -> {
        // do stuff here
    });
);

这里的可选表明您的过滤器方法可以返回一个空流,因此如果您调用 findFirst 它可以找到一个或零个元素。

【讨论】:

【解决方案2】:

如果您有很多 id 需要搜索,建议使用一次性完成搜索的解决方案,而不是对每个 id 进行线性搜索:

Map<Integer,Optional<Item>> map=ids.stream()
    .collect(Collectors.toMap(id -> id, id -> Optional.empty()));
items.forEach(item ->
    map.computeIfPresent(item.getId(), (i,o)->o.isPresent()? o: Optional.of(item)));
for(ListIterator<Integer> it=ids.listIterator(ids.size()); it.hasPrevious();) {
    map.get(it.previous()).ifPresent(item -> {
        // do stuff
    });
}

第一个语句只是从 ids 列表中创建一个映射,将每个搜索 id 映射到一个空的Optional

第二个语句使用forEach 遍历项目,并且对于每个项目,它检查是否存在从其 id 到空 Optional 的映射,如果存在则将其替换为封装项目的 Optional这样的映射,一站式操作,computeIfPresent

最后一个for 循环在ids 列表上向后迭代,因为您希望按该顺序处理它们并在有非空Optional 时执行操作。由于地图是使用列表中找到的所有 id 初始化的,get 永远不会返回 null,如果在 items 列表中找不到 id,它将返回一个空的 Optional

这样,假设Map的查找具有O(1)的时间复杂度,这是典型实现中的情况,净时间复杂度从O(m×n)变为O(m+n)...

【讨论】:

  • 我喜欢单次通过并获得所有必需条目的想法。但我需要按照我在 ids 列表中收到的顺序处理这些条目。我不认为上面的代码关心排序,对吧?另外,你能解释一下你在上面的代码中试图做什么吗?这会很有帮助。谢谢。
  • @Gengis Khan:它完全符合您的要求。 for 循环如您所愿在 ids 列表上向后迭代。
【解决方案3】:

如果你想坚持使用流并向后迭代,你可以这样做:

IntStream.iterate(ids.size() - 1, i -> i - 1)
    .limit(ids.size())
    .map(ids::get) // or .map(i -> ids.get(i))
    .forEach(id -> items.stream()
        .filter(item -> item.getId() == id)
        .findFirst().ifPresent(item -> {
            // do stuff
        }));

此代码与您的代码相同。

它向后迭代,从种子开始:ids.size() - 1ints 的初始流的大小限制为limit(),因此没有负的ints,并且流与ids 的列表大小相同。然后,map() 操作将索引转换为实际的id,它位于ids 列表的第i 个位置(这是通过调用ids.get(i) 完成的)。最后,在 items 列表中搜索该项目的方式与在您的代码中相同。

【讨论】:

  • 流操作意味着O(n) 复杂性无论如何......
  • @Holger 我的意思是超过ids列表
  • @Holger 现在我明白你的意思了,将编辑以删除该注释。谢谢!
【解决方案4】:

您希望为每个给定的 id 最多找到一个项目,并对找到的项目做一些事情,对吗?更多的性能改进:

Set<Integer> idsToLookup = new HashSet<>(getIdsToLookup()); // replace list with Set

items.stream()
    .filter(e -> idsToLookup.remove(e.getId()))
    .forEach(
       /* doing something */
     );

【讨论】:

    猜你喜欢
    • 2020-06-28
    • 1970-01-01
    • 2014-07-31
    • 1970-01-01
    • 1970-01-01
    • 2016-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多