Когда стоит использовать Iterable

Ответ

Iterable стоит использовать, когда нужно предоставить последовательный доступ к элементам коллекции без раскрытия её внутренней структуры. Это полезно для:

  1. Ленивой загрузки (например, чтение данных из сети/БД по мере необходимости).
  2. Работы с большими коллекциями, чтобы избежать загрузки всех элементов в память.
  3. Сокрытия реализации коллекции, предоставляя только итератор.

Пример:

fun fetchData(): Iterable<String> = object : Iterable<String> {
    override fun iterator() = object : Iterator<String> {
        private var count = 0
        override fun hasNext() = count < 3
        override fun next() = "Item ${count++}"
    }
}

// Использование:
fetchData().forEach { println(it) }

⚠️ Если нужны операции в памяти (например, filter, map), лучше использовать Sequence или List.

Ответ 18+ 🔞

Э, слушай, вот сидишь ты такой, думаешь: а нахуя мне этот Iterable? Ну типа, есть же List, есть Sequence... Ёпта, зачем ещё одна штука?

А вот представь, блядь, что у тебя есть какая-нибудь хитрая жопа система. Например, ты читаешь логи из файла, который размером с моё терпение — терпения ноль, ебать. Или стримишь данные из сети, где они приходят по одному пакетику, а не все сразу. Так вот, если ты вернёшь List, тебе придётся сначала всё это овердохуища информации в память загнать, а потом уже по ней ходить. А это, чувак, пиздопроебибна идея, если данных дохуя.

Вот тут-то Iterable и выходит на сцену, как хуй с горы. Его задача — не дать тебе всю коллекцию сразу, а сказать: «Слушай, вот тебе итератор, иди по элементам по одному, пока они есть. А откуда я их беру — это мои, блядь, проблемы». Это и есть ленивая загрузка. Ты не грузишь всё в память, а вытягиваешь данные по мере надобности, как из крана воду. Пока не открыл — не течёт.

И второй момент, ёперный театр, — сокрытие реализации. Твоему коду похуй, откуда данные берутся: из списка в памяти, из базы данных или их мартышка на клавиатуре набирает. Код просто получает Iterable и ходит по нему. Внутри можешь хоть впендюрить какую угодно логику в этот итератор — это твоё дело.

Вот смотри, пример, чтобы не быть полупидором, который только теорию несёт:

fun fetchData(): Iterable<String> = object : Iterable<String> {
    override fun iterator() = object : Iterator<String> {
        private var count = 0
        override fun hasNext() = count < 3
        override fun next() = "Item ${count++}"
    }
}

// Использование:
fetchData().forEach { println(it) }

Видишь? Функция fetchData() возвращает не готовый список, а объект, который умеет давать итератор. Итератор тут — мартышлюшка простая, которая три элемента выдаёт. Но на его месте мог бы быть итератор, который читает из сокета или файла. Для того, кто вызывает forEach, разницы да похуй — он просто проходит по элементам.

⚠️ Но вот важный момент, сам от себя охуел, когда это понял. Если тебе нужно делать filter, map или другие операции, и ты их сделаешь на Iterable... они будут eager (жадными) и вернут тебе List! То есть вся ленивость нахуй слетит, и данные загрузятся в память. Если тебе нужна ленивая цепочка преобразований — это залупа конская, тебе нужен Sequence. Iterable — это про последовательный доступ, а Sequence — про ленивые трансформации.

Короче, резюме: Iterable — это когда хочешь дать доступ «по одному», не светя внутренности и не грузя всё в оперативку. Для всего остального есть List или Sequence. Выбирай с умом, а то будет тебе хиросима и нигерсраки с памятью.