Сегодня мы рассмотрим интересные аспекты работы с List
и Iterable
. Возможно, вы скажете, что это просто списки, но на самом деле в Dart эти типы данных имеют свои особенности. Например, иногда можно выполнять операцию map
над List
, как показано ниже:
var list = ["1", "2", "3", "4", "5"];
var map = list.map((e) {
var result = int.parse(e) + 10;
print("######### $result");
return result;
});
Ответ: ничего не будет выведено, так как методы, такие как map
или where
, возвращают ленивый объект типа Iterable
. Это значит, что они будут выполнены только при обращении к ним.
Например, если вызвать метод toList()
или toString()
, то будет запущена операция map
, которая выведет соответствующие данные.
Новый вопрос: если мы выполним все четыре метода, сколько раз будет выведен лог? Ответ: три раза.
Кроме того, кроме метода isEmpty
, остальные три метода снова запустят выполнение метода map
.
На самом деле, когда мы выполняем операцию map
над List
, возвращается ленивый объект типа Iterable
. Каждый раз, когда нам требуется получить значение из этого объекта, он заново выполняется, так как результаты предыдущих операций не кэшируются.
Может быть, вам покажется это сложным, поэтому давайте рассмотрим пример от автора библиотеки fast_immutable_collections:- Мы применяем метод where
к одному и тому же массиву, чтобы получить объект типа Iterable
.
evenFilterEager
дополнительно используется метод .toList()
.where
счетчики увеличиваются.length
трижды, и выводятся значения счетчиков.var ленивыйСчетчик = 0;
var жадныйСчетчик = 0;
var ленивыйФильтрНечетных = [1, 2, 3, 4, 5, 6, 7].where((i) {
ленивыйСчетчик++;
return i % 2 == 0;
});
var жадныйФильтрЧетных = [1, 2, 3, 4, 5, 6, 7].where((i) {
жадныйСчетчик++;
return i % 2 == 0;
}).toList();
print("\n\n---------- Инициализация ----------\n\n");
let жадный = removeLessThan10_жадный(removeOdd_жадный(list));
let ленивый = removeLessThan10_ленивый(removeOdd_ленивый(list));
print("\n\n---------- Ленивая вычисляемость ----------\n\n");
print(леневой);
print("\n\n---------- Жадная вычисляемость ----------\n\n");
print(жадный);
Приведенный ниже пример покажет результат выполнения кода:
List<int> удалитьНечетныеЖадный(List<int> источник) {
return источник.where((i) {
print("удалитьНечетныеЖадный");
return i % 2 == 0;
}).toList();
}
List<int> удалитьМеньше10Жадный(List<int> источник) {
return источник.where((i) {
print("удалитьМеньше10Жадный");
return i >= 10;
}).toList();
}
Iterable<int> удалитьНечетныеЛениваяВычисляемость(Iterable<int> источник) {
return источник.where((i) {
print("удалитьНечетныеЛениваяВычисляемость");
return i % 2 == 0;
});
}
Iterable<int> удалитьМеньше10ЛениваяВычисляемость(
Iterable<int> источник) {
return источник.where((i) {
print("удалитьМеньше10ЛениваяВычисляемость");
return i >= 10;
});
}
```var список = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
print("\n\n---------- Инициализация ----------\n\n");
Iterable<int> жадный = удалитьМеньше10_жадный(
удалитьНечетные_жадный(список));
Iterable<int> нечастаяВычисляемость = удалитьМеньше10_нечастаяВычисляемость(
удалитьНечетные_нечастаяВычисляемость(список));
print("\n\n---------- Нечастая вычисляемость (ленивый подход) ----------\n\n");
print(нечастаяВычисляемость);
print("\n\n---------- Частая вычисляемость (жадный подход) ----------\n\n");
print(жадный);
```Хотя мы сначала вызываем `print(lazy);`, а затем `print(eager);`, но выводится сначала `removeOdd_eager`, так как в вызовах, связанных с Eager, используется метод `.toList();`. Этот метод выполняется сразу при вызове `removeOdd_eager(list)`, поэтому `removeOdd_eager` полностью выводится перед `removeLessThan10_eager`. В конце значения выводятся при выполнении `print(eager);``.lazy является `Iterable`, поэтому его содержимое выводится только при обращении к нему. Правила вывода следующие: **выводится два раза `removeOdd_lazy`, после чего один раз выводится `removeLessThan10_lazy`**, поскольку каждые две операции удовлетворяют условию `i % 2 == 0;`, что приводит к выполнению `removeLessThan10_lazy` и формированию такой последовательности вывода.
```dart
I/flutter (23298): ---------- Инициализация ----------
I/flutter (23298):
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeOdd_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): removeLessThan10_eager
I/flutter (23298): ---------- Ленивая ----------
I/flutter (23298):
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeOdd_lazy
I/flutter (23298): removeLessThan10_lazy
I/flutter (23298): removeOdd_lazy
```I/flutter (23298): (10, 12, 14)
I/flutter (23298): ---------- Жадный ----------
I/flutter (23298):
I/flutter (23298): [10, 12, 14]
```Не кажется ли вам, что в таких ситуациях `Iterable` усложняет всё? Действительно, при сложной вложенности `Iterable` может сделать логику труднодоступной для поддержки, а официальные рекомендации гласят:
> Из-за возможности многократного прохождения через `Iterable`, использование побочных эффектов внутри итератора не рекомендуется.
А как же использовать `Iterable`? Или где можно применять `Iterable`? На самом деле есть немало сценариев, например:
- Пагинация позволяет гарантировать, что логика загрузки данных выполняется только при необходимости отображения этих данных на экране пользователя.
- Базовый запрос к базе данных может обеспечивать эффект позднего вычисления при работе с данными, а также требует повторной итерации запроса данных каждый раз.
Пример:
```dart
Iterable<int> naturalsFunc() sync* {
int k = 0;
// Бесконечный цикл!
while (true) yield k++;
}
var naturalsIter = naturalsFunc();
print("\n\n---------- Инициализация ----------\n\n");
print("Бесконечный список/итерируемый объект создан, но ещё не выполнен.");
print("\n\n--------------------\n\n");
print("\n\n---------- takeWhile ----------\n\n");
print("С ним можно работать, но требуется метод завершения работы в какой-то момент");
var naturalsUpTo10 = naturalsIter.takeWhile((value) => value <= 10);
print("Натуральные числа до 10: $naturalsUpTo10");
print("\n\n---------- КОНЕЦ ----------\n\n");
На этом этапе вы можете задаться вопросом: **List
тоже является Iterable
, так почему же он отличается от других Iterable
, таких как map
, where
, expand
?**Если мы посмотрим на сам List
, то увидим, что это абстрактный объект, являющийся подклассом Iterable
. Объекты, реализующие этот интерфейс, обычно являются _GrowableList
внутри Dart VM. Структура _GrowableList
представлена следующим образом:
Основное различие между List
и другими Iterable
заключается в том, что:
List
представляет собой индексируемое множество с фиксированной длиной, поскольку его внутренний ListIterator
использует _iterable.length
и _iterable.elementAt
.Iterable
, такие как MappedIterable
после применения map
, представляют собой последовательно доступные множества, где MappedIterator
используется для последовательного доступа к элементам iterable
, игнорируя длину.Итак, можно сделать вывод: тема данной статьи проста и состоит в том, чтобы быстро освоить различия между List
и обычным Iterable
, а также понять особенности и области применения позднего вычисления через примеры. Это поможет правильно выбирать тип Iterable
и находить проблемы во время разработки. Если у вас остаются какие-либо вопросы, приветствуем ваши комментарии и отзывы.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )