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
История технологий сегодня — 23 Февраля

🇷🇺Россия — День защитника Отечества
🫡

День защитника Отечества — праздник, отмечаемый ежегодно 23 февраля в России, Беларуси, Казахстане, Кыргызстане, Таджикистане, а также в частично признанных Абхазии, Южной Осетии и непризнанной Приднестровской Молдавской Республике (ПМР).

23 февраля (н. ст.) 1918 года было опубликовано воззвание СНК от 21 февраля «Социалистическое отечество в опасности!», а также «Воззвание Военного главнокомандующего» Н. В. Крыленко.

Этот день был объявлен праздником в РСФСР 27 января 1922 года, когда Президиум ВЦИК РСФСР опубликовал постановление о четвёртой годовщине Красной армии, в котором говорилось: «В соответствии с постановлением IX Всероссийского съезда Советов о Красной армии Президиум ВЦИК обращает внимание исполкомов на наступающую годовщину создания Красной армии (23 февраля)».


С 1922 года в СССР эта дата ежегодно традиционно отмечалась как «День Красной армии», с 1946 года — «День Советской армии», с 1949 по 1992 годы — «День Советской армии и Военно-морского флота».


ℹ️ Кто родился в этот день

Деррик Генри «Дик» Лемер (англ. Derrick Henry Lehmer; 23 февраля 1905, Беркли (Калифорния) — 22 мая 1991, Беркли (Калифорния)) — американский математик, усовершенствовавший работу Эдуарда Люка в 1930-е годы и разработавший Тест Люка — Лемера для простых чисел Мерсенна. Карьера Лемера развивалась в области теории чисел. Во время Великой Депрессии он со своей женой был вынужден сменить множество профессий как в Соединённых Штатах, так и за рубежом, что в конечном итоге случайно привело его в центр исследований в области ранней электронной вычислительной техники.

Роберт Николас Кристиан Топала (англ. Robert Nicholas Christian Topala; род. 23 февраля 1987, Уппландс Весбю, Швеция), более известный как RobTop, — независимый шведский разработчик видеоигр, аниматор и музыкант, прославившийся платформером Geometry Dash.

Аллан Маклауд Кормак (англ. Allan McLeod Cormack; 23 февраля 1924, Йоханнесбург, Южная Африка — 7 мая 1998, Уинчестер, штат Массачусетс, США) — южноафриканский и американский физик, лауреат Нобелевской премии по физиологии и медицине 1979 года «за разработку компьютерной томографии», которую он получил с Годфри Хаунсфилдом.


🌐 Знаковые события

1987 — вспышка сверхновой SN 1987A достигла Земли. Это самая близкая сверхновая со времён изобретения телескопа.

1993 — создан язык программирования Ruby.


#Biography #Birth_Date #Events #23февраля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
[Совет по Java #008]

Тема: Не ловите Exception или Throwable без крайней необходимости. Ловите максимально конкретные исключения.

Проблема: Перехват общих типов исключений, таких как Exception или Throwable, является грубой ошибкой, нарушающей принцип обработки исключений.

Это приводит к тому, что блок catch обрабатывает не только ожидаемые исключения (например, IOException, SQLException), но и непредвиденные, фатальные ошибки, которые сигнализируют о серьезных сбоях в работе JVM. К таким ошибкам относятся NullPointerException, ArrayIndexOutOfBoundsException (программные ошибки), а также OutOfMemoryError, StackOverflowError (ошибки виртуальной машины).

Перехватывая Exception, разработчик маскирует баги, которые должны были привести к аварийному завершению программы и последующей диагностике. Вместо этого программа продолжает работу в некорректном состоянии, что может привести к повреждению данных, неконсистентности состояния и крайне сложной отладке.

Решение: Всегда перехватывайте максимально конкретные типы исключений, которые вы ожидаете и способны корректно обработать.

Используйте несколько блоков catch для разных типов исключений, располагая их от наиболее конкретных к наиболее общим. Если необходимо выполнить общие действия (например, логирование), используйте multi-catch (Java 7+) для объединения родственных типов исключений. Никогда не перехватывайте Throwable, если только вы не пишете код на самом верхнем уровне (например, пул потоков), где требуется гарантированно избежать "проглатывания" ошибок и обеспечить логирование фатальных сбоев перед завершением потока.

import java.io.*;
import java.sql.*;

public class ExceptionHandling {

//Антипаттерн
public void bad(String path) {
try {
BufferedReader reader = new BufferedReader(new FileReader(path));
} catch (Exception e) { // Ловит IOException, NPE, OOM
e.printStackTrace(); // Фатальные ошибки "проглочены"
}
}

//Правильно
public void good(String path) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
String line = reader.readLine();
} catch (FileNotFoundException e) {
System.err.println("Файл не найден: " + path);
// Это восстановимая ситуация
}
// IOException пробрасывается выше
// NPE, OOM не ловятся — пусть падают
}

//Multi-catch
public void multi(String path) {
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
} catch (FileNotFoundException | ClassNotFoundException e) {
System.err.println("Ресурс не найден: " + e.getMessage());
} catch (IOException e) {
throw new RuntimeException("Ошибка ввода-вывода", e);
}
}

//Единственный случай для Throwable
public void thread(Runnable task) {
new Thread(() -> {
try {
task.run();
} catch (Throwable t) { // Только для логирования на верхнем уровне
System.err.println("Критическая ошибка: " + t);
}
}).start();
}
}


Объяснение: Иерархия исключений делится на проверяемые (восстановимые) и непроверяемые (ошибки программирования и JVM).

Перехватывая Exception, вы стираете эту границу. Правило простое: лови только то, что можешь обработать. FileNotFoundException можно обработать — запросить другой файл. NullPointerException обрабатывать нельзя — нужно исправлять код. OutOfMemoryError тем более не для catch — JVM в критическом состоянии.

Разработчик всегда задает вопрос: "Я могу это исправить?" — и если нет, не ловит.


#Java #советы
👍5🤯1
Что выведет код?

public class Task230226 {
public static void main(String[] args) {
try {
throw new StackOverflowError();
} catch (Error e) {
System.out.println("Error caught");
} catch (Exception e) {
System.out.println("Exception caught");
} catch (Throwable e) {
System.out.println("Throwable caught");
}
}
}


#Tasks
👍1
7. Введение в распределённую трассировку. OpenTelemetry & Jaeger.

Когда у вас один сервис — всё просто. Но как только появляется второй, вы слепнете.
Почему запрос тормозит? Кто виноват: ваш код или внешний сервис?
Метрики врут, логи разрозненны. Нужна распределённая трассировка
. 🤓

В этом видео мы шаг за шагом разберём, как работает трассировка на реальном примере:
два микросервиса (order-service и notification-service), HTTP-вызовы, OpenTelemetry и Jaeger.

Что вы узнаете:
- Как устроена распределённая трассировка: trace, span, traceId, propagation
- Как настроить OpenTelemetry в Spring Boot 3 с Micrometer Tracing
- Как поднять Jaeger в Docker и отправлять в него трассы
- Как анализировать трассу: parent-child связи, длительность, теги
- Почему в трассе три спана, хотя сервисов два
- Как добавить бизнес-атрибуты (
order.id) и искать по ним в Jaeger

Исходный код проекта на GitHub очень ждет Ваших звезд ☺️

Ссылка на Youtube
Ссылка на Рутьюб

Смотрите, ставьте лайки, подписывайтесь на каналы!✌️
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥11
Как работает цикл for-each? 🤓

Ответ:

for-each (или enhanced for loop)
— это упрощенная форма цикла for для итерации по массивам и коллекциям, реализующим интерфейс Iterable.

Синтаксис: for (Тип переменная : коллекция) { ... }.

На каждой итерации в переменную автоматически помещается следующий элемент. Преимущество — краткость и отсутствие ошибок с индексами. Недостатки — нет доступа к индексу элемента, нельзя удалять элементы коллекции (вызовет ConcurrentModificationException) и изменять структуру коллекции во время итерации.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
История технологий сегодня — 24 Февраля

ℹ️ Кто родился в этот день

Сти́вен Пол (Стив) Джобс (англ. Steven Paul «Steve» Jobs; имя при рождении — Абдул Латиф Джандали; 24 февраля 1955, Сан-Франциско, Калифорния — 5 октября 2011, Пало-Алто, Санта-Клара, Калифорния)американский предприниматель, изобретатель и промышленный дизайнер, получивший широкое признание в качестве пионера эры информационных технологий. Один из основателей, председатель совета директоров и CEO корпорации Apple. Один из основателей и CEO киностудии Pixar.

Ян Борисович Кум (род. 24 февраля 1976, Киев) — американский предприниматель и программист, сооснователь и CEO мессенджера WhatsApp.

Григо́рий Алекса́ндрович Маргу́лис (род. 24 февраля 1946, Москва) советский и американский математик, доктор физико-математических наук (1978), научный сотрудник Института проблем передачи информации РАН, профессор Йельского университета (США), лауреат Филдсовской (1978), Абелевской (2020) премий и премии Вольфа (2004/05).


🌐 Знаковые события

Не нашел(


#Biography #Birth_Date #Events #24февраля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Please open Telegram to view this post
VIEW IN TELEGRAM
Раздел 8. Stream API и функциональный стиль в Java

Глава 3: Чистые преобразования и борьба с исключениями

Side Effects — главный враг предсказуемости

Самый соблазнительный и опасный антипаттерн при работе со Stream API — использование forEach для накопления результатов во внешнюю коллекцию. Код выглядит лаконично, интуитивно понятен разработчикам с императивным бэкграундом, но несёт в себе фундаментальные архитектурные дефекты.

// Антипаттерн: мутация внешнего списка из потока
List<String> names = new ArrayList<>();
users.stream()
.filter(User::isActive)
.forEach(u -> names.add(u.getName())); // Побочный эффект


На первый взгляд, это работает. В тестовом окружении с небольшими данными результат будет корректным. Но этот код нарушает базовый контракт функционального программирования: операции над потоком должны быть чистыми функциями без побочных эффектов.

Побочный эффект (side effect) — любое изменение состояния, наблюдаемое за пределами операции. В данном случае names.add() модифицирует состояние объекта names, созданного вне лямбды. Лямбда захватывает ссылку на внешний список и мутирует его.


Три уровня проблемы

Проблема первая: некорректность при параллелизме

Главное преимущество Stream API — возможность прозрачного распараллеливания вычислений вызовом .parallel().

Но код с побочными эффектами ломается при этом:
List<String> names = new ArrayList<>();
users.parallelStream() // Добавили параллелизм
.filter(User::isActive)
.forEach(u -> names.add(u.getName())); // Race condition!

// Результат: потерянные элементы, дублирование, ArrayIndexOutOfBoundsException


ArrayList не потокобезопасен. При одновременном доступе из нескольких потоков происходит состояние гонки (race condition): два потока читают текущий размер, вычисляют индекс вставки, один записывает, второй перезаписывает ту же позицию. Результат недетерминирован: часть элементов пропадает, часть дублируется, возможны исключения при расширении внутреннего массива.

Синхронизация вручную решает проблему, но уничтожает производительность:
List<String> names = Collections.synchronizedList(new ArrayList<>());
// или names = new CopyOnWriteArrayList<>(); // ещё хуже по производительности


CopyOnWriteArrayList создаёт копию всего массива при каждой записи — для потоковой обработки это катастрофа.

Проблема вторая: скрытая зависимость и невозможность тестирования

Код с побочными эффектами тесно связывает логику обработки с механизмом накопления. Функцию u -> names.add(u.getName()) невозможно протестировать изолированно: она требует существования внешнего names, модифицирует его, не возвращает значения.

Чистая функция User::getName тестируется просто: дали пользователя, получили строку. Лямбда с побочным эффектом требует подготовки окружения, проверки состояния после выполнения, очистки между тестами. Сложность тестирования растёт экспоненциально с количеством захваченных переменных.

Проблема третья: утрата композиционности

Потоковые операции предназначены для композиции. Результат одной операции — вход другой. Но forEach — терминальная операция, возвращающая void.

Она разрывает цепочку, превращая поток в императивный блок:
// Не компонуется: forEach возвращает void
users.stream()
.filter(User::isActive)
.forEach(u -> names.add(u.getName())); // Конец цепочки
// .map(String::toUpperCase) // Невозможно: forEach уже выполнен


Если позже потребуется дополнительная обработка (привести к верхнему регистру, отсортировать, убрать дубликаты), придётся либо модифицировать лямбду в forEach (нарушая единственную ответственность), либо создавать новый поток из names (лишние аллокации, потеря ленивости).


#Java #для_новичков #beginner #stream_api #side_effects
👍4
forEach: последнее средство, не первое

Метод forEach предназначен для конечных действий (terminal side effects), не для агрегации данных. Его корректное применение — операции, которые по своей природе требуют побочных эффектов и уже учитывают многопоточность:
// Корректное использование: логирование
orders.stream()
.filter(Order::isUrgent)
.forEach(o -> logger.info("Срочный заказ: {}", o.getId()));

// Корректное использование: запись в потокобезопасный sink
processedItems.parallelStream()
.forEach(database::save); // database.save потокобезопасен

// Корректное использование: отправка сообщений в брокер
events.stream()
.forEach(kafkaTemplate::send);


В этих случаях побочный эффект — сама цель операции. Мы не накапливаем данные для дальнейшей обработки, а выполняем окончательное действие над каждым элементом.
collect: правильный путь агрегации

Для накопления результатов Stream API предоставляет collect — мощную и гибкую терминальную операцию, инкапсулирующую стратегию свёртки потока в конкретную структуру данных.
// Правильно: агрегация через collect
List<String> names = users.stream()
.filter(User::isActive)
.map(User::getName) // Чистая трансформация
.collect(Collectors.toList()); // Инкапсулированная мутация внутри коллектора


Здесь мутация происходит внутри Collector, не видима снаружи, потокобезопасна при параллельном выполнении (через механизм комбайнеров), композиционна — результат можно передать дальше.

Как работает безопасность collect в параллельном потоке:
List<String> names = users.parallelStream()
.filter(User::isActive)
.map(User::getName)
.collect(Collectors.toList()); // Корректно при parallel!


Коллектор toList() использует стратегию "разделяй и властвуй": поток делится на сегменты, каждый обрабатывается в своём потоке с локальным ArrayList (аккумулятор), затем локальные списки комбинируются в один результат. Никакой совместной мутации, никаких блокировок — только локальные изменения и финальное слияние.


Мутация внутри map и filter: скрытая бомба

Побочные эффекты опасны не только в forEach.

Любая промежуточная операция с мутацией внешнего состояния создаёт непредсказуемое поведение:
// Антипаттерн: мутация в map
AtomicInteger counter = new AtomicInteger(0);
List<Integer> numbered = items.stream()
.map(item -> {
int num = counter.incrementAndGet(); // Побочный эффект!
return num + ": " + item;
})
.collect(toList());

// При parallelStream() нумерация будет хаотичной и пропущенной


Здесь map используется не для преобразования элемента, а для генерации глобального счётчика. Это нарушает чистоту функции: результат зависит не только от входа, но от внешнего состояния и порядка выполнения.

Решение — встроить нумерацию в структуру данных или использовать специализированные операции:
// Правильно: явная нумерация через индекс
List<String> numbered = IntStream.range(0, items.size())
.mapToObj(i -> (i + 1) + ": " + items.get(i))
.collect(toList());

// Или через StreamUtils сторонних библиотек с zipWithIndex



Идентификация побочных эффектов

Признаки, что код содержит опасные побочные эффекты:
- Лямбда не возвращает значение, но делает что-то "полезное": x -> list.add(x), x -> map.put(x.getKey(), x)
- Захват изменяемых внешних переменных: лямбда использует переменные, объявленные до неё, и вызывает на них модифицирующие методы
- Необходимость очистки состояния между запусками: тесты требуют list.clear() или создания новых объектов
- Недетерминированные результаты при параллельном выполнении: одинаковый вход даёт разный выход
- Рефакторинг от побочных эффектов к чистым функциям следует шаблону: вынести мутацию в collect, преобразования в map, фильтрацию в filter, а forEach оставить только для истинно терминальных действий.


#Java #для_новичков #beginner #stream_api #side_effects
🔥3👍2
Что выведет код?


import java.util.*;
import java.util.concurrent.*;

public class Task240226 {
public static void main(String[] args) {
List<Integer> source = Arrays.asList(1, 2, 3, 4, 5);
CopyOnWriteArrayList<Integer> target = new CopyOnWriteArrayList<>();

long count = source.parallelStream()
.filter(n -> {
target.add(n);
return n % 2 == 0;
})
.count();

System.out.println(target.size() + ":" + count);
}
}


#Tasks
👍2
👍4
Какие виды ссылок в Java вы знаете? 🤓

Ответ:

В Java существует 4 типа ссылок:

Strong Reference (сильная) — обычные ссылки (Object obj = new Object()), объект не будет собран GC, пока на него есть сильная ссылка.

Soft Reference (мягкая) — объект будет собран только перед OutOfMemoryError, если памяти не хватает (используется для кэшей).

Weak Reference (слабая) — объект будет собран при ближайшей сборке мусора, если на него нет сильных или мягких ссылок (используется в WeakHashMap).

Phantom Reference (фантомная) — позволяет узнать, что объект физически удален из памяти (используется для post-mortem cleanup).


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
История технологий сегодня — 25 Февраля

ℹ️ Кто родился в этот день

Лев Андре́евич Арцимо́вич (12 (25) февраля 1909, Москва — 1 марта 1973, там же) — советский физик, академик АН СССР (1953), Герой Социалистического Труда (1969).


🌐 Знаковые события

1992 — было образовано Российское космическое агентство.

2010 — в русской Википедии появилась юбилейная 500-тысячная статья.


#Biography #Birth_Date #Events #25февраля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
[Совет по Java #009]

Тема: Используйте Collections.emptyList(), Collections.singletonList() вместо создания новых коллекций, когда нужно вернуть пустой или одноэлементный список. Это экономит память.

Проблема: Создание новых экземпляров коллекций (например, new ArrayList<>()) для возврата пустого результата или списка с одним элементом приводит к неоправданному расходованию памяти и увеличению нагрузки на сборщик мусора.

При частых вызовах таких методов (в высоконагруженных системах) это создает тысячи лишних объектов, которые почти сразу становятся мусором. Особенно критично, когда пустой список возвращается как значение по умолчанию или при отсутствии данных.

Решение: Статические фабричные методы из класса Collections предоставляют иммутабельные (неизменяемые) реализации коллекций, которые переиспользуют один экземпляр для всех вызовов.

Collections.emptyList() возвращает константный пустой список (синглтон), а Collections.singletonList(T o) создает иммутабельный список ровно с одним элементом, оптимизированный по памяти. Эти методы гарантируют отсутствие аллокаций при каждом вызове. Важно помнить, что возвращаемые коллекции неизменяемы (immutable) — попытка модификации вызовет UnsupportedOperationException.

import java.util.*;

public class CollectionFactory {

private final List<String> data = Arrays.asList("a", "b", "c");

//Антипаттерн
public List<String> bad(boolean cond) {
if (!cond) return new ArrayList<>(); // Новый объект каждый раз
return new ArrayList<>(data);
}

//Правильно
public List<String> good(boolean cond) {
if (!cond) return Collections.emptyList(); // Один объект на все вызовы
if (data.size() == 1) return Collections.singletonList(data.get(0));
return Collections.unmodifiableList(data); // Защита от модификации
}

// Для Map и Set
public Map<String, String> emptyMap() {
return Collections.emptyMap();
}

public Set<Integer> singletonSet(Integer value) {
return value == null ? Collections.emptySet() : Collections.singleton(value);
}

// Java 9+ альтернатива
public List<String> java9() {
return List.of(); // Пустой список
// return List.of("one"); // С одним элементом
}

// Важно: иммутабельность
public void immutable() {
List<String> empty = Collections.emptyList();
// empty.add("x"); // UnsupportedOperationException!
}
}


Объяснение: Метод Collections.emptyList() возвращает ссылку на статический финальный экземпляр класса EmptyList (внутренняя реализация Collections).

Все вызовы этого метода во всем приложении используют один и тот же объект в памяти. Аналогично singletonList создает специализированную реализацию с одним полем для хранения элемента, без внутреннего массива. Это особенно важно в API, где методы часто возвращают пустые коллекции вместо null (что само по себе хорошая практика, исключающая NPE).

Начиная с Java 9, появились дополнительные фабричные методы List.of(), Set.of(), Map.of(), которые также возвращают иммутабельные коллекции и часто более читаемы.

Разработчик всегда выбирает между производительностью (переиспользование иммутабельных коллекций) и необходимостью модификации (тогда нужны new ArrayList<>()).


#Java #советы
👍3
Что выведет код?

import java.util.*;

public class Task250226 {
public static void main(String[] args) {
List<String> list1 = Collections.emptyList();
List<String> list2 = Collections.singletonList("hello");

boolean modifiable = false;
try {
list1.add("test");
} catch (Exception e) {
modifiable = true;
}

boolean nullElement = false;
try {
List<String> list3 = Collections.singletonList(null);
nullElement = list3.contains(null);
} catch (Exception e) {
nullElement = false;
}

System.out.println(modifiable + " " + nullElement);
}
}


#Tasks
👍2
Варианты ответа:
Anonymous Quiz
44%
true true
39%
true false
6%
false true
11%
false false
👍2
Что такое рефлексия (Reflection)? 🤓

Ответ:

Рефлексия (Reflection API)
— это механизм, позволяющий программе анализировать и изменять свое поведение во время выполнения.

С помощью рефлексии можно получать информацию о классах, интерфейсах, полях и методах, создавать объекты, вызывать методы и изменять значения полей динамически, даже если они приватные. Используется в фреймворках (Spring, Hibernate), IDE, тестовых библиотеках (JUnit).

Минусы: потеря производительности, нарушение инкапсуляции, проблемы с безопасностью.


#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
История технологий сегодня — 26 Февраля

ℹ️ Кто родился в этот день

Доминик Франсуа Жан Араго́ (фр. Dominique François Jean Arago), известный как Франсуа Араго (фр. François Arago; 26 февраля 1786, Эстажель, Королевство Франция — 2 октября 1853, Париж, Вторая французская империя) — французский физик, астроном и политический деятель. Открыл магнитную поляризацию света и доказал вращение Земли по поляризации света; его работы по оптике и магнетизму важны для физики, лежащей в основе оптоэлектроники и сенсоров.


🌐 Знаковые события

1909 – Кинемаколор, первый успешный процесс цветного кинопроизводства, впервые показан широкой публике в театре «Палас» в Лондоне.


#Biography #Birth_Date #Events #26февраля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1
Раздел 8. Stream API и функциональный стиль в Java

Глава 3: Чистые преобразования и борьба с исключениями

(Практика): Чистая агрегация уникальных жанров

Цель: Освоить операцию flatMap для «выпрямления» вложенных коллекций и научиться обрабатывать ошибки декларативно, без побочных эффектов и промежуточных изменяемых структур.

Подготовка: текущее состояние проекта «Библиотека»

К этому моменту в проекте должно быть:
Класс Book с полями: title, author, year, List<String> genres
Класс Library с List<Book> books и методами добавления/поиска
Возможно, класс Genre с иерархией (из урока про рекурсию)
Обновление класса Book для этого урока:

Если в Book жанры хранятся как одна строка или отсутствуют — обновите структуру:
public class Book {
private final String title;
private final String author;
private final int year;
private final List<String> genres; // список жанров для одной книги

// конструктор с genres
public Book(String title, String author, int year, List<String> genres) {
this.title = title;
this.author = author;
this.year = year;
// защитная копия или unmodifiableList
this.genres = genres != null ? new ArrayList<>(genres) : new ArrayList<>();
}

public List<String> getGenres() {
return Collections.unmodifiableList(genres);
}

// остальные геттеры...
}



Часть 1. Проблема: вложенные коллекции

В библиотеке каждая книга имеет несколько жанров.

Нужно получить полный набор уникальных жанров всей библиотеки для:
построения фильтра в UI
статистики по жанрам
валидации при добавлении книги

Плохой подход: изменяемая переменная и цикл
// В классе Library — метод getAllGenresImperative()
public Set<String> getAllGenresImperative() {
Set<String> genres = new HashSet<>();
for (Book book : books) {
genres.addAll(book.getGenres()); // побочный эффект на genres
}
return genres;
}


Проблемы этого подхода:
Внешняя изменяемая переменная genres — состояние, которое нужно отслеживать
Метод addAll — побочный эффект, модифицирующий коллекцию
Сложно распараллелить без синхронизации
Не выразительно — «как» вместо «что»


Часть 2. Хороший подход: flatMap + collect

Решение через Stream API
// В классе Library — метод getAllGenresFunctional()
public Set<String> getAllGenresFunctional() {
return books.stream()
.flatMap(book -> book.getGenres().stream()) // Stream<Book> → Stream<String>
.collect(Collectors.toSet()); // собираем в Set для уникальности
}


Что происходит:
books.stream() — поток книг
flatMap(book -> book.getGenres().stream()) — для каждой книги получаем поток её жанров и «выпрямляем» в один общий поток строк. Без flatMap был бы Stream<List<String>>, с ним — Stream<String>
collect(Collectors.toSet()) — собираем в HashSet, автоматически устраняя дубликаты

Преимущества:
Нет промежуточных переменных
Нет побочных эффектов
Легко добавить фильтрацию, сортировку, преобразование
Потокобезопасность при parallelStream()


#Java #для_новичков #beginner #stream_api #практика
👍2