web-dev-qa-db-fra.com

Iterable et Sequence de Kotlin se ressemblent exactement. Pourquoi deux types sont-ils requis?

Ces deux interfaces définissent une seule méthode

public operator fun iterator(): Iterator<T>

La documentation indique que Sequence est censé être paresseux. Mais est-ce que Iterable n'est pas trop paresseux (à moins qu'il ne soit soutenu par un Collection)?

67
Venkata Raju

La principale différence réside dans la sémantique et la mise en œuvre des fonctions d'extension stdlib pour Iterable<T> Et Sequence<T>.

  • Pour Sequence<T>, Les fonctions d'extension fonctionnent paresseusement dans la mesure du possible, de manière similaire aux opérations Java Streams intermédiaire. Par exemple, Sequence<T>.map { ... } renvoie un autre Sequence<R> Et ne traite pas réellement les éléments avant une opération terminal comme toList ou fold est appelé.

    Considérez ce code:

    val seq = sequenceOf(1, 2)
    val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
    print("before sum ")
    val sum = seqMapped.sum() // terminal
    

    Il imprime:

    before sum 1 2
    

    Sequence<T> Est destiné à une utilisation paresseuse et à un pipelining efficace lorsque vous souhaitez réduire le travail effectué dans les opérations terminal autant que possible, de la même manière que pour Java = Streams. Cependant, la paresse introduit une surcharge, ce qui n'est pas souhaitable pour les transformations simples courantes de petites collections et les rend moins performantes.

    En général, il n'y a aucun bon moyen de déterminer quand c'est nécessaire, donc dans Kotlin, la paresse stdlib est rendue explicite et extraite de l'interface Sequence<T> Pour éviter de l'utiliser sur tous les Iterable par défaut.

  • Pour Iterable<T>, Au contraire, les fonctions d'extension avec intermédiaire la sémantique des opérations fonctionne avec impatience, traite les éléments immédiatement et renvoie un autre Iterable. Par exemple, Iterable<T>.map { ... } renvoie un List<R> Avec les résultats du mappage.

    Le code équivalent pour Iterable:

    val lst = listOf(1, 2)
    val lstMapped: List<Int> = lst.map { print("$it "); it * it }
    print("before sum ")
    val sum = lstMapped.sum()
    

    Cela imprime:

    1 2 before sum
    

    Comme dit ci-dessus, Iterable<T> N'est pas paresseux par défaut, et cette solution se montre bien: dans la plupart des cas, elle a une bonne localité de référence profitant ainsi du cache CPU, de la prédiction, de la prélecture etc. de sorte que même la copie multiple d'une collection fonctionne toujours assez bien et fonctionne mieux dans les cas simples avec de petites collections.

    Si vous avez besoin de plus de contrôle sur le pipeline d'évaluation, il existe une conversion explicite en une séquence paresseuse avec la fonction Iterable<T>.asSequence() .

115
hotkey

Compléter la réponse du raccourci clavier:

Il est important de noter comment Sequence et Iterable itèrent dans tous vos éléments:

Exemple de séquence:

        list.asSequence()
            .filter { field ->
                Log.d("Filter", "filter")
                field.value > 0
            }.map {
                Log.d("Map", "Map")
            }.forEach {
                Log.d("Each", "Each")
            }

Résultat du journal:

filtre - Carte - Chacun; filtre - Carte - Chacun

Exemple itérable:

             list.filter { field ->
                    Log.d("Filter", "filter")
                    field.value > 0
                }.map {
                    Log.d("Map", "Map")
                }.forEach {
                    Log.d("Each", "Each")
                }

filtre - filtre - Carte - Carte - Chacun - Chaque

37