1. 程式人生 > >Java 使用 Stream API 篩選 List

Java 使用 Stream API 篩選 List

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方法。