2.3.1. Создание стримов
Stream.of(), Collection.stream(), Arrays.stream()
Материалы
Что такое Stream?
Stream - это последовательность элементов, поддерживающая последовательные и параллельные агрегатные операции.
Ключевые характеристики Stream
-
No storage (Не хранит данные)
- Stream не является структурой данных
- Передает элементы из источника через pipeline операций
-
Functional in nature (Функциональная природа)
- Операции не модифицируют источник
- Создают новый stream с результатом
-
Laziness-seeking (Ленивые вычисления)
- Промежуточные операции выполняются только при терминальной операции
- Оптимизация вычислений
-
Possibly unbounded (Могут быть бесконечными)
- Stream может быть бесконечным
- Short-circuiting операции позволяют завершить обработку
-
Consumable (Одноразовые)
- Элементы можно посетить только один раз
- Для повторной обработки нужен новый stream
Типы Stream
1. Stream - Stream объектов
Stream<String> stringStream;
Stream<Integer> integerStream;
Stream<Person> personStream;
2. IntStream - Stream примитивов int
IntStream intStream;
3. LongStream - Stream примитивов long
LongStream longStream;
4. DoubleStream - Stream примитивов double
DoubleStream doubleStream;
Почему примитивные стримы?
- Избегают boxing/unboxing
- Специализированные методы (sum, average, max, min)
- Лучшая производительность
Способы создания Stream
1. Из Collections - collection.stream()
Последовательный stream
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
Set<Integer> set = Set.of(1, 2, 3);
Stream<Integer> stream = set.stream();
Map<String, Integer> map = Map.of("a", 1, "b", 2);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
Параллельный stream
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> parallelStream = list.parallelStream();
// Или преобразование последовательного в параллельный
Stream<String> parallelStream = list.stream().parallel();
Примеры с разными коллекциями:
// ArrayList
ArrayList<String> arrayList = new ArrayList<>(Arrays.asList("x", "y", "z"));
Stream<String> stream1 = arrayList.stream();
// LinkedList
LinkedList<Integer> linkedList = new LinkedList<>(Arrays.asList(1, 2, 3));
Stream<Integer> stream2 = linkedList.stream();
// HashSet
Set<String> hashSet = new HashSet<>(Arrays.asList("one", "two"));
Stream<String> stream3 = hashSet.stream();
// TreeSet (отсортированный)
TreeSet<Integer> treeSet = new TreeSet<>(Arrays.asList(5, 2, 8, 1));
Stream<Integer> stream4 = treeSet.stream(); // Упорядоченный: 1, 2, 5, 8
// Queue
Queue<String> queue = new LinkedList<>(Arrays.asList("first", "second"));
Stream<String> stream5 = queue.stream();
2. Из массивов - Arrays.stream()
Для массивов объектов
String[] array = {"a", "b", "c", "d"};
Stream<String> stream = Arrays.stream(array);
// С указанием диапазона (inclusive start, exclusive end)
Stream<String> stream = Arrays.stream(array, 1, 3); // "b", "c"
Для массивов примитивов
// int[]
int[] intArray = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(intArray);
// long[]
long[] longArray = {1L, 2L, 3L};
LongStream longStream = Arrays.stream(longArray);
// double[]
double[] doubleArray = {1.0, 2.0, 3.0};
DoubleStream doubleStream = Arrays.stream(doubleArray);
// С диапазоном
IntStream rangeStream = Arrays.stream(intArray, 1, 4); // 2, 3, 4
Пример обработки:
int[] numbers = {1, 2, 3, 4, 5};
int sum = Arrays.stream(numbers)
.filter(n -> n % 2 == 0) // Четные числа
.sum(); // 2 + 4 = 6
3. Stream.of() - Варадик метод
Создание из перечисленных элементов
// Из элементов
Stream<String> stream = Stream.of("a", "b", "c");
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
// Один элемент
Stream<String> singleElement = Stream.of("only one");
// Из массива
String[] array = {"x", "y", "z"};
Stream<String> stream = Stream.of(array);
Примеры использования:
// Создание и обработка в одной цепочке
long count = Stream.of("apple", "banana", "cherry")
.filter(s -> s.length() > 5)
.count(); // 2 (banana, cherry)
// Различные типы
Stream<Object> mixed = Stream.of("text", 123, 45.6, true);
4. Stream.empty() - Пустой stream
Stream<String> emptyStream = Stream.empty();
// Проверка
emptyStream.count(); // 0
// Полезно для возврата из методов
public Stream<String> findUsers(String query) {
if (query.isEmpty()) {
return Stream.empty(); // Вместо null
}
// ... поиск пользователей
}
5. Stream.generate() - Бесконечный stream с генератором
Синтаксис:
static <T> Stream<T> generate(Supplier<T> s)
Генерация случайных чисел
// Бесконечный stream случайных чисел
Stream<Double> randomNumbers = Stream.generate(Math::random);
// Ограничение количества элементов
Stream<Double> tenRandomNumbers = Stream.generate(Math::random)
.limit(10);
List<Double> numbers = tenRandomNumbers.collect(Collectors.toList());
Генерация константных значений
// Бесконечный stream с одним значением
Stream<String> constants = Stream.generate(() -> "constant");
// Первые 5 элементов
Stream.generate(() -> "Hello")
.limit(5)
.forEach(System.out::println); // Hello (5 раз)
Генерация с изменяемым состоянием (НЕ рекомендуется)
// ❌ Плохой пример - stateful generator
AtomicInteger counter = new AtomicInteger(0);
Stream<Integer> numbers = Stream.generate(counter::incrementAndGet)
.limit(5); // 1, 2, 3, 4, 5
// ✅ Лучше использовать Stream.iterate() для последовательностей
Генерация объектов
// Генерация UUID
Stream<UUID> uuids = Stream.generate(UUID::randomUUID)
.limit(10);
// Генерация дат
Stream<LocalDateTime> timestamps = Stream.generate(LocalDateTime::now)
.limit(3);
6. Stream.iterate() - Бесконечный stream с итерацией
Базовая форма (Java 8+)
Синтаксис:
static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)
// Последовательность: 0, 1, 2, 3, 4, ...
Stream<Integer> numbers = Stream.iterate(0, n -> n + 1);
// Первые 10 чисел
Stream.iterate(0, n -> n + 1)
.limit(10)
.forEach(System.out::println); // 0, 1, 2, ..., 9
// Четные числа
Stream.iterate(0, n -> n + 2)
.limit(5)
.forEach(System.out::println); // 0, 2, 4, 6, 8
// Степени двойки
Stream.iterate(1, n -> n * 2)
.limit(10)
.forEach(System.out::println); // 1, 2, 4, 8, 16, ...
Форма с условием (Java 9+)
Синтаксис:
static <T> Stream<T> iterate(T seed, Predicate<T> hasNext, UnaryOperator<T> next)
// Числа от 0 до 9 (эквивалент for loop)
Stream.iterate(0, n -> n < 10, n -> n + 1)
.forEach(System.out::println);
// Фибоначчи до 100
Stream.iterate(new int[]{0, 1},
f -> f[0] < 100,
f -> new int[]{f[1], f[0] + f[1]})
.map(f -> f[0])
.forEach(System.out::println); // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89
Примеры последовательностей:
// Геометрическая прогрессия
Stream.iterate(1.0, n -> n * 1.5)
.limit(5)
.forEach(System.out::println); // 1.0, 1.5, 2.25, 3.375, 5.0625
// Строки
Stream.iterate("a", s -> s + "a")
.limit(5)
.forEach(System.out::println); // a, aa, aaa, aaaa, aaaaa
// Даты
Stream.iterate(LocalDate.now(), date -> date.plusDays(1))
.limit(7)
.forEach(System.out::println); // Следующие 7 дней
7. IntStream, LongStream, DoubleStream методы
IntStream.range() и IntStream.rangeClosed()
// range(start, end) - exclusive end
IntStream.range(1, 5)
.forEach(System.out::println); // 1, 2, 3, 4
// rangeClosed(start, end) - inclusive end
IntStream.rangeClosed(1, 5)
.forEach(System.out::println); // 1, 2, 3, 4, 5
// Полезно для циклов
IntStream.range(0, 10)
.map(i -> i * i)
.forEach(System.out::println); // Квадраты: 0, 1, 4, 9, ..., 81
LongStream.range() и LongStream.rangeClosed()
// Для long значений
LongStream.range(1L, 1000000L)
.parallel()
.sum();
LongStream.rangeClosed(1L, 100L)
.forEach(System.out::println);
Создание из массивов примитивов
// IntStream
IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
// LongStream
LongStream longStream = LongStream.of(10L, 20L, 30L);
// DoubleStream
DoubleStream doubleStream = DoubleStream.of(1.1, 2.2, 3.3);
Генерация примитивных стримов
// IntStream.generate()
IntStream randomInts = IntStream.generate(() -> (int)(Math.random() * 100))
.limit(10);
// IntStream.iterate()
IntStream evenNumbers = IntStream.iterate(0, n -> n + 2)
.limit(10); // 0, 2, 4, ..., 18
8. Stream.builder() - Построитель stream
// Создание builder
Stream.Builder<String> builder = Stream.builder();
// Добавление элементов
builder.add("a");
builder.add("b");
builder.add("c");
// Построение stream
Stream<String> stream = builder.build();
// Или в цепочке
Stream<Integer> stream = Stream.<Integer>builder()
.add(1)
.add(2)
.add(3)
.build();
// Пример динамического построения
Stream.Builder<String> builder = Stream.builder();
for (String name : names) {
if (name.length() > 3) {
builder.add(name);
}
}
Stream<String> filteredStream = builder.build();
Примитивные builders:
// IntStream.Builder
IntStream.Builder intBuilder = IntStream.builder();
intBuilder.add(1).add(2).add(3);
IntStream intStream = intBuilder.build();
// LongStream.Builder
LongStream longStream = LongStream.builder()
.add(10L)
.add(20L)
.build();
// DoubleStream.Builder
DoubleStream doubleStream = DoubleStream.builder()
.add(1.5)
.add(2.5)
.build();
9. Stream.concat() - Объединение stream’ов
Stream<String> stream1 = Stream.of("a", "b", "c");
Stream<String> stream2 = Stream.of("d", "e", "f");
// Объединение
Stream<String> combined = Stream.concat(stream1, stream2);
combined.forEach(System.out::println); // a, b, c, d, e, f
// Объединение нескольких stream'ов
Stream<String> result = Stream.concat(
Stream.concat(stream1, stream2),
stream3
);
// Или через flatMap (рекомендуется для >2 стримов)
Stream<String> result = Stream.of(stream1, stream2, stream3)
.flatMap(s -> s);
Примеры:
// Объединение коллекций
List<Integer> list1 = Arrays.asList(1, 2, 3);
List<Integer> list2 = Arrays.asList(4, 5, 6);
Stream<Integer> combined = Stream.concat(
list1.stream(),
list2.stream()
);
// Объединение с примитивными стримами
IntStream combined = IntStream.concat(
IntStream.range(1, 5),
IntStream.range(10, 15)
); // 1, 2, 3, 4, 10, 11, 12, 13, 14
10. Из строк - String методы
chars() - IntStream символов
String text = "Hello";
IntStream chars = text.chars();
chars.forEach(c -> System.out.print((char)c + " ")); // H e l l o
// Подсчет гласных
long vowelCount = "Hello World".chars()
.filter(c -> "aeiouAEIOU".indexOf(c) != -1)
.count(); // 3
// Преобразование в символы
Stream<Character> charStream = "test".chars()
.mapToObj(c -> (char) c);
codePoints() - IntStream кодовых точек Unicode
String emoji = "Hello 😀 World 🌍";
IntStream codePoints = emoji.codePoints();
codePoints.forEach(cp -> System.out.print(
Character.getName(cp) + " "
));
lines() - Stream строк (Java 11+)
String multiline = "line1\nline2\nline3";
Stream<String> lines = multiline.lines();
lines.forEach(System.out::println);
// line1
// line2
// line3
11. Из файлов - Files и BufferedReader
Files.lines() - Читать строки из файла
import java.nio.file.Files;
import java.nio.file.Paths;
// Чтение всех строк файла
try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) {
lines.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
// С указанием кодировки
try (Stream<String> lines = Files.lines(Paths.get("file.txt"),
StandardCharsets.UTF_8)) {
long count = lines.filter(line -> line.contains("Java"))
.count();
System.out.println("Lines with 'Java': " + count);
}
Важно: Stream нужно закрывать (try-with-resources)!
BufferedReader.lines()
BufferedReader reader = new BufferedReader(
new FileReader("file.txt")
);
Stream<String> lines = reader.lines();
try (Stream<String> stream = lines) {
stream.filter(line -> !line.isEmpty())
.forEach(System.out::println);
}
Files.walk() - Обход дерева файлов
// Рекурсивный обход директории
try (Stream<Path> paths = Files.walk(Paths.get("src"))) {
paths.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".java"))
.forEach(System.out::println);
}
// С ограничением глубины
try (Stream<Path> paths = Files.walk(Paths.get("src"), 2)) {
// Только 2 уровня вглубь
paths.forEach(System.out::println);
}
Files.list() - Список файлов в директории
// Не рекурсивный (только текущая директория)
try (Stream<Path> paths = Files.list(Paths.get("."))) {
paths.filter(Files::isDirectory)
.forEach(System.out::println);
}
Files.find() - Поиск файлов с условием
try (Stream<Path> paths = Files.find(
Paths.get("src"),
Integer.MAX_VALUE, // max depth
(path, attrs) -> path.toString().endsWith(".java")
)) {
paths.forEach(System.out::println);
}
12. Random числа - Random класс
Random.ints()
Random random = new Random();
// Бесконечный stream случайных int
IntStream infiniteInts = random.ints();
// Ограниченное количество
IntStream tenInts = random.ints(10); // 10 случайных int
// С диапазоном [origin, bound)
IntStream boundedInts = random.ints(10, 0, 100); // 10 чисел от 0 до 99
// Использование
random.ints(5, 1, 11) // 5 чисел от 1 до 10
.forEach(System.out::println);
Random.longs()
Random random = new Random();
// Случайные long числа
LongStream longs = random.longs(5);
// С границами
LongStream boundedLongs = random.longs(10, 0L, 1000L);
Random.doubles()
Random random = new Random();
// Случайные double числа [0.0, 1.0)
DoubleStream doubles = random.doubles(10);
// С границами
DoubleStream boundedDoubles = random.doubles(5, 0.0, 100.0);
// Пример: генерация случайных цен
random.doubles(10, 9.99, 99.99)
.forEach(price -> System.out.printf("$%.2f%n", price));
ThreadLocalRandom (для многопоточности)
// Лучше для многопоточных приложений
IntStream randomInts = ThreadLocalRandom.current()
.ints(10, 0, 100);
13. Pattern.splitAsStream() - Разделение строки
import java.util.regex.Pattern;
String text = "one,two,three,four";
Pattern pattern = Pattern.compile(",");
Stream<String> parts = pattern.splitAsStream(text);
parts.forEach(System.out::println);
// one
// two
// three
// four
// Сложная регулярка
Pattern whitespace = Pattern.compile("\\s+");
Stream<String> words = whitespace.splitAsStream("Hello World Test");
words.forEach(System.out::println); // Hello, World, Test
14. BitSet.stream() - Stream установленных битов
import java.util.BitSet;
BitSet bitSet = new BitSet();
bitSet.set(1);
bitSet.set(3);
bitSet.set(5);
bitSet.set(7);
// Stream индексов установленных битов
IntStream indices = bitSet.stream();
indices.forEach(System.out::println); // 1, 3, 5, 7
15. JarFile.stream() - Stream записей JAR файла
import java.util.jar.JarFile;
try (JarFile jar = new JarFile("library.jar")) {
Stream<JarEntry> entries = jar.stream();
entries.filter(entry -> entry.getName().endsWith(".class"))
.forEach(entry -> System.out.println(entry.getName()));
}
16. Optional.stream() - Stream из Optional (Java 9+)
Optional<String> optional = Optional.of("value");
// Преобразование Optional в Stream
Stream<String> stream = optional.stream();
stream.forEach(System.out::println); // value
// Пустой Optional -> пустой Stream
Optional<String> empty = Optional.empty();
Stream<String> emptyStream = empty.stream(); // Пустой stream
// Полезно для flatMap
List<Optional<String>> optionals = Arrays.asList(
Optional.of("a"),
Optional.empty(),
Optional.of("b")
);
Stream<String> values = optionals.stream()
.flatMap(Optional::stream);
values.forEach(System.out::println); // a, b (empty пропущен)
17. Collection специфичные методы
Map.entrySet().stream()
Map<String, Integer> map = Map.of(
"one", 1,
"two", 2,
"three", 3
);
// Stream из Entry
Stream<Map.Entry<String, Integer>> entries = map.entrySet().stream();
// Обработка
map.entrySet().stream()
.filter(entry -> entry.getValue() > 1)
.forEach(entry -> System.out.println(
entry.getKey() + ": " + entry.getValue()
));
Map.keySet().stream()
Stream<String> keys = map.keySet().stream();
keys.forEach(System.out::println); // one, two, three
Map.values().stream()
Stream<Integer> values = map.values().stream();
int sum = values.mapToInt(Integer::intValue).sum();
18. StreamSupport - Низкоуровневое создание
Из Spliterator
import java.util.stream.StreamSupport;
List<String> list = Arrays.asList("a", "b", "c");
Spliterator<String> spliterator = list.spliterator();
Stream<String> stream = StreamSupport.stream(spliterator, false);
// ^^^^
// parallel?
Из Supplier
Supplier<Spliterator<String>> supplier = () -> list.spliterator();
Stream<String> stream = StreamSupport.stream(supplier,
Spliterator.ORDERED,
false);
Преобразование между типами Stream
Объектный Stream → Примитивный Stream
// Stream<Integer> -> IntStream
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
IntStream intStream = integerStream.mapToInt(Integer::intValue);
// Stream<Long> -> LongStream
Stream<Long> longObjStream = Stream.of(1L, 2L, 3L);
LongStream longStream = longObjStream.mapToLong(Long::longValue);
// Stream<Double> -> DoubleStream
Stream<Double> doubleObjStream = Stream.of(1.0, 2.0, 3.0);
DoubleStream doubleStream = doubleObjStream.mapToDouble(Double::doubleValue);
Примитивный Stream → Объектный Stream
// IntStream -> Stream<Integer>
IntStream intStream = IntStream.range(1, 5);
Stream<Integer> boxedStream = intStream.boxed();
// LongStream -> Stream<Long>
LongStream longStream = LongStream.range(1, 5);
Stream<Long> boxedLongs = longStream.boxed();
// DoubleStream -> Stream<Double>
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0);
Stream<Double> boxedDoubles = doubleStream.boxed();
// Через mapToObj
IntStream intStream = IntStream.range(1, 5);
Stream<String> strings = intStream.mapToObj(i -> "Number: " + i);
Между примитивными Stream
// IntStream -> LongStream
IntStream intStream = IntStream.range(1, 5);
LongStream longStream = intStream.asLongStream();
// IntStream -> DoubleStream
IntStream intStream2 = IntStream.range(1, 5);
DoubleStream doubleStream = intStream2.asDoubleStream();
// LongStream -> DoubleStream
LongStream longStream2 = LongStream.range(1, 5);
DoubleStream doubleStream2 = longStream2.asDoubleStream();
Практические примеры создания Stream
1. Чтение CSV файла
try (Stream<String> lines = Files.lines(Paths.get("data.csv"))) {
lines.skip(1) // Пропустить заголовок
.map(line -> line.split(","))
.forEach(columns -> System.out.println(
"Name: " + columns[0] + ", Age: " + columns[1]
));
}
2. Генерация тестовых данных
// 100 случайных пользователей
List<User> users = Stream.generate(() -> new User(
UUID.randomUUID().toString(),
"User" + ThreadLocalRandom.current().nextInt(1000),
ThreadLocalRandom.current().nextInt(18, 80)
))
.limit(100)
.collect(Collectors.toList());
3. Последовательность дат
// Все дни текущего месяца
LocalDate start = LocalDate.now().withDayOfMonth(1);
LocalDate end = start.plusMonths(1);
Stream<LocalDate> datesInMonth = Stream.iterate(start,
date -> date.isBefore(end),
date -> date.plusDays(1));
datesInMonth.forEach(System.out::println);
4. Объединение данных из разных источников
// Из базы данных
Stream<User> dbUsers = userRepository.findAll().stream();
// Из файла
Stream<User> fileUsers = Files.lines(Paths.get("users.txt"))
.map(User::fromString);
// Из API
Stream<User> apiUsers = apiClient.getUsers().stream();
// Объединение
Stream<User> allUsers = Stream.of(dbUsers, fileUsers, apiUsers)
.flatMap(s -> s)
.distinct(); // Удалить дубликаты
5. Фильтрация файлов
// Найти все .java файлы больше 1KB
try (Stream<Path> paths = Files.walk(Paths.get("src"))) {
paths.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".java"))
.filter(p -> {
try {
return Files.size(p) > 1024;
} catch (IOException e) {
return false;
}
})
.forEach(System.out::println);
}
6. Генерация последовательности Фибоначчи
// Первые 20 чисел Фибоначчи
Stream.iterate(new BigInteger[]{BigInteger.ZERO, BigInteger.ONE},
f -> new BigInteger[]{f[1], f[0].add(f[1])})
.limit(20)
.map(f -> f[0])
.forEach(System.out::println);
7. Обработка логов
try (Stream<String> logs = Files.lines(Paths.get("app.log"))) {
// Подсчет ошибок
long errorCount = logs.filter(line -> line.contains("ERROR"))
.count();
System.out.println("Total errors: " + errorCount);
}
Параллельные Stream
Создание параллельного stream
// Из коллекции
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> parallelStream = numbers.parallelStream();
// Преобразование последовательного в параллельный
Stream<Integer> stream = numbers.stream();
Stream<Integer> parallel = stream.parallel();
// Проверка режима
boolean isParallel = stream.isParallel();
// Обратно в последовательный
Stream<Integer> sequential = parallel.sequential();
Когда использовать параллельный stream?
✅ Используй параллельный stream когда:
- Большой объем данных (>10000 элементов)
- Операции CPU-intensive
- Операции независимы (stateless)
- Нет гонки данных (race conditions)
❌ НЕ используй параллельный stream когда:
- Маленький набор данных
- I/O операции
- Операции с shared state
- Порядок элементов важен
Пример:
// Хорошо для параллельной обработки
List<Integer> largeList = IntStream.range(0, 1_000_000)
.boxed()
.collect(Collectors.toList());
long sum = largeList.parallelStream()
.mapToLong(i -> i * i)
.sum();
// Плохо для параллельной обработки (I/O)
files.parallelStream() // ❌ Не рекомендуется
.forEach(file -> writeToDatabase(file));
Best Practices
✅ DO (рекомендуется):
-
Используй try-with-resources для файловых stream’ов
try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) { lines.forEach(System.out::println); } -
Используй примитивные stream’ы для примитивов
IntStream.range(1, 100).sum(); // ✅ Эффективно Stream.of(1, 2, 3).mapToInt(i -> i).sum(); // ❌ Избыточно -
Ограничивай бесконечные stream’ы
Stream.iterate(0, n -> n + 1) .limit(100) // ✅ Ограничение обязательно .forEach(System.out::println); -
Используй метод reference когда возможно
stream.map(String::toUpperCase) // ✅ stream.map(s -> s.toUpperCase()) // Работает, но менее идиоматично -
Закрывай stream’ы из Files
try (Stream<String> lines = Files.lines(path)) { // обработка } // Автоматически закроется
❌ DON’T (не рекомендуется):
-
Не переиспользуй stream
Stream<String> stream = list.stream(); stream.forEach(System.out::println); stream.forEach(System.out::println); // ❌ IllegalStateException! -
Не модифицируй источник во время обработки
list.stream() .forEach(item -> list.add(item)); // ❌ ConcurrentModificationException -
Не используй parallelStream() бездумно
smallList.parallelStream() // ❌ Overhead > benefit .filter(...) .collect(Collectors.toList()); -
Не забывай про limit() для бесконечных stream’ов
Stream.generate(Math::random) .forEach(System.out::println); // ❌ Бесконечный цикл! -
Не используй peek() для изменения элементов
stream.peek(list::add) // ❌ Не гарантируется выполнение .collect(Collectors.toList()); // ✅ Используй map или forEach
Сводная таблица способов создания
| Источник | Метод | Пример |
|---|---|---|
| Collection | .stream() | list.stream() |
| Collection | .parallelStream() | list.parallelStream() |
| Массив | Arrays.stream() | Arrays.stream(array) |
| Элементы | Stream.of() | Stream.of(1, 2, 3) |
| Пустой | Stream.empty() | Stream.empty() |
| Генератор | Stream.generate() | Stream.generate(Math::random) |
| Итерация | Stream.iterate() | Stream.iterate(0, n -> n + 1) |
| Диапазон | IntStream.range() | IntStream.range(1, 10) |
| Builder | Stream.builder() | Stream.builder().add(1).build() |
| Конкатенация | Stream.concat() | Stream.concat(s1, s2) |
| Файл | Files.lines() | Files.lines(path) |
| Директория | Files.walk() | Files.walk(path) |
| Случайные числа | Random.ints() | new Random().ints(10) |
| Строка | .chars() | "text".chars() |
| Регулярка | Pattern.splitAsStream() | pattern.splitAsStream(text) |
| Optional | .stream() | optional.stream() |
Заключение
Ключевые моменты:
- Stream - это абстракция для работы с последовательностями данных
- Существует много способов создания stream’ов
- Примитивные stream’ы (IntStream, LongStream, DoubleStream) эффективнее
- Stream’ы одноразовые - после использования нужно создавать новый
- Ленивые вычисления - промежуточные операции выполняются только при терминальной
- Параллельные stream’ы эффективны только для больших данных
- Файловые stream’ы нужно закрывать
Выбор способа создания:
Коллекция? → collection.stream()
Массив? → Arrays.stream(array)
Известные элементы? → Stream.of(...)
Нужен диапазон? → IntStream.range(start, end)
Генерация данных? → Stream.generate() или Stream.iterate()
Файл? → Files.lines(path)
Случайные числа? → Random.ints()
Строка? → string.chars() или string.lines()
Stream API - мощный инструмент для функционального программирования в Java!