前言
上課的時候看到老師用迭代器來遍歷 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
方法。