如何正確終止 forEach

本文來自 Kotlin 中文博客:Kotlinerjavascript

問題背景

話說週六下午團建回來,看到羣裏的小夥伴們在糾結一個問題,如何退出 forEach 循環,或者說有沒有終止 forEach 循環的方法,就像 break 那樣。咱們來看個例子:java

val list = listOf(1,3,4,5,6,7,9) 
 for(e in list){ 
     if(e > 3) break 
     println(e) 
 }複製代碼

若是 e 大於 3,那麼循環終止,這是傳統的寫法,那麼問題來了,咱們如今追求更現代化的寫法,要把代碼改爲 forEach 的函數調用,那麼咱們會怎麼寫呢?算法

list.forEach { 
     if(it > 3) ??? 
     println(it) 
 }複製代碼

就像上面這樣嗎?感受應該是這樣,不過大於 3 的時候,究竟該怎麼辦才能退出這個循環呢?api

return 仍是 return@forEach ?

做爲基本上等價於匿名函數的 Lambda 表達式,咱們可能但願它可以提早返回,這樣是否是就至關於終止循環了呢?函數

fun main(args: Array<String>) { 
     val list = listOf(1,3,4,5,6,7,9) 
     list.forEach { 
         if(it > 3) return 
         println(it) 
     } 
 }複製代碼

這時候咱們堅決果斷的寫下了這樣的代碼,大於 3 的時候,直接 return,結果呢?運行到 4 的時候,forEach 就真的被終止了,後面也就沒有了輸出。oop

嗯,這樣是否是就算把問題解決啦?想一想也不可能呀,否則我這周的文章豈不是太坑人了?性能

fun main(args: Array<String>) { 
     val list = listOf(1,3,4,5,6,7,9) 
     list.forEach { 
         if(it > 3) return 
         println(it) 
     } 
     println("Hello") 
 }複製代碼

當咱們把代碼改爲這樣的時候,咱們運行時發現只輸出 1 3,後面的 Hello 則是沒法打印的。緣由呢,固然也很簡單,在 return 眼裏,Lambda 表達式都不算事兒,因此咱們在大於 3 時的 return,其實是返回了 main 函數,因而 list.forEach 這個結構以後的代碼就不能被執行了。ui

好吧,那這裏用 return 確定是有問題的,咱們不用它了行了吧。那不用 return 用什麼呢?好在 Kotlin 爲咱們提供了標籤式的返回方法,也就是說,若是你想從一個 Lambda 表達式當中顯式地返回,那麼你只須要寫 return@xxx 便可,例如:this

fun main(args: Array<String>) { 
     val list = listOf(1,3,4,5,6,7,9) 
     list.forEach { 
         if(it > 3) return@forEach 
         println(it) 
     } 
     println("Hello") 
 }複製代碼

你也能夠給這個 Lambda 表達式起個新標籤名稱,好比 block:spa

fun main(args: Array<String>) { 
     val list = listOf(1,3,4,5,6,7,9) 
     list.forEach block@{ 
         if(it > 3) return@block 
         println(it) 
     } 
     println("Hello") 
 }複製代碼

這樣,咱們的程序運行結果就是:

1 
 3 
 Hello複製代碼

這一步你們都會想到的,不過這並非最終的解。

調用仍是循環?

我來問你們一個問題,前面的 forEach 後面傳入的 Lambda 表達式體是循環體嗎?

固然不是。那其實就是一個函數體,所以對這個函數體的退出只能退出當前的調用。爲了說明這個問題,咱們仍是須要對原有的例子作下小修改:

fun main(args: Array<String>) { 
     val list = listOf(1,3,4,5,6,7,9) 
     list.forEach block@{ 
         println("it=$it") 
         if(it > 3) return@block 
         println(it) 
     } 
     println("Hello") 
 }複製代碼

結果呢?

it=1 
 1 
 it=3 
 3 
 it=4 
 it=5 
 it=6 
 it=7 
 it=9 
 Hello複製代碼

好傢伙,儘管咱們在大於 3 的時候 return@block,但看上去仍然沒有什麼軟用,顯然,後面的循環仍然執行了。

簡單總結一下,在 Lambda 表達式中,return 返回的是所在函數,return@xxx 返回的是 xxx 標籤對應的代碼塊。因爲 forEach 後面的這個 Lambda 實際上被調用了屢次,所以咱們沒有辦法像 for 循環那樣直接 break 。

額。。這可如何是好?

流式數據處理

實際上咱們在 Kotlin 當中用到的 forEach、map、flatMap 等等這樣的高階函數調用,都是流式數據處理的典型例子,咱們也看到不甘落後卻又跟不上節奏的 Java 在 8.0 推出了 stream Api,其實也無非就是爲流式數據處理提供了方便。

採用流式 api 處理數據,咱們就不能再像之前那樣思考問題啦,之前的思惟方式多單薄呀,只要是遍歷,那就是 for 循環,只要是條件那就是 if...else,卻不知世界在變,api 也在變,你不跟着變,那你就請便啦。

那麼,回到咱們最開始的問題,需求其實很明確,遇到某一個大於 3 的數,咱們就終止遍歷,這樣的代碼用流式 api 寫出來應該是這樣的:

val list = listOf(1,3,4,5,6,7,9) 
 list.takeWhile { it <= 3 }.forEach(::println) 
 println("Hello")複製代碼

咱們首先經過 takeWhile 來篩選出前面連續不大於 3 的元素,也就是說一旦遇到一個大於 3 的元素咱們就丟棄從這個開始全部後面的元素;接着,咱們把取到的這些不大於 3 的元素經過 forEach 打印出來,這樣的話,程序的效果與文章最開頭的 for 循環 break 的實現就徹底一致了。

val list = listOf(1,3,4,5,6,7,9) 
 for(e in list){ 
     if(e > 3) break 
     println(e) 
 }複製代碼

固然,你可能會說若是我想要打印其中的偶數,那我該怎麼寫呢?這時候我告訴你們,若是你寫出了下面這樣的代碼,那麼我只能告訴你,。。額,我剛想說啥來着??

list.forEach {  
     if(it % 2 == 0){ 
         println(it) 
     } 
 }複製代碼

上面這樣寫的代碼呢,讓我想起了辮帥張勳:張將軍,你知不知道,咱大清已經亡了呢?

list.filter { it % 2 == 0 }.forEach(::println)複製代碼

哈哈,若是真的但願使用流式 api,那麼上面這樣的寫法纔算是符合風格的寫法。固然了,若是你願意,你還能夠定義一個 isEven 的方法,代碼寫出來就像下面這樣:

fun Int.isEven() = this % 2 == 0 
   
 fun main(args: Array<String>) { 
     val list = listOf(1,3,4,5,6,7,9) 
     list.filter(Int::isEven).forEach(::println) 
 }複製代碼

性能

前不久看到有一篇文章對 Java 8 的流式 api 作了評測,說流式 api 的執行效率比傳統的 for-loop 差出一倍甚至更多,因此建議你們慎重考慮選擇。

其實對於這個東西我認爲咱們不必把神經繃這麼緊。緣由也很簡單呀,流式 api 的執行效率從實現上來說,確實很難達到純 for-loop 那樣的高效,例如咱們前面的:

list.filter(Int::isEvent).forEach(::println)複製代碼

在 filter 的時候就調用了一次完整的 for-loop,然後面的 forEach 一樣再來一遍,也就是說咱們用傳統的 for-loop 一遍搞定的事兒,用流式 api 寫了兩遍,若是條件比較複雜,出現兩遍三遍的狀況也是比較正常的。

不過這並不能說明流式 api 就必定要慎重使用。流式 api 更適用於數據的流式處理,特別是涉及到較多 UI 交互的場景,這樣的業務邏輯用流式 api 表達起來會很是的簡潔直觀,也易於維護,相應的,這樣的場景對於性能的要求並無到吹毛求疵的地步;而對於性能比較敏感的程序,一般來講也沒有很複雜的業務邏輯,流式 api 在這裏也難以發揮做用。

另外,僅僅多個幾回循環也並不會改變算法自己的運算效率的數量級,因此對於適用於流式 api 的場景,你們仍是能夠放心去使用的。

--

若是你有興趣加入咱們,請直接關注公衆號 Kotlin ,或者加 QQ 羣:162452394 聯繫咱們。

相關文章
相關標籤/搜索