本文來自 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
做爲基本上等價於匿名函數的 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 聯繫咱們。