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
不能适應動态變化的集合,因為我在動作中删除了元素。
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
方法。