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
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
Часть 3. Расширение: фильтрация и сортировка жанров

Задача: только жанры классики, отсортированные
public Set<String> getClassicGenresSorted() {
return books.stream()
.filter(book -> book.getYear() < 1950) // только старые книги
.flatMap(book -> book.getGenres().stream())
.filter(genre -> !genre.equals("Фэнтези")) // исключаем современное
.sorted() // сортировка (временно в Stream)
.collect(Collectors.toCollection(LinkedHashSet::new)); // сохраняем порядок
}


Важно: sorted() в промежуточной операции работает, но для больших данных лучше сортировать после collect, если порядок критичен.


Часть 4. Задача с исключением: безопасный парсинг годов

Поступают строки годов из внешнего источника (файл, API, пользовательский ввод). Нужно получить список валидных целых чисел, игнорируя ошибки парсинга.

Входные данные:
["1999", "invalid", "2005", "nineteen", "2020", ""]



Плохой подход:
try-catch в цикле с изменяемым списком
// Не делайте так
List<Integer> years = new ArrayList<>();
for (String s : yearStrings) {
try {
years.add(Integer.parseInt(s));
} catch (NumberFormatException e) {
// игнорируем — но где гарантия, что мы ничего не пропустили?
}
}


Проблемы: изменяемый список, скрытое игнорирование ошибок, сложно тестировать.

Хороший подход: flatMap + Optional
Создайте вспомогательный метод для безопасного парсинга:
// В отдельном классе-утилите или в Library
private static Optional<Integer> safeParseYear(String yearString) {
try {
return Optional.of(Integer.parseInt(yearString));
} catch (NumberFormatException e) {
return Optional.empty();
}
}


Использование в Stream:
public List<Integer> parseValidYears(List<String> yearStrings) {
return yearStrings.stream()
.flatMap(s -> safeParseYear(s).stream()) // Optional → Stream (0 или 1 элемента)
.collect(Collectors.toList());
}


Что происходит:
safeParseYear возвращает Optional<Integer> — явное указание на возможность отсутствия значения
Optional.stream() (Java 9+) создаёт поток из 0 или 1 элемента
flatMap «выпрямляет» потоки: Stream<Optional<Integer>> → Stream<Integer>
Невалидные строки превращаются в пустые потоки и исчезают из результата

Для Java 8 (без Optional.stream()):
.flatMap(s -> safeParseYear(s).map(Stream::of).orElseGet(Stream::empty))



#Java #для_новичков #beginner #stream_api #практика
👍2
Часть 5. Интеграция в проект «Библиотека»

Задача 1: обновление метода добавления книги с валидацией жанров


В Library добавьте проверку, что жанр книги существует в библиотеке:
public boolean isValidGenre(String genre) {
return getAllGenresFunctional().contains(genre);
}

public void addBookWithGenreValidation(Book book) {
for (String genre : book.getGenres()) {
if (!isValidGenre(genre) && !books.isEmpty()) {
System.out.println("Предупреждение: новый жанр '" + genre + "'");
}
}
addBook(book);
}

Улучшение: кэшировать getAllGenresFunctional() при частых проверках.

Задача 2: статистика по жанрам

public Map<String, Long> getGenreStatistics() {
return books.stream()
.flatMap(book -> book.getGenres().stream())
.collect(Collectors.groupingBy(
genre -> genre,
Collectors.counting()
));
}

Результат: { "Роман"=15, "Детектив"=8, "Фантастика"=12 }

Задача 3: книги с неизвестными годами

Используем safeParseYear для обработки внешних данных при импорте:
public List<Book> importBooksFromRawData(List<String[]> rawData) {
return rawData.stream()
.flatMap(row -> {
Optional<String> title = Optional.ofNullable(row[0]);
Optional<String> author = Optional.ofNullable(row[1]);
Optional<Integer> year = safeParseYear(row[2]);
List<String> genres = row.length > 3
? Arrays.asList(row[3].split(","))
: Collections.emptyList();

// создаём книгу только если все обязательные поля валидны
return title.flatMap(t ->
author.flatMap(a ->
year.map(y -> new Book(t, a, y, genres))
)
).stream();
})
.collect(Collectors.toList());
}



Практические задания


Задача 1: рефакторинг существующего кода


Найдите в проекте «Библиотека» методы, которые:
собирают данные в Set или List через циклы и add/addAll
фильтруют коллекции вручную
Перепишите их через flatMap, filter, collect. Добавьте peek для отладки, затем удалите.

Задача 2: поиск книг по нескольким жанрам

Реализуйте метод findBooksByAnyGenre(List<String> targetGenres) — книги, имеющие хотя бы один жанр из списка.
Подсказка: anyMatch внутри filter.
Реализуйте метод findBooksByAllGenres(List<String> targetGenres) — книги, имеющие все жанры из списка.
Подсказка: containsAll или targetGenres.stream().allMatch(...).

Задача 3: отчёт по авторам и их жанрам

Создайте Map<String, Set<String>> — автор → все жанры его книг.
// Ожидаемый результат:
// { "Толстой"=["Роман", "Исторический"],
// "Оруэлл"=["Антиутопия", "Сатира"] }

Используйте flatMap для «размножения» пары (автор, жанр), затем groupingBy.

Задача 4: импорт с валидацией (звёздочка)

Создайте метод importFromCsv(List<String> lines), где каждая строка: "title;author;year;genre1,genre2".

Требования:
пропускать строки с невалидным годом (через safeParseYear)
пропускать строки с пустым названием или автором
собрать статистику: сколько строк успешно, сколько с ошибкой
вернуть ImportResult с List<Book> и List<String> errors
Используйте flatMap для преобразования строки в Optional<Book> → Stream<Book>.


#Java #для_новичков #beginner #stream_api #практика
👍3
Что выведет код?

public class Task260225 {
public static void main(String[] args) {
float a = 0.1f;
double b = 0.1;

System.out.println(a == 0.1);
System.out.println(b == 0.1);
System.out.println(a == 0.1f);
System.out.println(a == (float)0.1);
}
}


#Tasks
👍2