forEach: последнее средство, не первое
Метод forEach предназначен для конечных действий (terminal side effects), не для агрегации данных. Его корректное применение — операции, которые по своей природе требуют побочных эффектов и уже учитывают многопоточность:
В этих случаях побочный эффект — сама цель операции. Мы не накапливаем данные для дальнейшей обработки, а выполняем окончательное действие над каждым элементом.
collect: правильный путь агрегации
Для накопления результатов Stream API предоставляет collect — мощную и гибкую терминальную операцию, инкапсулирующую стратегию свёртки потока в конкретную структуру данных.
Здесь мутация происходит внутри Collector, не видима снаружи, потокобезопасна при параллельном выполнении (через механизм комбайнеров), композиционна — результат можно передать дальше.
Как работает безопасность collect в параллельном потоке:
Коллектор toList() использует стратегию "разделяй и властвуй": поток делится на сегменты, каждый обрабатывается в своём потоке с локальным ArrayList (аккумулятор), затем локальные списки комбинируются в один результат. Никакой совместной мутации, никаких блокировок — только локальные изменения и финальное слияние.
Мутация внутри map и filter: скрытая бомба
Побочные эффекты опасны не только в forEach.
Любая промежуточная операция с мутацией внешнего состояния создаёт непредсказуемое поведение:
Здесь map используется не для преобразования элемента, а для генерации глобального счётчика. Это нарушает чистоту функции: результат зависит не только от входа, но от внешнего состояния и порядка выполнения.
Решение — встроить нумерацию в структуру данных или использовать специализированные операции:
Идентификация побочных эффектов
Признаки, что код содержит опасные побочные эффекты:
- Лямбда не возвращает значение, но делает что-то "полезное": x -> list.add(x), x -> map.put(x.getKey(), x)
- Захват изменяемых внешних переменных: лямбда использует переменные, объявленные до неё, и вызывает на них модифицирующие методы
- Необходимость очистки состояния между запусками: тесты требуют list.clear() или создания новых объектов
- Недетерминированные результаты при параллельном выполнении: одинаковый вход даёт разный выход
- Рефакторинг от побочных эффектов к чистым функциям следует шаблону: вынести мутацию в collect, преобразования в map, фильтрацию в filter, а forEach оставить только для истинно терминальных действий.
#Java #для_новичков #beginner #stream_api #side_effects
Метод 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
Что выведет код?
#Tasks
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).
#собеседование
Ответ:
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февраля
Лев Андре́евич Арцимо́вич (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.
Объяснение: Метод Collections.emptyList() возвращает ссылку на статический финальный экземпляр класса EmptyList (внутренняя реализация Collections).
Все вызовы этого метода во всем приложении используют один и тот же объект в памяти. Аналогично singletonList создает специализированную реализацию с одним полем для хранения элемента, без внутреннего массива. Это особенно важно в API, где методы часто возвращают пустые коллекции вместо null (что само по себе хорошая практика, исключающая NPE).
Начиная с Java 9, появились дополнительные фабричные методы List.of(), Set.of(), Map.of(), которые также возвращают иммутабельные коллекции и часто более читаемы.
Разработчик всегда выбирает между производительностью (переиспользование иммутабельных коллекций) и необходимостью модификации (тогда нужны new ArrayList<>()).
#Java #советы
Тема: Используйте 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
Что выведет код?
#Tasks
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
👍2
Что такое рефлексия (Reflection)? 🤓
Ответ:
Рефлексия (Reflection API) — это механизм, позволяющий программе анализировать и изменять свое поведение во время выполнения.
С помощью рефлексии можно получать информацию о классах, интерфейсах, полях и методах, создавать объекты, вызывать методы и изменять значения полей динамически, даже если они приватные. Используется в фреймворках (Spring, Hibernate), IDE, тестовых библиотеках (JUnit).
Минусы: потеря производительности, нарушение инкапсуляции, проблемы с безопасностью.
#собеседование
Ответ:
С помощью рефлексии можно получать информацию о классах, интерфейсах, полях и методах, создавать объекты, вызывать методы и изменять значения полей динамически, даже если они приватные. Используется в фреймворках (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февраля
Доминик Франсуа Жан Араго́ (фр. 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 жанры хранятся как одна строка или отсутствуют — обновите структуру:
Часть 1. Проблема: вложенные коллекции
В библиотеке каждая книга имеет несколько жанров.
Нужно получить полный набор уникальных жанров всей библиотеки для:
построения фильтра в UI
статистики по жанрам
валидации при добавлении книги
Плохой подход: изменяемая переменная и цикл
Проблемы этого подхода:
Внешняя изменяемая переменная genres — состояние, которое нужно отслеживать
Метод addAll — побочный эффект, модифицирующий коллекцию
Сложно распараллелить без синхронизации
Не выразительно — «как» вместо «что»
Часть 2. Хороший подход: flatMap + collect
Решение через Stream API
Что происходит:
books.stream() — поток книг
flatMap(book -> book.getGenres().stream()) — для каждой книги получаем поток её жанров и «выпрямляем» в один общий поток строк. Без flatMap был бы Stream<List<String>>, с ним — Stream<String>
collect(Collectors.toSet()) — собираем в HashSet, автоматически устраняя дубликаты
Преимущества:
Нет промежуточных переменных
Нет побочных эффектов
Легко добавить фильтрацию, сортировку, преобразование
Потокобезопасность при parallelStream()
#Java #для_новичков #beginner #stream_api #практика
Глава 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. Расширение: фильтрация и сортировка жанров
Задача: только жанры классики, отсортированные
Важно: sorted() в промежуточной операции работает, но для больших данных лучше сортировать после collect, если порядок критичен.
Часть 4. Задача с исключением: безопасный парсинг годов
Поступают строки годов из внешнего источника (файл, API, пользовательский ввод). Нужно получить список валидных целых чисел, игнорируя ошибки парсинга.
Входные данные:
Плохой подход: try-catch в цикле с изменяемым списком
Проблемы: изменяемый список, скрытое игнорирование ошибок, сложно тестировать.
Хороший подход: flatMap + Optional
Создайте вспомогательный метод для безопасного парсинга:
Использование в Stream:
Что происходит:
safeParseYear возвращает Optional<Integer> — явное указание на возможность отсутствия значения
Optional.stream() (Java 9+) создаёт поток из 0 или 1 элемента
flatMap «выпрямляет» потоки: Stream<Optional<Integer>> → Stream<Integer>
Невалидные строки превращаются в пустые потоки и исчезают из результата
Для Java 8 (без Optional.stream()):
#Java #для_новичков #beginner #stream_api #практика
Задача: только жанры классики, отсортированные
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 добавьте проверку, что жанр книги существует в библиотеке:
Улучшение: кэшировать getAllGenresFunctional() при частых проверках.
Задача 2: статистика по жанрам
Результат: { "Роман"=15, "Детектив"=8, "Фантастика"=12 }
Задача 3: книги с неизвестными годами
Используем safeParseYear для обработки внешних данных при импорте:
Практические задания
Задача 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 #практика
Задача 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
Что выведет код?
#Tasks
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
Варианты ответа:
Anonymous Quiz
4%
false true false true
36%
false true true true
44%
true true true true
16%
false false true true
👍2
Что такое сериализация (Serialization)? 🤓
Ответ:
Сериализация — это процесс преобразования состояния объекта в поток байтов для сохранения в файл, передачи по сети или хранения в БД.
Десериализация — обратный процесс восстановления объекта из байтов.
Для сериализации объект должен реализовать маркерный интерфейс Serializable. Ключевое слово transient исключает поле из сериализации. Нужно быть осторожным с версионированием классов — поле serialVersionUID помогает контролировать совместимость версий.
#собеседование
Ответ:
Десериализация — обратный процесс восстановления объекта из байтов.
Для сериализации объект должен реализовать маркерный интерфейс Serializable. Ключевое слово transient исключает поле из сериализации. Нужно быть осторожным с версионированием классов — поле serialVersionUID помогает контролировать совместимость версий.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
История технологий сегодня — 27 Февраля
ℹ️ Кто родился в этот день
Ю́лий Бори́сович Харито́н (14 [27] февраля 1904, Санкт-Петербург — 18 декабря 1996, Саров) — советский и российский физик и физикохимик. Деятель науки и техники — ядерщик. Академик АН СССР и РАН (1953). Один из руководителей советского проекта атомной бомбы.
А́лан Ха́рви Гут (Гус) (англ. Alan Harvey Guth; род. 27 февраля 1947, Нью-Брансуик, округ Мидлсекс, Нью-Джерси, США) — американский физик и космолог, предложивший идею космической инфляции. Его теория требует сложных численных расчётов и стала одним из драйверов развития вычислительной космологии и высокопроизводительных вычислений, где активно используются методы моделирования, статистики и обработки больших данных.
🌐 Знаковые события
1932 — Джеймс Чедвик открыл нейтрон.
#Biography #Birth_Date #Events #27февраля
Ю́лий Бори́сович Харито́н (14 [27] февраля 1904, Санкт-Петербург — 18 декабря 1996, Саров) — советский и российский физик и физикохимик. Деятель науки и техники — ядерщик. Академик АН СССР и РАН (1953). Один из руководителей советского проекта атомной бомбы.
А́лан Ха́рви Гут (Гус) (англ. Alan Harvey Guth; род. 27 февраля 1947, Нью-Брансуик, округ Мидлсекс, Нью-Джерси, США) — американский физик и космолог, предложивший идею космической инфляции. Его теория требует сложных численных расчётов и стала одним из драйверов развития вычислительной космологии и высокопроизводительных вычислений, где активно используются методы моделирования, статистики и обработки больших данных.
1932 — Джеймс Чедвик открыл нейтрон.
#Biography #Birth_Date #Events #27февраля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Итак, на фоне уже явных сообщений о блокировке телеграмма, раздумываю о переносе канала.
Недавно обнаружил что можно создать канал в max. Нужен ли он?
Хотел спросить вашего мнения:
- нужно ли вообще переносить канал или вы согласны читать его под vpn/санкциями/прочей херней
- если переносить - то как назвать новый канал. Вопрос возник так как мы давно уже не для новичков канал))
Предложите варианты))
- или пора каналу стать прошлым и мне сконцентрироваться на ютубе/рутубе и обучающих роликах, которые дают наибольшую конверсию?
Давайте хорошенько обсудим эти вопросы. Помогите мне принять решение, от него зависит будущее канала.
✌️
Недавно обнаружил что можно создать канал в max. Нужен ли он?
Хотел спросить вашего мнения:
- нужно ли вообще переносить канал или вы согласны читать его под vpn/санкциями/прочей херней
- если переносить - то как назвать новый канал. Вопрос возник так как мы давно уже не для новичков канал))
Предложите варианты))
- или пора каналу стать прошлым и мне сконцентрироваться на ютубе/рутубе и обучающих роликах, которые дают наибольшую конверсию?
Давайте хорошенько обсудим эти вопросы. Помогите мне принять решение, от него зависит будущее канала.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥3😱3
[Совет по Java #010]
Тема: Ключевое слово finally выполняется всегда, даже если в try или catch был return. Но если в finally тоже будет return, он перекроет предыдущий — так не делайте.
Проблема: Блок finally гарантированно выполняется после try (и catch, если было исключение) независимо от того, был ли в них оператор return, break, continue или выброшено исключение.
Это фундаментальное свойство, используемое для очистки ресурсов. Однако если в самом finally поместить return, он перезапишет результат, возвращаемый из try/catch, и "проглотит" любое исключение. Такое поведение крайне неочевидно и приводит к трудноотлаживаемым багам, когда метод возвращает не то значение или не бросает ожидаемое исключение.
Решение: Никогда не используйте return в блоке finally.
Блок finally должен содержать только код очистки (закрытие ресурсов, счетчики, разблокировки) и не должен вмешиваться в поток возврата значения или исключения. Если вам нужно обработать результат или исключение, делайте это в try или catch, а finally оставляйте для побочных эффектов.
Объяснение: Спецификация Java гарантирует, что блок finally выполняется перед тем, как метод вернет управление.
Если finally содержит return, то значение, вычисленное в try (или выброшенное исключение), отбрасывается, и метод завершается с тем значением, которое указано в finally. Это же касается и исключений — если в finally нет throw, то любое исключение из try/catch считается обработанным. Такое поведение противоречит интуиции и здравому смыслу.
Разработчик использует finally исключительно для освобождения ресурсов (или с 2011 года — try-with-resources), никогда не помещая туда управляющие операторы, влияющие на результат метода.
#Java #советы
Тема: Ключевое слово finally выполняется всегда, даже если в try или catch был return. Но если в finally тоже будет return, он перекроет предыдущий — так не делайте.
Проблема: Блок finally гарантированно выполняется после try (и catch, если было исключение) независимо от того, был ли в них оператор return, break, continue или выброшено исключение.
Это фундаментальное свойство, используемое для очистки ресурсов. Однако если в самом finally поместить return, он перезапишет результат, возвращаемый из try/catch, и "проглотит" любое исключение. Такое поведение крайне неочевидно и приводит к трудноотлаживаемым багам, когда метод возвращает не то значение или не бросает ожидаемое исключение.
Решение: Никогда не используйте return в блоке finally.
Блок finally должен содержать только код очистки (закрытие ресурсов, счетчики, разблокировки) и не должен вмешиваться в поток возврата значения или исключения. Если вам нужно обработать результат или исключение, делайте это в try или catch, а finally оставляйте для побочных эффектов.
import java.io.*;
public class FinallyReturn {
//Антипаттерн: return в finally
public static int badExample() {
try {
System.out.println("Try");
return 42; // Этот return будет проигнорирован
} finally {
System.out.println("Finally");
return 100; // Перекрывает return из try
}
}
//Еще хуже: finally "проглатывает" исключение
public static int badWithException() {
try {
throw new RuntimeException("Ошибка в try");
} finally {
return 200; // Исключение ПРОПАДАЕТ!
}
}
//Правильно: finally без return
public static int goodExample() {
try {
System.out.println("Try");
return 42; // Нормальный выход
} finally {
System.out.println("Finally");
// Здесь только очистка, без return
}
}
//Правильно с исключением
public static int goodWithException() throws IOException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
return Integer.parseInt(reader.readLine());
} finally {
if (reader != null) {
try {
reader.close(); // Только закрытие, return нет
} catch (IOException e) {
// Логируем, но не перезаписываем исходное исключение
}
}
}
}
// Демонстрация
public static void main(String[] args) {
System.out.println("badExample(): " + badExample());
// Вывод: Try, Finally, 100 (а не 42!)
System.out.println("badWithException(): " + badWithException());
// Исключение не выбрасывается, выводит 200
System.out.println("goodExample(): " + goodExample());
// Вывод: Try, Finally, 42 (корректно)
}
}
Объяснение: Спецификация Java гарантирует, что блок finally выполняется перед тем, как метод вернет управление.
Если finally содержит return, то значение, вычисленное в try (или выброшенное исключение), отбрасывается, и метод завершается с тем значением, которое указано в finally. Это же касается и исключений — если в finally нет throw, то любое исключение из try/catch считается обработанным. Такое поведение противоречит интуиции и здравому смыслу.
Разработчик использует finally исключительно для освобождения ресурсов (или с 2011 года — try-with-resources), никогда не помещая туда управляющие операторы, влияющие на результат метода.
#Java #советы
👍6
Что выведет код?
#Tasks
public class Task270226 {
public static void main(String[] args) {
System.out.println(test());
}
static String test() {
try {
System.exit(0);
return "try";
} catch (Exception e) {
return "catch";
} finally {
return "finally";
}
}
}#Tasks
👍2
🔥3👍1