Java列表过滤和查找第一个/最后一个

4

我有一个Java对象列表,该对象的属性如下:

public class CheckPoint {
    private String message;
    private String tag;
}

现在,我想根据标签过滤列表并获取第一个/最后一个元素。
例如:可能的标签值:A、B、C。对于值为A的标签,我要获取最后一个元素;对于值为B的标签,我要获取第一个元素;对于值为C的标签,我也要获取第一个元素。
当前解决方案:
CheckPoint inTransitCheckPoint = checkPointsList.stream().filter(c -> c.getTag().equals("A")).reduce((first, second) -> second).orElse(null);

CheckPoint useCheckPoint = checkPointsList.stream().filter(c -> c.getTag().equals("B")).findFirst.orElse(null);

CheckPoint typeCheckPoint = checkPointsList.stream().filter(c -> c.getTag().equals("C")).findFirst.orElse(null);

然而,我明白这种解决方案并不高效,因为我们要进行三次循环。有没有人能帮助我以更高效的方式解决它呢?

非常感谢任何帮助 :)


在最坏情况下,checkPointsList 有多大?你是否遇到了任何实际的性能问题? - Thomas
3
如果你想要代码更加清晰,使用Stream就可以了。但如果你想要更好的性能,在这种情况下,使用“for”循环可能更适合。 - Arnaud Denoyelle
1
你真的应该使用一个好的旧时代循环,通过减少超过3的开销。只需在for循环之前创建初始变量并有3个if语句即可。我完全不同意其他帖子中说只有在出现性能问题时才更改的观点。这就是你最终会遇到性能问题的方式。或者,如果你真的想使用流,可以使用forEach... - Tyler Nichols
2
这并不是一种低效的解决方案。相比其他部分,循环本身非常小。如果你希望在这个层次上进行微优化,你应该使用 for 循环而不是流,因为单个流的开销远远超过 3 个 for 循环的性能,更不用说一个单独的 for 循环收集所有 3 个结果了。简而言之,“低效的解决方案”是不正确的,你应该继续使用对你来说最合理的代码,直到代码剖析显示你实际上有问题。 - Andreas
循环三次同一个列表以查找三个不同项的第一次出现是疯狂的,考虑到他正在积极询问此代码,没有理由继续进行其他事情。 - Tyler Nichols
2个回答

2
你可以先将内容收集到一个Map中,例如:
Map<String, List<CheckPoint>> map = 
       checkPointsList.stream()
                      .collect(Collectors.groupingBy(CheckPoint::getTag));

Optional.ofNullable(map.get("A"))
        .orElse(Collection.emptyList())
        .reduce((left, right) -> right)
        .orElse(null);
// same for "B" and "C"

2

我建议将checkPointList按标签分组成一个Map<String, LinkedList<CheckPoint>>

Map<String, LinkedList<CheckPoint>> map = new HashMap<>();
map.put("A", new LinkedList<>());
map.put("B", new LinkedList<>());
map.put("C", new LinkedList<>());

for(CheckPoint c : checkPointList) {
    map.computeIfAbsent(c.getTag(), ignored -> new LinkedList<>()).add(c);
}

链表是一个方便的助手,因为它允许您直接获取第一个或最后一个元素(如果不存在则返回null):

CheckPoint A = map.get("A").pollLast();
CheckPoint B = map.get("B").pollFirst();
CheckPoint C = map.get("C").pollFirst();

或者你可以使用这个更简单的for循环

CheckPoint a = null, b = null, c = null;
for (CheckPoint checkPoint : checkPointList) {
    String tag = checkPoint.getTag();
    if ("A".equals(tag) && a == null) {
        a = checkPoint;
    } else if("B".equals(tag)){
        b = checkPoint;
    } else if("C".equals(tag)){
        c = checkPoint;
    }
}

"B""C"的变量总是被最后一个值覆盖,而对于a只会选择第一个"A"检查点。


@Eugene 我考虑了一下应该使用哪些集合,也许一个 Queue 会更好,其中一个只保留最后添加的元素,另一个则保留最先添加的元素。 - Lino
@Eugene,如果你觉得需要的话可以在你的回答中包含使用ArrayList的方法。但我认为使用ArrayList并不能大大改善我的答案。虽然它更节省内存,但在大多数环境下这不会产生太大影响。 - Lino
这是时间和空间之间的经典权衡吗?我的原始解决方案似乎效率低下,因为它需要三次流操作,而你的解决方案使用了一个映射和另外三个列表?我的理解正确吗? - The-Proton-Resurgence
@SwapnilBagadia 正确,我已经更新了我的答案,使用一些 if-else 和 3 个变量的更简单的解决方案,应该是最快的。 - Lino
1
感谢您提供的解决方案。这是我想不到的最佳解决方案。简单的循环拯救了一天。 - The-Proton-Resurgence

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