Обидва ці інтерфейси визначають лише один метод
public operator fun iterator(): Iterator<T>
Документація стверджує, що Sequence
вона призначена для ледачого. Але чи не Iterable
лінь теж (якщо не підкріплений а Collection
)?
Обидва ці інтерфейси визначають лише один метод
public operator fun iterator(): Iterator<T>
Документація стверджує, що Sequence
вона призначена для ледачого. Але чи не Iterable
лінь теж (якщо не підкріплений а Collection
)?
Відповіді:
Ключова відмінність полягає у семантиці та реалізації функцій розширення stdlib для Iterable<T>
та Sequence<T>
.
Адже Sequence<T>
функції розширення виконують ліниво, де це можливо, подібно до проміжних операцій Java Streams . Наприклад, Sequence<T>.map { ... }
повертає інший Sequence<R>
і фактично не обробляє елементи, доки не буде викликана операція терміналу, подібна toList
або fold
викликана.
Розглянемо цей код:
val seq = sequenceOf(1, 2)
val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
print("before sum ")
val sum = seqMapped.sum() // terminal
Друкується:
before sum 1 2
Sequence<T>
призначений для ледачого використання та ефективного конвеєрингу, коли ви хочете максимально скоротити роботу, виконану в термінальних операціях, так само, як і для Java Streams. Однак лінощі вносять деякі накладні витрати, що небажано для звичайних простих перетворень менших колекцій і робить їх менш ефективними.
Взагалі, немає хорошого способу визначити, коли це потрібно, тому в Kotlin stdlib лінощі чітко виражаються та витягуються в Sequence<T>
інтерфейс, щоб уникнути його використання на всіх Iterable
s за замовчуванням.
Адже Iterable<T>
навпаки, функції розширення з проміжною операційною семантикою працюють з працею, обробляють елементи відразу і повертають інші Iterable
. Наприклад, Iterable<T>.map { ... }
повертає a List<R>
із результатами відображення в ньому.
Еквівалентний код для Iterable:
val lst = listOf(1, 2)
val lstMapped: List<Int> = lst.map { print("$it "); it * it }
print("before sum ")
val sum = lstMapped.sum()
Це роздруковує:
1 2 before sum
Як було сказано вище, Iterable<T>
за замовчуванням не ледачий, і це рішення добре себе демонструє: у більшості випадків воно має хорошу локалізацію посилань, таким чином використовуючи переваги кеша процесора, прогнозування, попереднього вибору тощо, так що навіть багаторазове копіювання колекції все ще працює добре достатньо і краще працює в простих випадках з невеликими колекціями.
Якщо вам потрібен більше контролю над конвеєром оцінки, існує явне перетворення в ледачу послідовність з Iterable<T>.asSequence()
функцією.
map
, filter
і інші не несуть достатньо інформації , щоб вирішити , крім від типу колекції джерела, і так як більшість колекцій також Iterable, що не є хорошим маркером «лінуватися» , тому що це зазвичай СКРІЗ. лінивий повинен бути явним, щоб бути в безпеці.
Заповнення відповіді гарячої клавіші:
Важливо зауважити, як послідовність та ітерація повторюються у ваших елементах:
Приклад послідовності:
list.asSequence().filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
Результат журналу:
фільтр - Карта - Кожен; фільтр - Карта - Кожен
Ітеративний приклад:
list.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
filter - filter - Карта - Карта - Кожен - Кожен
Iterable
відображається наjava.lang.Iterable
інтерфейсі наJVM
і реалізується загальновживаними колекціями, такими як List або Set. Функції розширення колекції на них оцінюються з бажанням, що означає, що всі вони негайно обробляють всі елементи, що вводяться, і повертають нову колекцію, що містить результат.Ось простий приклад використання функцій збору для отримання імен перших п’яти людей у списку, вік яких становить принаймні 21 рік:
val people: List<Person> = getPeople() val allowedEntrance = people .filter { it.age >= 21 } .map { it.name } .take(5)
Цільова платформа: JVMRunning на kotlin v. 1.3.61 По-перше, перевірка віку виконується для кожної окремої Особи у списку, а результат вноситься в абсолютно новий список. Потім відображення їхніх імен виконується для кожної Особи, яка залишилася після оператора фільтра, і потрапляє до чергового нового списку (це тепер a
List<String>
). Нарешті, є останній новий список, створений, щоб містити перші п’ять елементів попереднього списку.На відміну від цього, Sequence - це нова концепція в Котліні, яка представляє ліньо оцінену колекцію цінностей. Для
Sequence
інтерфейсу доступні ті самі розширення колекції , але вони негайно повертають екземпляри Sequence, які представляють оброблений стан дати, але фактично не обробляючи жодних елементів. Щоб розпочати обробку, термінSequence
повинен бути завершений за допомогою оператора терміналу, в основному це запит до Послідовності матеріалізувати дані, які вона представляє, у певній формі. Приклади включають в себеtoList
,toSet
іsum
, згадати лише деякі з них. Коли вони викликані, буде оброблено лише мінімально необхідну кількість елементів для отримання необхідного результату.Перетворення існуючої колекції в послідовність досить просто, вам просто потрібно використовувати
asSequence
розширення. Як вже згадувалося вище, вам також потрібно додати оператора терміналу, інакше Послідовність ніколи не буде виконувати жодну обробку (знову ж таки, ліниво!).
val people: List<Person> = getPeople() val allowedEntrance = people.asSequence() .filter { it.age >= 21 } .map { it.name } .take(5) .toList()
Цільова платформа: JVMRunning на kotlin v. 1.3.61. У цьому випадку кожен екземпляр Person у послідовності перевіряється на свій вік, якщо вони проходять, їх ім’я витягується, а потім додається до списку результатів. Це повторюється для кожної людини у вихідному списку, поки не знайдеться п’ять людей. На цьому етапі функція toList повертає список, а решта людей у файлі
Sequence
не обробляються.Також є щось додаткове, на що здатна Послідовність: вона може містити нескінченну кількість предметів. З огляду на це, має сенс, що оператори працюють так, як вони працюють - оператор на нескінченній послідовності ніколи не зможе повернутися, якщо він виконує свою роботу з бажанням.
Як приклад, ось послідовність, яка буде генерувати стільки потужностей 2, скільки вимагає оператор терміналу (ігноруючи той факт, що це швидко переповнюється):
generateSequence(1) { n -> n * 2 } .take(20) .forEach(::println)
Ви можете знайти більше тут .
Java
(в основномуGuava
) вболівальників