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
Раздел 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
Что такое сериализация (Serialization)? 🤓

Ответ:

Сериализация
— это процесс преобразования состояния объекта в поток байтов для сохранения в файл, передачи по сети или хранения в БД.

Десериализация — обратный процесс восстановления объекта из байтов.

Для сериализации объект должен реализовать маркерный интерфейс 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февраля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Итак, на фоне уже явных сообщений о блокировке телеграмма, раздумываю о переносе канала.

Недавно обнаружил что можно создать канал в 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 оставляйте для побочных эффектов.

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
Что выведет код?

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
Что такое клоннирование (Cloning)? Как правильно клонировать объекты? 🤓

Ответ:

Клонирование
— создание точной копии объекта.

В Java для этого используется метод clone() класса Object. Чтобы объект можно было клонировать, класс должен реализовать маркерный интерфейс Cloneable (иначе будет CloneNotSupportedException).

Метод clone() делает поверхностное копирование (shallow copy) — копирует примитивы и ссылки, но не сами объекты по ссылкам.

Для глубокого копирования (deep copy) нужно переопределить clone() и вручную клонировать вложенные объекты.

Альтернативы: конструктор копирования, статический фабричный метод, сериализация.


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

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

То́мас Нью́комен (англ. Thomas Newcomen; крещён 28 февраля 1664, Дартмут — 7 августа 1729, Лондон) — английский изобретатель; один из создателей первого теплового (парового) двигателя, известного как паровая машина Ньюкомена.

Леон Н.Купер (англ. Leon Neil Cooper; при рождении — Леон Н. Купчик; 28 февраля 1930, Нью-Йорк, Нью-Йорк — 23 октября 2024, Провиденс) — американский физик, лауреат Нобелевской премии по физике 1972 года (совместно с Джоном Бардиным и Джоном Шриффером) «за создание теории сверхпроводимости, обычно называемой БКШ-теорией». Именем Купера названы электронные пары Купера и BCM теория синаптической пластичности.


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

1956 — в США патентуется сетевой кабель для компьютеров.

1956 — Джей Форрестер получает патент на магнитно‑ядерную память (core memory). Магнитное сердечниковое ОЗУ стало стандартной памятью компьютеров 1950–60‑х и фактически сделало надёжные электронные компьютеры массовыми; без неё не было бы ни ранних мейнфреймов, ни многих космических систем.


#Biography #Birth_Date #Events #28февраля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
С 21.02 по 27.02
Предыдущий пост(с 14.02 по 20.02)

Воскресный мотивационный пост:
ГЛОБАЛИЗАЦИЯ ЗАКАНЧИВАЕТСЯ

Запись встреч/видео:
7. Введение в распределённую трассировку. OpenTelemetry & Jaeger.

Обучающие статьи:
Раздел 8. Stream API и функциональный стиль

Глава 3: Чистые преобразования и борьба с исключениями
Side Effects — главный враг предсказуемости
(Практика): Чистая агрегация уникальных жанров

Советы по Java:
[Совет по Java #008]
Не ловите Exception или Throwable без крайней необходимости. Ловите максимально конкретные исключения.

[Совет по Java #009]
Используйте Collections.emptyList(), Collections.singletonList() вместо создания новых коллекций, когда нужно вернуть пустой или одноэлементный список.

[Совет по Java #010]
Ключевое слово finally выполняется всегда, даже если в try или catch был return.



Полезные статьи и видео:
JDBC для профи: пулы, batch, транзакции и скрытые риски


Как и всегда, задачи можно найти под тегом - #Tasks, вопросы с собеседований - #собеседование
👍2
История технологий сегодня — 29 Февраля/01 марта

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

Сеймур Пейперт (Seymour Papert [ˈsimɔɹ ˈpæpɚt]; 29 февраля 1928, Претория, Южная Африка — 31 июля 2016, Блу-Хилл, Мэн, США) — математик, программист, психолог и педагог. Один из основоположников теории искусственного интеллекта, создатель языка Logo (1968), автор конструкционистской теории обучения[6].

Герман Хо́ллерит (англ. Herman Hollerith; 29 февраля 1860 — 17 ноября 1929) — американский инженер и изобретатель. Считается отцом современных автоматических вычислений. Известен как создатель электрической табулирующей системы.


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

1888 — американский инженер Герман Холлерит изобрёл табулятор, первую электрическую счётную машину.

1936 — Нильс Бор предложил теорию составного ядра атома.

1966 — советская автоматическая межпланетная станция Венера-3 впервые в мире достигла поверхности Венеры.


#Biography #Birth_Date #Events #29февраля #01марта
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥2👍1
Сегодня день тишины))

Я целый день готовился к видео с elk… так и не успел))

Завтра продолжу))
🔥9
История технологий сегодня — 02 марта

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

Князь Бори́с Бори́сович Голи́цын (18 февраля [2 марта] 1862, Санкт-Петербург — 4 [17] мая 1916, Петергоф, Петроградская губерния) — русский физик и геофизик из рода Голицыных, один из основоположников сейсмологии, изобретатель первого электромагнитного сейсмографа (1906).


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

1969 — Первый испытательный полёт французского сверхзвукового пассажирского самолёта «Конкорд».


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

Глава 4: Искусство агрегации. Коллекторы и группировки

Коллектор — это рецепт агрегации

Предыдущие главы посвящены преобразованиям потоков: фильтрации, отображению, уплощению. Но поток — эфемерная абстракция. Чтобы извлечь из него ценность, нужно свернуть его в конкретную структуру данных: список, множество, карту, число, строку. Эту свёртку выполняет Collector — центральный интерфейс терминальных операций агрегации.

Collector — не просто утилита для создания List. Это декларативный рецепт, описывающий, как создать контейнер для результатов, как наполнять его элементами, как объединять частичные результаты при параллельном выполнении, и как выполнить финальное преобразование. Понимание его компонентов раскрывает полную мощь Stream API.


Анатомия Collector

Интерфейс Collector<T, A, R> параметризован тремя типами: T — тип элементов входного потока, A — тип промежуточного контейнера аккумуляции, R — тип конечного результата.

Четыре функциональных компонента определяют поведение:

Supplier<A> — фабрика начального состояния. Вызывается один раз для каждого сегмента обработки (при параллельном выполнении — для каждого потока). Создаёт пустой контейнер, готовый к наполнению.
// Для toList(): () -> new ArrayList<T>()
// Для toSet(): () -> new HashSet<T>()
// Для toMap(): () -> new HashMap<K, V>()


BiConsumer<A, T> accumulator — функция накопления. Добавляет элемент потока в контейнер. Вызывается для каждого элемента, не требует возврата значения — мутирует контейнер.
// Для toList(): (list, element) -> list.add(element)
// Для toMap(): (map, element) -> map.put(keyExtractor.apply(element), valueExtractor.apply(element))


BinaryOperator<A> — функция слияния. Объединяет два контейнера в один при параллельном выполнении. Критична для корректности parallelStream: без неё частичные результаты останутся разрозненными.
// Для toList(): (left, right) -> { left.addAll(right); return left; }
// Для toSet(): (left, right) -> { left.addAll(right); return left; }


Function<A, R> finisher — финальное преобразование. Превращает контейнер аккумуляции в конечный результат. Для toList() это тождественное преобразование (list -> list), но для сложных коллекторов может быть нетривиальным: например, collectingAndThen оборачивает результат в unmodifiable коллекцию.
// collectingAndThen(toList(), Collections::unmodifiableList)
// finisher: list -> Collections.unmodifiableList(list)



Характеристики коллектора: оптимизационные подсказки

Третий параметр Collector — Set<Characteristics> — метаданные, влияющие на исполнение:
CONCURRENT — сигнализирует, что один контейнер может безопасно наполняться из нескольких потоков одновременно. При наличии этой характеристики фреймворк создаёт единственный контейнер и вызывает accumulator из множества потоков без предварительного разделения. Пример: Collector.of(ConcurrentHashMap::new, ..., ..., Characteristics.CONCURRENT).

UNORDERED — гарантия, что порядок элементов в результате не важен. Позволяет оптимизировать параллельное выполнение, устраняя накладные расходы на сохранение порядка при слиянии сегментов. toSet() имеет эту характеристику, toList() — нет.

IDENTITY_FINISH — утверждение, что finisher является тождественным преобразованием (A совпадает с R). Фреймворк может пропустить вызов finisher, возвращая контейнер напрямую. Это оптимизация, но также контракт: если объявить эту характеристику и предоставить нетривиальный finisher, получим ClassCastException при попытке привести A к R.


#Java #для_новичков #beginner #stream_api #Collectors #toMap
👍4