Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

2.3.1. Создание стримов

Stream.of(), Collection.stream(), Arrays.stream()

Материалы

ТипСсылка
Документссылка
Видеоссылка

Что такое Stream?

Stream - это последовательность элементов, поддерживающая последовательные и параллельные агрегатные операции.

Ключевые характеристики Stream

  1. No storage (Не хранит данные)

    • Stream не является структурой данных
    • Передает элементы из источника через pipeline операций
  2. Functional in nature (Функциональная природа)

    • Операции не модифицируют источник
    • Создают новый stream с результатом
  3. Laziness-seeking (Ленивые вычисления)

    • Промежуточные операции выполняются только при терминальной операции
    • Оптимизация вычислений
  4. Possibly unbounded (Могут быть бесконечными)

    • Stream может быть бесконечным
    • Short-circuiting операции позволяют завершить обработку
  5. 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 (рекомендуется):

  1. Используй try-with-resources для файловых stream’ов

    try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) {
        lines.forEach(System.out::println);
    }
    
  2. Используй примитивные stream’ы для примитивов

    IntStream.range(1, 100).sum();  // ✅ Эффективно
    Stream.of(1, 2, 3).mapToInt(i -> i).sum();  // ❌ Избыточно
    
  3. Ограничивай бесконечные stream’ы

    Stream.iterate(0, n -> n + 1)
          .limit(100)  // ✅ Ограничение обязательно
          .forEach(System.out::println);
    
  4. Используй метод reference когда возможно

    stream.map(String::toUpperCase)  // ✅
    stream.map(s -> s.toUpperCase())  // Работает, но менее идиоматично
    
  5. Закрывай stream’ы из Files

    try (Stream<String> lines = Files.lines(path)) {
        // обработка
    }  // Автоматически закроется
    

❌ DON’T (не рекомендуется):

  1. Не переиспользуй stream

    Stream<String> stream = list.stream();
    stream.forEach(System.out::println);
    stream.forEach(System.out::println);  // ❌ IllegalStateException!
    
  2. Не модифицируй источник во время обработки

    list.stream()
        .forEach(item -> list.add(item));  // ❌ ConcurrentModificationException
    
  3. Не используй parallelStream() бездумно

    smallList.parallelStream()  // ❌ Overhead > benefit
             .filter(...)
             .collect(Collectors.toList());
    
  4. Не забывай про limit() для бесконечных stream’ов

    Stream.generate(Math::random)
          .forEach(System.out::println);  // ❌ Бесконечный цикл!
    
  5. Не используй 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)
BuilderStream.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()

Заключение

Ключевые моменты:

  1. Stream - это абстракция для работы с последовательностями данных
  2. Существует много способов создания stream’ов
  3. Примитивные stream’ы (IntStream, LongStream, DoubleStream) эффективнее
  4. Stream’ы одноразовые - после использования нужно создавать новый
  5. Ленивые вычисления - промежуточные операции выполняются только при терминальной
  6. Параллельные stream’ы эффективны только для больших данных
  7. Файловые 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!