Hoe kan ik een “pauze” of “doorgaan” wanneer ik in een functionele lus zit binnen Kotlin?

In Kotlin kan ik geen breakof continuedoen binnen een functielus en mijn lambda — zoals ik kan met een normale forlus. Dit werkt bijvoorbeeld niet:

(1..5).forEach {
    continue@forEach  // not allowed, nor break@forEach
}

Er zijn oude documentatiewaarin wordt vermeld dat dit beschikbaar, maar het lijkt erop dat het nooit is geïmplementeerd. Wat is de beste manier om hetzelfde gedrag te krijgen als ik vanuit de lambda wil continueof break?

Opmerking:deze vraag is met opzet geschreven en beantwoord door de auteur (Zelfbeantwoorde vragen), zodat de idiomatische antwoorden op veelgestelde Kotlin-onderwerpen aanwezig zijn in SO. Ook om enkele echt oude antwoorden te verduidelijken die zijn geschreven voor alfa’s van Kotlin die niet nauwkeurig zijn voor het huidige Kotlin.


Antwoord 1, autoriteit 100%

Er zijn andere opties dan waar u om vraagt ​​die vergelijkbare functionaliteit bieden. Bijvoorbeeld:

Je kunt het verwerken van sommige waarden vermijden met behulp van filter: (like a continue)

dataSet.filter { it % 2 == 0 }.forEach {
    // do work on even numbers
}

Je kunt een functionele lus stoppen door takeWhile: (als een break)

dataSet.takeWhile { it < 10 }.forEach {
    // do work on numbers as long as they are < 10, otherwise stop
}

Een complexer, hoewel onzinnig voorbeeld waarbij u wat verwerking wilt doen, enkele resulterende waarden wilt overslaan en vervolgens wilt stoppen bij een reeks verschillende voorwaarden, zou zijn:

dataSet.asSequence()
       .takeWhile { it >=  0 }    // a -1 signals end of the dataset (break)
       .map { it + 1 }            // increment each number
       .filterNot { it % 5 == 0 } // skip (continue) numbers divisible by 5
       .map { it - 1 }            // decrement each number by 1
       .filter { it < 100 }       // skip (continue) if number is >= 100
       .drop(5)                   // ignore the first 5 numbers
       .take(10)                  // use the next 10 numbers and end
       .forEach {
           // do work on the final list
       }

Een combinatie van deze functies heeft de neiging om continueof breakte elimineren. En er zijn hier eindeloos veel verschillende opties en meer dan kan worden gedocumenteerd. Om een ​​idee te krijgen van wat er gedaan kan worden, kunt u het beste alle functies leren die beschikbaar zijn in de Kotlin-standaardbibliotheek voor collecties, luie reeksenen itereerbaar.

Soms zijn er gevallen waarin je een muterende toestand hebt die nog moet breakof continueen dat is moeilijk te doen in een functioneel model. Je kunt het laten werken met complexere functies zoals folden reducein combinatie met de functies filteren takeWhile, maar soms dat is moeilijker te grommen. Daarom, als je echt dat exacte gedrag wilt, kun je return from lambda expressiondie een continueof breaknabootst, afhankelijk van je gebruik.

Hier is een voorbeeld dat continuenabootst:

(1..5).forEach  {
    if (it == 3) return@forEach  // mimic continue@forEach
    // ... do something more
}

En je kunt ingewikkelder gaan en labels gebruiken als je nesten of verwarrende situaties hebt:

(1..3).forEach outer@ { x ->
    (1..3).forEach inner@ { y ->
        if (x == 2 && y == 2) return@outer // mimic continue@outer
        if (x == 1 && y == 1) return@inner // mimic continue@inner
        // ... do something more
    }
}

Als je een breakwilt doen, heb je iets buiten de lus nodig waarvan je kunt terugkeren, hier zullen we de functie run()gebruiken om ons te helpen:

run breaker@ {
    (1..20).forEach { x ->
        if (x == 5) return@breaker  // mimic break@forEach
        // ... do something more
    }
}

In plaats van run()zou het let()of apply()kunnen zijn of iets natuurlijks dat je hebt rond de forEachdat is een plek waar je wilt breken. Maar je slaat ook de code over binnen hetzelfde blok na de forEachdus wees voorzichtig.

Dit zijn inline-functies, dus ze voegen niet echt overhead toe.

Lees de Kotlin-referentiedocumenten voor Retouren en sprongenvoor alle speciale gevallen, inclusief voor anonieme functies.


Hier is een eenheidstestdie aantoont dat dit allemaal werkt:

@Test fun testSo32540947() {
    val results = arrayListOf<Pair<Int,Int>>()
    (1..3).forEach outer@ { x ->
        (1..3).forEach inner@ { y ->
            if (x == 2 && y == 2) return@outer // continue @outer
            if (x == 1 && y == 1) return@inner // continue @inner
            results.add(Pair(x,y))
        }
    }
    assertEquals(listOf(Pair(1,2), Pair(1,3), Pair(2,1), Pair(3,1), Pair(3,2), Pair(3,3)), results)
    val results2 = arrayListOf<Int>()
    run breaker@ {
        (1..20).forEach { x ->
            if (x == 5) return@breaker
            results2.add(x)
        }
    }
    assertEquals(listOf(1,2,3,4), results2)
}

Antwoord 2, autoriteit 2%

takeWhilestdlib-functie kan gebruikt in plaats van pauze.

Bijvoorbeeld

val array = arrayOf(2, 8, 4, 5, 13, 12, 16)
array.takeWhile { it % 2 == 0 }.forEach { println(it) } // break on odd
array.takeWhile { it % 3 != 0 }.forEach { println(it) } // break on 3 * n

Antwoord 3

forEachmet pauze kan specifiek worden vervangen door elkefunctie:

(1..20).any { x ->
    (x == 5).apply { // break on true
        if (!this) {
            results2.add(x)
        }
    }
}

Of mogelijk nog korter:

(1..20).any { x ->
    results2.add(x)
    x == 4 // break on true
}

Other episodes