Stream.limit: примеры (JAVA)
Stream.limit(long maxSize): StreamОписание и поведение Stream.limit
Метод limit у интерфейса java.util.stream.Stream (и у примитивных потоков IntStream, LongStream, DoubleStream) возвращает новый поток, содержащий не более указанного количества элементов исходного потока. Метод является промежуточной, stateful операцией и не изменяет исходный источник.
Сигнатуры (несколько основных):
Streamlimit(long maxSize) IntStream limit(long maxSize)LongStream limit(long maxSize)DoubleStream limit(long maxSize)
Аргументы и возврат:
- maxSize - максимальное число элементов в результирующем потоке. Если
maxSizeменьше нуля, выбрасываетсяIllegalArgumentException. - Возвращается новый
Streamтого же типа, что и исходный, содержащий первые (в порядке потока) не болееmaxSizeэлементов.
Поведенческие детали:
- Для упорядоченных потоков сохраняется порядок исходного потока и будут взяты именно первые элементы в этом порядке.
- Для неупорядоченных потоков реализация может вернуть любые элементы, при этом часто достигается лучшая параллельная производительность.
- Операция limit поддерживает короткое замыкание: для конечных и бесконечных источников выполнение может остановиться после получения
maxSizeэлементов, что делает её полезной для работы с бесконечными генераторами. - При использовании с
parallel()поведение корректно, но производительность и выбор элементов (для неупорядоченных стримов) зависят от реализации. - Если
maxSizeбольше количества элементов в исходном потоке, возвращается поток с исходным количеством элементов (ограничение не добавляет пустые элементы).
Примечание: метод не изменяет источник данных и не гарантирует материализацию; результатом является ленивый промежуточный стрим, требующий терминальной операции.
Короткие примеры использования
Ниже приведены простые примеры для иллюстрации поведения.
1) Обычный список, ограничение первых трех элементов
import java.util.*;
import java.util.stream.*;
List<String> list = Arrays.asList("a","b","c","d");
List<String> res = list.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println(res);
[a, b, c]
2) Ограничение больше размера
List<Integer> nums = Arrays.asList(1,2);
List<Integer> r = nums.stream().limit(5).collect(Collectors.toList());
System.out.println(r);
[1, 2]
3) Бесконечный поток с ограничением
Stream.iterate(0, n -> n + 1)
.limit(5)
.forEach(System.out::println);
0 1 2 3 4
4) Нулевой лимит
Arrays.asList(1,2,3).stream().limit(0).count();
0
5) Примитивный поток
IntStream.range(0,10).limit(3).forEach(System.out::print);
012
Аналоги и смежные операции в Java
- skip(long n) - пропускает первые n элементов. Часто используется вместе с limit для реализации пагинации (skip(offset).limit(size)).
- takeWhile(Predicate) (Java 9) - берет элементы, пока предикат истинно. Предпочтительнее, когда нужно ограничение по условию, а не по числу.
- dropWhile(Predicate) (Java 9) - противоположность takeWhile, полезна для фильтрации префикса.
- distinct() - удаляет дубликаты; при желании получить N уникальных элементов важен порядок применения distinct и limit.
- Collections.subList или List.stream().skip(...).limit(...) - для уже материализованных списков subList может быть быстрее и экономнее по памяти.
Рекомендации:
- Для пaginации при уже доступной коллекции предпочтительнее subList. Для ленивой обработки больших/бесконечных потоков использовать stream().skip(...).limit(...).
- Для ограничения по условию использовать takeWhile вместо limit+фильтра, когда нужно прервать при первом несоответствии условию.
Эквиваленты в других языках и различия
Короткие примеры и отличия от Java:
JavaScript (Array и генераторы)
// Массив
const arr = [1,2,3,4];
console.log(arr.slice(0,3));
// Генератор + limit
function* naturals(){let i=0; while(true) yield i++;}
const it = naturals();
const res = Array.from({length:3}, () => it.next().value);
console.log(res);
[1,2,3] [0,1,2]
Python (списки и itertools)
# Список
l = [1,2,3,4]
print(l[:3])
# Итератор
import itertools
it = itertools.count(0)
print(list(itertools.islice(it, 3)))
[1, 2, 3] [0, 1, 2]
PHP (массивы)
$a = [1,2,3,4];
print_r(array_slice($a, 0, 3));
Array
(
[0] => 1
[1] => 2
[2] => 3
)
C# (LINQ)
using System.Linq;
var a = new[]{1,2,3,4};
Console.WriteLine(string.Join(",", a.Take(3)));
1,2,3
SQL
SELECT * FROM table LIMIT 3; -- синтаксис для MySQL, PostgreSQL
-- в T-SQL: SELECT TOP 3 * FROM table;
(первые 3 строки результата)
Go (срезы)
a := []int{1,2,3,4}
fmt.Println(a[:3])
[1 2 3]
Kotlin (Sequence и коллекции)
val s = sequenceOf(0,1,2,3).take(2).toList()
println(s)
val l = listOf(1,2,3,4).subList(0,3)
println(l)
[0, 1] [1, 2, 3]
Lua (таблицы, ручной цикл)
local a = {1,2,3,4}
local res = {}
for i=1,math.min(3,#a) do table.insert(res,a[i]) end
for _,v in ipairs(res) do io.write(v, ' ') end
1 2 3
Отличия от Java:
- Во многих языках ограничение списочного контейнера выполняется константным доступом (срезы) и не требует ленивости. В Java advantage limit проявляется на ленивых/бесконечных стримах.
- В Java есть различие между упорядоченными и неупорядоченными стримами, что влияет на результат limit в параллельных случаях.
- В языках с генераторами/итераторами (JS, Python) limit обычно реализуется через собирающий шаг или islice.
Типичные ошибки при использовании limit
- Передача отрицательного аргумента. Пример:
Stream.of(1,2,3).limit(-1).count();
Exception in thread "main" java.lang.IllegalArgumentException: maxSize must be non-negative
at java.base/java.util.stream.ReferencePipeline.limit(ReferencePipeline.java:??)
...
- Попытка повторного использования потока после терминальной операции:
Stream s = Stream.of(1,2,3).limit(2);
s.forEach(System.out::println);
s.count(); // Ошибка
1
2
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:??? )
...
- Ожидание определенного набора элементов в неупорядоченном параллельном стриме. Пример: попытка взять первые N элементов без учета порядка может дать непредвиденные элементы.
- Неправильный порядок применения операций: например, применение
limitдоsorted()приводит к сортировке только ограниченной части и не эквивалентно сортировке всего набора с последующим limit. - Использование
limitиdistinctв неправильной последовательности при попытке получить N уникальных элементов. Пример:stream.limit(10).distinct()может вернуть меньше уникальных элементов, чем требуется.
Изменения и заметки по версиям Java
- Метод limit введен вместе с Stream API в Java 8 и сохранял свою семантику в последующих версиях.
- В Java 9 появились дополнительные операции takeWhile и dropWhile, которые закрывают часть сценариев, где раньше использовался limit+проверка.
- В последних релизах JVM и библиотеках происходили оптимизации производительности и улучшения реализации spliterator-ов, что может повлиять на эффективность limit в параллельных потоках, но семантика осталась прежней.
Расширенные и нестандартные примеры использования
1) limit в сочетании с parallel() и unordered для получения быстрых N результатов (неупорядоченно)
List<Integer> data = IntStream.range(0,1000).boxed().collect(Collectors.toList());
List<Integer> res = data.parallelStream()
.unordered() // позволяет реализации выбирать любые элементы быстрее
.filter(i -> i % 2 == 0)
.limit(5)
.collect(Collectors.toList());
System.out.println(res);
[0, 2, 4, 6, 8] // порядок может меняться, но часто будут первые найденные
2) short-circuit демонстрация: limit прерывает генерацию элементов
Stream.generate(() -> {
System.out.println("генерирую");
return Math.random();
}).limit(3).forEach(x -> System.out.println("получено: "+x));
генерирую получено: 0.12345 генерирую получено: 0.54321 генерирую получено: 0.99999
3) limit до сортировки vs после сортировки
List<Integer> vals = Arrays.asList(5,4,3,2,1);
System.out.println(vals.stream().limit(3).sorted().collect(Collectors.toList()));
System.out.println(vals.stream().sorted().limit(3).collect(Collectors.toList()));
[3, 4, 5] // сначала взяты первые 3 элемента [5,4,3], потом отсортированы [1, 2, 3] // сначала сортировка всего потока, затем первые 3 наименьших
4) Получение N уникальных элементов из потенциально бесконечного источника
// Неправильно: может вернуть меньше чем нужно
Stream.generate(() -> (int)(Math.random()*5))
.limit(10)
.distinct()
.forEach(System.out::println);
// Правильно: distinct сначала, затем limit
Stream.generate(() -> (int)(Math.random()*10000))
.distinct()
.limit(5)
.forEach(System.out::println);
(в первом случае количество уникальных элементов <=10, может быть меньше 5) (во втором случае гарантированно ровно 5 уникальных чисел)
5) Использование с ограничением потоков ввода/вывода
Files.lines(Paths.get("file.txt"))
.limit(100)
.forEach(System.out::println);
// чтение только первых 100 строк и немедленное закрытие потока при завершении
(первые 100 строк файла)
6) Комбинация skip/limit для реализации пагинации
int page = 2, size = 5; // третья страница с нулевого индекса
List<T> pageData = source.stream()
.skip((long)page * size)
.limit(size)
.collect(Collectors.toList());
(список элементов для страницы)
7) Limit вместе с map, где map выполняет ресурсоемкую операцию. Важно ставить limit до map, если требуется ограничить количество дорогостоящих вычислений.
Stream.of(1,2,3,4,5)
.limit(2) // ограничение до дорогостоящих вызовов
.map(i -> expensive(i))
.forEach(System.out::println);
(expensive вызывается только для первых 2 элементов)