Java for Beginner
870 subscribers
1.01K photos
275 videos
14 files
1.69K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Раздел 8. Stream API и функциональный стиль в Java

Глава 7: Stream API в экосистеме Java. Интеграционные паттерны

Примитивные стримы (IntStream, LongStream)

Одно из фундаментальных ограничений Java — отсутствие обобщений (generics) для примитивных типов. Stream<T> может содержать только ссылочные типы, что вынуждает упаковывать int в Integer, long в Long, double в Double. Эта операция, называемая боксингом (boxing), создаёт объекты-обёртки в куче, увеличивает нагрузку на GC и снижает производительность кэша процессора из-за разыменования указателей.

Примитивные стримы — IntStream, LongStream, DoubleStream — решают эту проблему через специализацию. Они работают с сырыми примитивами, избегая аллокаций и обеспечивая плотное размещение данных в памяти.


Генерация числовых последовательностей

range и rangeClosed

IntStream.range(start, end) генерирует последовательность от start (включительно) до end (исключительно). rangeClosed включает обе границы.
// Индексы для обхода: 0, 1, 2, ..., 99
IntStream indices = IntStream.range(0, 100);

// Дни месяца: 1, 2, ..., 31
IntStream days = IntStream.rangeClosed(1, 31);


Эти методы возвращают упорядоченные, конечные потоки с известным размером — оптимальные характеристики для параллелизма. Spliterator такого потока делится идеально: середина вычисляется как (start + end) >>> 1, создавая сбалансированные сегменты.

iterate и generate

Бесконечные последовательности создаются через iterate (с заданным началом и функцией следующего элемента) или generate (с поставщиком значений).
// Степени двойки: 1, 2, 4, 8, ... с ограничением
IntStream powersOfTwo = IntStream.iterate(1, n -> n * 2)
.limit(20);

// Случайные числа
IntStream randomInts = new Random().ints(); // Бесконечный
IntStream limitedRandom = new Random().ints(100, 0, 1000); // 100 чисел от 0 до 999


iterate не поддерживает эффективное разделение — каждый элемент зависит от предыдущего, что делает параллелизм бесполезным. Random.ints() оптимизирован для параллельного использования через специализированный


Spliterator.

Агрегация примитивов: статистика без промежуточных объектов

Примитивные стримы предоставляют терминальные операции, возвращающие примитивные значения без боксинга:
int sum = IntStream.range(1, 101).sum();  // 5050, возвращает int

long count = LongStream.range(0, 1_000_000).count(); // 1000000L

OptionalDouble average = IntStream.of(10, 20, 30).average(); // OptionalDouble[20.0]

OptionalInt max = IntStream.of(3, 1, 4, 1, 5).max(); // OptionalInt[5]
OptionalInt, OptionalLong, OptionalDouble — примитивные версии Optional, избегающие обёртки. Методы: isPresent(), getAsInt() / getAsLong() / getAsDouble(), orElse(int), orElseGet(IntSupplier).


summaryStatistics: комплексная агрегация

Для многократной статистики за один проход:
IntSummaryStatistics stats = IntStream.of(10, 20, 30, 40, 50)
.summaryStatistics();

System.out.println(stats.getCount()); // 5
System.out.println(stats.getSum()); // 150
System.out.println(stats.getMin()); // 10
System.out.println(stats.getMax()); // 50
System.out.println(stats.getAverage()); // 30.0


IntSummaryStatistics — mutable контейнер, накапливающий данные без промежуточных коллекций. Эффективен для больших потоков: вместо boxed().collect(summarizingInt(Integer::intValue)) используется прямая агрегация примитивов.


#Java #для_новичков #beginner #stream_api #IntStream #LongStream
👍5
Преобразования между типами стримов

boxed: примитив к ссылочному

Когда нужен полноценный Stream<T> с методами объектов:
Stream<Integer> boxed = IntStream.range(0, 100).boxed();

// Применение сложных операций, недоступных для примитивов
List<String> labels = IntStream.range(0, 100)
.boxed()
.map(i -> "Item #" + i)
.collect(toList());


boxed() создаёт объект для каждого примитива — дорогая операция для больших потоков. Применять осознанно, когда объектная семантика необходима.

mapToObj: примитив к произвольному объекту

Более эффективный путь создания объектов из примитивов:
List<Book> booksByIndex = IntStream.range(0, library.size())
.mapToObj(i -> new Book("Book " + i, "Author " + i, 2000 + i))
.collect(toList());
mapToObj(IntFunction<R>) принимает функцию, создающую объект из индекса, без промежуточного boxed().
mapToInt, mapToLong, mapToDouble: объект к примитиву


Обратное преобразование для агрегации:
// Суммарное количество страниц
int totalPages = library.stream()
.mapToInt(Book::pageCount) // Book -> int
.sum();

// Средняя цена в long-центах для точности
long averagePriceCents = library.stream()
.mapToLong(b -> b.price().multiply(BigDecimal.valueOf(100)).longValue())
.average()
.orElse(0L);


Эти методы критичны для производительности: mapToInt(Book::pageCount).sum() вместо map(Book::pageCount).reduce(0, Integer::sum) избегает миллионов аллокаций Integer.


Сценарий: работа с индексами

Классическая задача, не имеющая прямого решения в Stream<T> — доступ к индексу элемента. Примитивные стримы предоставляют элегантный обход:
// Нумерация элементов с сохранением порядка
List<String> numbered = IntStream.range(0, books.size())
.mapToObj(i -> (i + 1) + ". " + books.get(i).title())
.collect(toList());

// Поиск с индексом
OptionalInt firstExpensiveIndex = IntStream.range(0, books.size())
.filter(i -> books.get(i).price().compareTo(THRESHOLD) > 0)
.findFirst(); // Возвращает индекс, не объект


Для List с произвольным доступом это эффективно. Для LinkedList или потоков без размера — требуется промежуточная материализация:
// Обобщённый подход для любого Stream
Stream<IndexedValue<Book>> withIndex = StreamUtils.zipWithIndex(books.stream());

// Или через AtomicInteger (менее элегантно, но без внешних библиотек)
AtomicInteger index = new AtomicInteger(0);
List<String> numbered = books.stream()
.map(b -> index.getAndIncrement() + ": " + b.title())
.collect(toList()); // Не параллелизуемо!



Сценарий: статистические расчёты

Примитивные стримы оптимизированы для математической обработки:
// Корреляция двух наборов данных (упрощённо)
double[] x = fetchDataX();
double[] y = fetchDataY();

double meanX = Arrays.stream(x).average().orElse(0.0);
double meanY = Arrays.stream(y).average().orElse(0.0);

double covariance = IntStream.range(0, x.length)
.mapToDouble(i -> (x[i] - meanX) * (y[i] - meanY))
.average()
.orElse(0.0);
Arrays.stream(double[]) создаёт DoubleStream без копирования массива — эффективный доступ к существующим данным.



Параллелизм примитивных стримов

IntStream.parallel(), LongStream.parallel() наследуют все особенности ForkJoinPool. Дополнительное преимущество — отсутствие boxing при слиянии результатов. sum(), average(), summaryStatistics() в параллельном режиме используют примитивные аккумуляторы, минимизируя синхронизацию.
// Эффективная параллельная агрегация
long sum = LongStream.range(0, 100_000_000)
.parallel()
.sum(); // Разделение по сегментам, локальные суммы, финальное слияние


#Java #для_новичков #beginner #stream_api #IntStream #LongStream
👍5