Java 使用 Stream API 筛选 List
前言
上课的时候看到老师用迭代器来遍历 List 中的元素的时候,我的内心是极其嫌弃的,这种迭代方法不能直接访问当前的元素,而且写起来也麻烦。于是上网查了查 Java 有没有类似于 Linq 的东西,虽然发现了一个 JLinq 但是抱着学习的心态,还是没有用这个东西。看了看 Intellji 的自动补全然后想出了下面的代码。
题目
删除 List 中信息重复的学生
解法一
LinkedList<T3.Student> repo3 = new T3.StudentTest().getRepo();
repo3.removeIf(s->repo3.indexOf(s)!=repo3.lastIndexOf(s));
这个方法看起来是没有很大的问题的,但是如果问题稍微变了一下,这就没用了。
题目 update
删除 List 中信息部分重复的学生,也就是只要姓名、年龄相同的学生就认定为信息重复,即使学号不同。
解法二
上面的解法一到了这个问题就失效了,对于这个问题我只能想到用下面的代码来解决
for (int i = 0; i < repo3.size(); i++)
{
T3.Student stu = repo3.get(i);//Lambda 表达式不允许我用没被引用的变量,所以就把这句单独提了出来
repo3.removeIf(s->s.equals(stu));//根据题意定义的 equals 方法
}
增加了一个 for
循环,其余的基本没变,但是代码的简洁程度相较于使用迭代器得到了大幅度提高。
踩到的坑
0.为毛不用foreach
或者forEach
循环?
foreach
不能适应动态变化的集合,因为我在动作中删除了元素。
forEach
虽然是一个内部循环,有并行计算的优势,但是还是由于上面的原因不能使用。
1.Stream 接口的操作不会对原有的数据产生影响。
repo3.removeAll(repo3.stream()
.filter(
s -> repo3.stream().filter(stu -> stu.equals(s)).count() != 0)
.collect(Collectors.toList()));
本来我是想用这种方法拿到所有重复的元素,然而事实上是不行的,因为Stream 接口的操作不会对原有的数据产生影响。导致第二个 filter
会把所有元素重新扫描一遍,所以需要改成下面的代码:
repo3.removeIf
(
s -> repo3
.subList(repo3.indexOf(s),repo3.size())
.stream()
.filter(stu -> stu.equals(s))
.count() != 0
);
看起来好像比解法二复杂了许多但是在效率上有很大的进步,Stream API
是并行化的,比外部循环不知道要高到哪里去了,然而这里还存在一个问题,就是在subList
中获得对当前元素的索引的速度可能会拖慢效率,然而,在这道题目的测试用例当中学号起到了索引的作用,然后这里的效率可以大幅度提升。
总结
一开始准备用 Stream API
我是拒绝的,我看到它是以方法的形式出现的,我还以为会出现类型转换,后来发现这是 Java 缺少 extend methods
才出现的东西。然后这个东西实现了跟 Linq 差不多的功能,配合 Lambda 表达式很好用。
那么下面给出最终的版本:
repo3.removeIf
(
s -> repo3
.stream()
.filter(stu -> stu.equals(s))
.count() != 0
);
这里之所以不需要把 list 截断可能是因为 removeif
也是一个stream
方法。