Сегодня я собираюсь поговорить о Sequence в Kotlin и о том, как он работает под деревом. В качестве последовательности мы в основном говорим о лямбда-функции оператора и о том, чем она отличается от интерфейса List.
Последовательность похожа на библиотеку коллекций из Java
public interface Sequence<out T> { public operator fun iterator(): Iterator<T> }
То же, что и List, но имеет только одну функцию, которая возвращает объект-итератор.
Все операторы являются функциями расширения, о которых мы поговорим ниже.
Это простой способ создать объект Sequence.
val seq : Sequence = listOf(1,2,3).asSequence()
asSequence()
функция - это функция расширения Iterable.
public fun <T> Iterable<T>.asSequence(): Sequence<T> { // Create by Sequence anonymous class use the iterator same as list object return Sequence { this.iterator() } }
Из приведенного выше кода мы можем сказать, что нам нужно сначала создать объект List, а затем использовать функцию asSequence
для преобразования объекта List в объект Sequence.
Итак, зачем нам Sequence, если у нас уже есть List. Вот простая разница с точки зрения оператора.
У нас есть две простые операторные функции.
val list : List<Int> = listOf(1,2,3).also { list -> list.map { it + 1 }.filter { it != 1 } } val seq : Sequence = listOf(1,2,3).also { seq -> seq.map { it + 1 }.filter { it != 1 } }
В объекте List он перебирает все элементы внутри списка, возвращает новый объект List, затем переходит к следующей операторной функции filter
, мы снова запускаем процесс.
Мы просто посмотрим на функцию map
из списка
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> { return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform) } public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C { for (item in this) destination.add(transform(item)) return destination }
Мы видим, что он создаст новый объект List для хранения новых transformed
элементов в destination
Итак, если у нас есть 10 операторов, он создаст 10 объектов List, и ему нужно будет перебирать все элементы, пока все они не будут добавлены в список.
Теперь давайте посмотрим на Sequence
исходный код.
Подпись для каждого оператора похожа, поэтому я беру функцию map
в качестве образца.
public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> { return TransformingSequence(this, transform) }
Как и List, он принимает в качестве аргумента лямбда-функцию.
internal class TransformingSequence<T, R> constructor(private val sequence: Sequence<T>, private val transformer: (T) -> R) : Sequence<R> { override fun iterator(): Iterator<R> = object : Iterator<R> { val iterator = sequence.iterator() override fun next(): R { return transformer(iterator.next()) } override fun hasNext(): Boolean { return iterator.hasNext() } } internal fun <E> flatten(iterator: (R) -> Iterator<E>): Sequence<E> { return FlatteningSequence<T, R, E>(sequence, transformer, iterator) } }
Но вот главное отличие.
Для map
он создаст новый объект Sequence из TransformingSequence
, который реализует интерфейс Sequence и имеет свою собственную реализацию для функции iterator
.
TransformingSequence
имеет два свойства: последовательность, которая относится к предыдущей последовательности, и преобразователь, как лямбда-функция преобразования.
Что касается функции iterator
, если вы присмотритесь, вы поймете сейчас.
По сути, sequence
преобразует элемент только при вызове next()
Если нам нужно использовать последовательность после преобразования, нам нужно вызвать toList()
Поэтому независимо от того, сколько у вас операторов, он управляет оператором только тогда, когда вы вызываете toList
. Для каждого оператора он преобразует последовательность в объект различия и обновляет текущий iterator
.
О реализации итератора
override fun next(): R { return transformer(iterator.next()) }
Мы видим, что это рекурсивная функция.
Мы получаем текущий элемент из previous sequence
, используем transformer
для преобразования текущего элемента в новый элемент, затем перебираем следующий элемент.
Итак по образцу
val seq : Sequence = listOf(1,2,3).also { seq -> seq.map { it + 1 }.filter { it != 1 }.toList()
Он будет выполнять map
функцию, 1+1
затем пройти filter
2 != 1
и так далее ...
Вот и все ! Спасибо за чтение !
Если у меня есть ошибки в этой статье, прокомментируйте их ниже.
Если вы хотите узнать больше о Sequence
, прочтите эту статью.
Эффективный котлин: предпочитать последовательность для больших коллекций с более чем одним этапом обработки
Это часть книги« Эффективный котлин . blog.kotlin-academy.com»