Раздел 6. Коллекции в Java
Глава 1. Введение в коллекции
(Практика):
Начать проект «Библиотека».
Создать класс Book с полями title, author, year.
Сделать список книг (пока как массив) и вывести их на экран
Перед тем, как писать код, важно правильно настроить среду разработки. Это обеспечит удобную работу и избежание ошибок на старте.
Запустите IntelliJ IDEA:
Если IDE не открыта, запустите её. Убедитесь, что у вас установлен JDK. Если проект новый, выберите "New Project" на приветственном экране.
Создайте новый проект:
В окне создания проекта выберите "Java" как тип.
Укажите имя проекта, например, "LibraryProject".
Выберите JDK в поле Project SDK (если не настроено, добавьте путь к JDK).
Оставьте остальные настройки по умолчанию (без Maven или Gradle для простоты) и нажмите "Create".
IDE создаст структуру проекта с папкой src для исходного кода.
Настройте структуру проекта:
В дереве проекта (слева) найдите src.
Если нужно, создайте пакет для организации кода: Правой кнопкой на src → New → Package → Назовите "library" (или другое имя). Это хорошая практика для группировки классов.
Создание класса Book
Теперь создадим основной класс для представления книги. Это будет простой класс с полями, который позже расширим.
Создайте класс Book:
В пакете (или прямо в src, если без пакета) щелкните правой кнопкой → New → Java Class.
Назовите класс "Book".
IDE создаст файл Book.java с базовой структурой
Добавьте поля:
В теле класса объявите три приватных поля:
title типа String (для названия книги).
author типа String (для автора).
year типа int (для года издания).
Используйте модификатор private для инкапсуляции, как мы изучали в ООП.
Добавьте конструктор:
Создайте публичный конструктор, который принимает три параметра (String title, String author, int year) и присваивает их соответствующим полям с помощью this.
Это позволит создавать объекты Book с начальными значениями.
Добавьте метод для вывода:
Создайте публичный метод, например, printDetails(), который выводит информацию о книге на экран с помощью System.out.println.
В нём используйте поля для формирования строки вроде "Название: [title], Автор: [author], Год: [year]".
Создание списка книг с использованием массива
Пока мы используем массив как простую структуру для хранения списка книг — это поможет сравнить с коллекциями позже.
Создайте класс Main:
Аналогично создайте новый класс "Main" в том же пакете.
Добавьте статический метод main(String[] args) — точку входа.
Объявите массив:
В методе main объявите массив объектов Book фиксированного размера, например, Book[] books = new Book[3]; (размер выберите небольшой для теста).
Инициализируйте массив:
Создайте несколько объектов Book с помощью new Book(title, author, year) и присвойте их элементам массива (books[0] = new Book(...); и т.д.).
Используйте разные значения для демонстрации (например, книги разных авторов и годов).
Выведите список на экран:
Используйте цикл for (или for-each: for (Book book : books)) для перебора массива.
В цикле вызовите метод printDetails() для каждого элемента, чтобы вывести информацию о книгах.
#Java #для_новичков #beginner #Collections #Практика
Глава 1. Введение в коллекции
(Практика):
Начать проект «Библиотека».
Создать класс Book с полями title, author, year.
Сделать список книг (пока как массив) и вывести их на экран
Перед тем, как писать код, важно правильно настроить среду разработки. Это обеспечит удобную работу и избежание ошибок на старте.
Запустите IntelliJ IDEA:
Если IDE не открыта, запустите её. Убедитесь, что у вас установлен JDK. Если проект новый, выберите "New Project" на приветственном экране.
Создайте новый проект:
В окне создания проекта выберите "Java" как тип.
Укажите имя проекта, например, "LibraryProject".
Выберите JDK в поле Project SDK (если не настроено, добавьте путь к JDK).
Оставьте остальные настройки по умолчанию (без Maven или Gradle для простоты) и нажмите "Create".
IDE создаст структуру проекта с папкой src для исходного кода.
Настройте структуру проекта:
В дереве проекта (слева) найдите src.
Если нужно, создайте пакет для организации кода: Правой кнопкой на src → New → Package → Назовите "library" (или другое имя). Это хорошая практика для группировки классов.
Создание класса Book
Теперь создадим основной класс для представления книги. Это будет простой класс с полями, который позже расширим.
Создайте класс Book:
В пакете (или прямо в src, если без пакета) щелкните правой кнопкой → New → Java Class.
Назовите класс "Book".
IDE создаст файл Book.java с базовой структурой
public class Book {}
Добавьте поля:
В теле класса объявите три приватных поля:
title типа String (для названия книги).
author типа String (для автора).
year типа int (для года издания).
Используйте модификатор private для инкапсуляции, как мы изучали в ООП.
Добавьте конструктор:
Создайте публичный конструктор, который принимает три параметра (String title, String author, int year) и присваивает их соответствующим полям с помощью this.
Это позволит создавать объекты Book с начальными значениями.
Добавьте метод для вывода:
Создайте публичный метод, например, printDetails(), который выводит информацию о книге на экран с помощью System.out.println.
В нём используйте поля для формирования строки вроде "Название: [title], Автор: [author], Год: [year]".
Создание списка книг с использованием массива
Пока мы используем массив как простую структуру для хранения списка книг — это поможет сравнить с коллекциями позже.
Создайте класс Main:
Аналогично создайте новый класс "Main" в том же пакете.
Добавьте статический метод main(String[] args) — точку входа.
Объявите массив:
В методе main объявите массив объектов Book фиксированного размера, например, Book[] books = new Book[3]; (размер выберите небольшой для теста).
Инициализируйте массив:
Создайте несколько объектов Book с помощью new Book(title, author, year) и присвойте их элементам массива (books[0] = new Book(...); и т.д.).
Используйте разные значения для демонстрации (например, книги разных авторов и годов).
Выведите список на экран:
Используйте цикл for (или for-each: for (Book book : books)) для перебора массива.
В цикле вызовите метод printDetails() для каждого элемента, чтобы вывести информацию о книгах.
#Java #для_новичков #beginner #Collections #Практика
👍3🔥1
Тестирование и отладка проекта
После реализации протестируйте проект, чтобы убедиться, что всё работает.
Запустите проект:
Правой кнопкой на файле Main.java → Run 'Main.main()'.
В консоли IDE вы должны увидеть вывод списка книг с их деталями.
Отладка:
Если ошибки: Проверьте синтаксис (точки с запятой, скобки).
Используйте отладчик: Установите breakpoint (красная точка слева от строки в main), запустите в debug-режиме (Shift+F9) и шагайте по коду (F8).
Общие проблемы: NullPointerException (если массив не инициализирован), IndexOutOfBoundsException (если выход за пределы массива).
Проверьте вывод:
Убедитесь, что книги выводятся в порядке добавления в массив.
Попробуйте изменить размер массива или добавить больше книг — увидите, как фиксированный размер ограничивает (это мотивирует к коллекциям позже).
Полезные советы для новичков
Организация кода: Используйте пакеты для группировки (например, library.models для Book).
Инкапсуляция: Даже в простом проекте делайте поля private и добавьте геттеры/сеттеры, если нужно изменять.
Массивы vs коллекции: Заметьте ограничения массива (фиксированный размер, ручное управление) — в следующих уроках заменим на ArrayList.
Комментарии: Добавляйте // комментарии к шагам, чтобы код был читаемым.
Версионирование: Если используете Git, создайте репозиторий и закоммитьте начальную версию проекта.
Ресурсы: Документация Oracle по классам и массивам для напоминания синтаксиса.
Практическое задание
Задача 1: Расширьте класс Book, добавив приватное поле isbn (String) и обновите конструктор и метод printDetails() для его включения.
Задача 2: Увеличьте массив до 5 элементов, добавьте больше книг и убедитесь, что вывод работает.
Задача 3: Попробуйте вывести только книги после определенного года — используйте if в цикле перебора массива.
Реализуйте эти задачи самостоятельно, следуя шагам урока. Это поможет закрепить основы перед переходом к коллекциям.
#Java #для_новичков #beginner #Collections #Практика
После реализации протестируйте проект, чтобы убедиться, что всё работает.
Запустите проект:
Правой кнопкой на файле Main.java → Run 'Main.main()'.
В консоли IDE вы должны увидеть вывод списка книг с их деталями.
Отладка:
Если ошибки: Проверьте синтаксис (точки с запятой, скобки).
Используйте отладчик: Установите breakpoint (красная точка слева от строки в main), запустите в debug-режиме (Shift+F9) и шагайте по коду (F8).
Общие проблемы: NullPointerException (если массив не инициализирован), IndexOutOfBoundsException (если выход за пределы массива).
Проверьте вывод:
Убедитесь, что книги выводятся в порядке добавления в массив.
Попробуйте изменить размер массива или добавить больше книг — увидите, как фиксированный размер ограничивает (это мотивирует к коллекциям позже).
Полезные советы для новичков
Организация кода: Используйте пакеты для группировки (например, library.models для Book).
Инкапсуляция: Даже в простом проекте делайте поля private и добавьте геттеры/сеттеры, если нужно изменять.
Массивы vs коллекции: Заметьте ограничения массива (фиксированный размер, ручное управление) — в следующих уроках заменим на ArrayList.
Комментарии: Добавляйте // комментарии к шагам, чтобы код был читаемым.
Версионирование: Если используете Git, создайте репозиторий и закоммитьте начальную версию проекта.
Ресурсы: Документация Oracle по классам и массивам для напоминания синтаксиса.
Практическое задание
Задача 1: Расширьте класс Book, добавив приватное поле isbn (String) и обновите конструктор и метод printDetails() для его включения.
Задача 2: Увеличьте массив до 5 элементов, добавьте больше книг и убедитесь, что вывод работает.
Задача 3: Попробуйте вывести только книги после определенного года — используйте if в цикле перебора массива.
Реализуйте эти задачи самостоятельно, следуя шагам урока. Это поможет закрепить основы перед переходом к коллекциям.
#Java #для_новичков #beginner #Collections #Практика
👍4🔥1
Аспектно-ориентированное программирование в Java (AOP)
АОП — это подход к программированию, который позволяет отделить "сквозные" concerns (это слово значит "заботы" или "аспекты" — повторяющийся код, не связанный с основной логикой, например, логирование или проверка прав доступа) от основной бизнес-логики. В обычном объектно-ориентированном программировании такой код разбросан по всему приложению, что делает его сложным в поддержке. АОП позволяет "вплести" этот код в нужные места автоматически, без изменения основного кода.
Почему АОП полезно?
Представь, что в твоём приложении нужно логировать каждый вызов метода сервиса: записывать, кто вызвал, когда и с какими параметрами. Без АОП ты добавишь строки логирования в каждый метод — это загрязнит код и нарушит принцип "единственной ответственности". С АОП ты создаёшь отдельный "аспект" (модуль для сквозной логики), который автоматически применяется к нужным методам.
Плюсы:
Чистый код: Основная логика не смешивается с вспомогательной.
Легко изменять: Измени аспект — и всё приложение обновится.
Переиспользование: Один аспект для множества мест.
Примеры использования: Логирование, транзакции (атомарные операции с базой данных), кэширование, обработка ошибок, безопасность.
В Java АОП реализуется через библиотеки вроде AspectJ (полноценный язык АОП) или Spring AOP (упрощённая версия, интегрированная в Spring). Spring AOP проще для новичков, использует прокси (заместители объектов) и подходит для большинства задач. Если нужно что-то сложное, как аспекты на уровне полей, переходи к AspectJ, который Spring тоже поддерживает.
Настройка проекта в Spring
Давай создадим простой проект. Предполагаем, у тебя Spring Boot (фреймворк для быстрой разработки). Используй Spring Initializr для генерации.
Добавь зависимости в pom.xml (файл конфигурации сборки Maven):
Включи АОП в конфигурации. В основном классе приложения добавь аннотацию (метку):
Основные понятия АОП
Аспект: Класс с логикой, которая применяется сквозно. Обозначается @Aspect.
Совет (Advice): Что именно делать — до, после или вокруг метода. Например, @Before — перед вызовом.
Точка присоединения (Join Point): Место в коде, где аспект применяется, например, вызов метода.
Точка среза (Pointcut): Выражение, определяющее, где применять аспект, например, все методы в пакете сервисов.
Введение (Introduction): Добавление новых методов или интерфейсов (редко, но мощно).
Вплетение (Weaving): Процесс применения аспекта — в Spring это на этапе выполнения (runtime) через прокси.
#Java #middle #on_request #AOP
АОП — это подход к программированию, который позволяет отделить "сквозные" concerns (это слово значит "заботы" или "аспекты" — повторяющийся код, не связанный с основной логикой, например, логирование или проверка прав доступа) от основной бизнес-логики. В обычном объектно-ориентированном программировании такой код разбросан по всему приложению, что делает его сложным в поддержке. АОП позволяет "вплести" этот код в нужные места автоматически, без изменения основного кода.
Почему АОП полезно?
Представь, что в твоём приложении нужно логировать каждый вызов метода сервиса: записывать, кто вызвал, когда и с какими параметрами. Без АОП ты добавишь строки логирования в каждый метод — это загрязнит код и нарушит принцип "единственной ответственности". С АОП ты создаёшь отдельный "аспект" (модуль для сквозной логики), который автоматически применяется к нужным методам.
Плюсы:
Чистый код: Основная логика не смешивается с вспомогательной.
Легко изменять: Измени аспект — и всё приложение обновится.
Переиспользование: Один аспект для множества мест.
Примеры использования: Логирование, транзакции (атомарные операции с базой данных), кэширование, обработка ошибок, безопасность.
В Java АОП реализуется через библиотеки вроде AspectJ (полноценный язык АОП) или Spring AOP (упрощённая версия, интегрированная в Spring). Spring AOP проще для новичков, использует прокси (заместители объектов) и подходит для большинства задач. Если нужно что-то сложное, как аспекты на уровне полей, переходи к AspectJ, который Spring тоже поддерживает.
Настройка проекта в Spring
Давай создадим простой проект. Предполагаем, у тебя Spring Boot (фреймворк для быстрой разработки). Используй Spring Initializr для генерации.
Добавь зависимости в pom.xml (файл конфигурации сборки Maven):
xml<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
spring-boot-starter-aop включает всё необходимое для АОП.
Включи АОП в конфигурации. В основном классе приложения добавь аннотацию (метку):
javaimport org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableAspectJAutoProxy // Включает автоматическое создание прокси для аспектов
public class AopApplication {
public static void main(String[] args) {
SpringApplication.run(AopApplication.class, args);
}
}
Это говорит Spring: "Используй АОП с прокси".
Основные понятия АОП
Аспект: Класс с логикой, которая применяется сквозно. Обозначается @Aspect.
Совет (Advice): Что именно делать — до, после или вокруг метода. Например, @Before — перед вызовом.
Точка присоединения (Join Point): Место в коде, где аспект применяется, например, вызов метода.
Точка среза (Pointcut): Выражение, определяющее, где применять аспект, например, все методы в пакете сервисов.
Введение (Introduction): Добавление новых методов или интерфейсов (редко, но мощно).
Вплетение (Weaving): Процесс применения аспекта — в Spring это на этапе выполнения (runtime) через прокси.
#Java #middle #on_request #AOP
👍1
Пример: Аспект для логирования
Создадим сервис — класс с бизнес-логикой:
Теперь аспект для логирования:
Более сложный пример: Аспект вокруг метода
Для обработки ошибок или измерения времени используй @Around — он оборачивает метод.
Продвинутые советы для опытных разработчиков
Производительность: Прокси в Spring добавляют overhead (небольшую задержку). Для критичных мест используй compile-time weaving из AspectJ.
Порядок аспектов: Если несколько аспектов на одном методе, используй @Order(1) для приоритета (меньше число — выше приоритет).
Обработка исключений: В @Around лови Throwable, логируй и перебрасывай, чтобы не глотать ошибки.
Тестирование: Используй @EnableAspectJAutoProxy в тестах, моки (заменители) для аспектов с Mockito.
Интеграция с другими модулями: Spring Security или Spring Cache часто используют АОП внутри — изучи их исходники для идей.
Ограничения: Spring AOP работает только на методах бинов (объектов, управляемых Spring). Для статических методов или конструкторов нужен AspectJ.
#Java #middle #on_request #AOP
Создадим сервис — класс с бизнес-логикой:
javaimport org.springframework.stereotype.Service;
@Service // Обозначает, что это сервис, Spring создаст экземпляр
public class MyService {
public String doSomething(String input) {
return "Результат: " + input.toUpperCase(); // Простая логика
}
}
Теперь аспект для логирования:
javaimport org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect // Это аспект
@Component // Spring зарегистрирует его
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))") // Pointcut: все методы в пакете service
public void logBefore(JoinPoint joinPoint) { // JoinPoint — информация о точке
System.out.println("Вызов метода: " + joinPoint.getSignature().getName());
System.out.println("Аргументы: " + Arrays.toString(joinPoint.getArgs()));
}
}
Здесь @Before значит "выполни перед методом". execution — выражение для pointcut: * значит любой возврат, com.example.service..(..) — любой класс в пакете service, любой метод с любыми аргументами.
Если вызвать myService.doSomething("hello"), в консоли увидишь лог перед результатом.
Более сложный пример: Аспект вокруг метода
Для обработки ошибок или измерения времени используй @Around — он оборачивает метод.
javaimport org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TimingAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed(); // Выполняет оригинальный метод
long end = System.currentTimeMillis();
System.out.println("Время выполнения: " + (end - start) + " мс");
return result; // Возвращает результат метода
}
}
ProceedingJoinPoint позволяет контролировать вызов: можно пропустить метод, изменить аргументы или результат. Идеально для транзакций или кэша.
Продвинутые советы для опытных разработчиков
Производительность: Прокси в Spring добавляют overhead (небольшую задержку). Для критичных мест используй compile-time weaving из AspectJ.
Порядок аспектов: Если несколько аспектов на одном методе, используй @Order(1) для приоритета (меньше число — выше приоритет).
Обработка исключений: В @Around лови Throwable, логируй и перебрасывай, чтобы не глотать ошибки.
Тестирование: Используй @EnableAspectJAutoProxy в тестах, моки (заменители) для аспектов с Mockito.
Интеграция с другими модулями: Spring Security или Spring Cache часто используют АОП внутри — изучи их исходники для идей.
Ограничения: Spring AOP работает только на методах бинов (объектов, управляемых Spring). Для статических методов или конструкторов нужен AspectJ.
#Java #middle #on_request #AOP
👍4
Реактивное программирование
Базовые операторы в Reactor: map, filter, flatMap
Операторы — это методы на Mono/Flux, которые позволяют строить конвейеры: преобразовывать, фильтровать и комбинировать данные асинхронно. Представьте их как звенья в цепи: каждый берёт входной поток, меняет его и передаёт дальше. Сегодня разберём три фундаментальных: map (преобразование элементов), filter (фильтрация) и flatMap (плоское преобразование, для слияния подпотоков). Эти операторы — основа для сложных сценариев, они решают проблемы из первого поста, позволяя писать декларативный код вместо ручных циклов и ожиданий.
Операторы в Reactor — декларативные: вы описываете, что делать с данными, а библиотека заботится об асинхронности, backpressure и ошибках. Они не меняют исходный поток (иммутабельны), а создают новый. Это делает код читаемым и тестируемым.
Map: простое преобразование элементов
Map — оператор для изменения каждого элемента потока. Он берёт входной элемент, применяет функцию и выдаёт результат. Синхронный: функция должна быть быстрой и без блокировок. Идеален для конвертации типов, вычислений или форматирования.
Пример на Flux:
На Mono:
Почему map полезен? В традиционных подходах (как в CompletableFuture.thenApply) вы строите цепочки, но рискуете вложенностью. В Reactor map делает конвейер линейным: читается как последовательный код, но работает асинхронно. Поддерживает backpressure: если подписчик запрашивает n, map передаёт запрос upstream (источнику).
Filter: отбор элементов по условию
Filter — для пропуска только нужных элементов. Принимает предикат (функцию, возвращающую true/false) и пропускает те, для которых true. Остальные игнорируются — поток "сужается".
Пример на Flux:
На Mono:
Filter экономит ресурсы: ненужные элементы не обрабатываются дальше в цепи. В отличие от императивных циклов (где вы фильтруете в for с if), здесь всё асинхронно и с backpressure — запросы передаются источнику только для прошедших элементов.
Комбинация с map: numbers.filter(num -> num > 5).map(num -> num * 10).subscribe(); // 60, 70, 80, 90, 100
Это строит конвейер: фильтр → преобразование, без ручных переменных.
#Java #middle #Reactor #map #filter #flatMap
Базовые операторы в Reactor: map, filter, flatMap
Операторы — это методы на Mono/Flux, которые позволяют строить конвейеры: преобразовывать, фильтровать и комбинировать данные асинхронно. Представьте их как звенья в цепи: каждый берёт входной поток, меняет его и передаёт дальше. Сегодня разберём три фундаментальных: map (преобразование элементов), filter (фильтрация) и flatMap (плоское преобразование, для слияния подпотоков). Эти операторы — основа для сложных сценариев, они решают проблемы из первого поста, позволяя писать декларативный код вместо ручных циклов и ожиданий.
Операторы в Reactor — декларативные: вы описываете, что делать с данными, а библиотека заботится об асинхронности, backpressure и ошибках. Они не меняют исходный поток (иммутабельны), а создают новый. Это делает код читаемым и тестируемым.
Map: простое преобразование элементов
Map — оператор для изменения каждого элемента потока. Он берёт входной элемент, применяет функцию и выдаёт результат. Синхронный: функция должна быть быстрой и без блокировок. Идеален для конвертации типов, вычислений или форматирования.
Пример на Flux:
import reactor.core.publisher.Flux;
Flux<String> originalFlux = Flux.just("яблоко", "банан", "вишня");
Flux<String> transformed = originalFlux.map(fruit -> fruit.toUpperCase()); // Преобразование в верхний регистр
transformed.subscribe(System.out::println); // Вывод: "ЯБЛОКО", "БАНАН", "ВИШНЯ"
Здесь map применяет лямбду к каждому элементу последовательно. Если ошибка в функции — сработает onError.
На Mono:
Mono<Integer> num = Mono.just(5).map(x -> x * 2); // Результат: 10
Почему map полезен? В традиционных подходах (как в CompletableFuture.thenApply) вы строите цепочки, но рискуете вложенностью. В Reactor map делает конвейер линейным: читается как последовательный код, но работает асинхронно. Поддерживает backpressure: если подписчик запрашивает n, map передаёт запрос upstream (источнику).
Filter: отбор элементов по условию
Filter — для пропуска только нужных элементов. Принимает предикат (функцию, возвращающую true/false) и пропускает те, для которых true. Остальные игнорируются — поток "сужается".
Пример на Flux:
Flux<Integer> numbers = Flux.range(1, 10);
Flux<Integer> evenNumbers = numbers.filter(num -> num % 2 == 0); // Только чётные
evenNumbers.subscribe(System.out::println); // Вывод: 2, 4, 6, 8, 10
Если поток пустой или ничего не проходит — onComplete сработает без onNext.
На Mono:
Mono<String> word = Mono.just("привет").filter(w -> w.length() > 7); // Не пройдёт — пустой Mono
Filter экономит ресурсы: ненужные элементы не обрабатываются дальше в цепи. В отличие от императивных циклов (где вы фильтруете в for с if), здесь всё асинхронно и с backpressure — запросы передаются источнику только для прошедших элементов.
Комбинация с map: numbers.filter(num -> num > 5).map(num -> num * 10).subscribe(); // 60, 70, 80, 90, 100
Это строит конвейер: фильтр → преобразование, без ручных переменных.
#Java #middle #Reactor #map #filter #flatMap
👍2
FlatMap: плоское преобразование для асинхронных подпотоков
FlatMap — мощный оператор для случаев, когда из одного элемента нужно создать подпоток (Publisher), и слить их в плоский результат. Это как map, но для асинхронных или множественных выходов: он "разворачивает" вложенные потоки. Полезен для запросов в цикле: например, для каждого пользователя — асинхронно запросить данные.
Пример на Flux:
Асинхронный пример: симулируем API-запросы.
Почему flatMap решает проблемы? В традиционных подходах (циклы с Future) вы ждёте каждый запрос, блокируя. Здесь — асинхронное слияние, без ожиданий и callback-ада: цепочка читаема.
Практические советы и подводные камни
Читаемость: цепочки операторов пишите по строкам для ясности: flux.filter(...).map(...).flatMap(...);
Ошибки: если в map/flatMap исключение — onError. Используйте handle() для условной обработки.
Производительность: в flatMap устанавливайте concurrency (default 256) для контроля параллелизма: flatMap(func, 4) — max 4 подпотока одновременно.
Камень: блокирующий код в лямбдах — сломает асинхронность. Для IO — используйте flatMap с Mono.fromCallable и publishOn(Schedulers.boundedElastic()).
Тестирование: StepVerifier.create(flux.map(...)).expectNext("ЯБЛОКО").verifyComplete();
#Java #middle #Reactor #map #filter #flatMap
FlatMap — мощный оператор для случаев, когда из одного элемента нужно создать подпоток (Publisher), и слить их в плоский результат. Это как map, но для асинхронных или множественных выходов: он "разворачивает" вложенные потоки. Полезен для запросов в цикле: например, для каждого пользователя — асинхронно запросить данные.
Пример на Flux:
Flux<String> fruits = Flux.just("яблоко", "банан");
Flux<Character> letters = fruits.flatMap(fruit -> Flux.fromArray(fruit.toCharArray())); // Из строки — поток символов
letters.subscribe(System.out::println); // Вывод: я, б, л, о, к, о, б, а, н, а, н (в возможном перемешанном порядке, если асинхронно)
Здесь flatMap берёт строку, создаёт Flux из символов и сливает всё в один поток. В отличие от map (который вернул бы Flux<Flux<Character>> — вложенный), flatMap "сплющивает".
Асинхронный пример: симулируем API-запросы.
import java.time.Duration;
Flux<String> users = Flux.just("user1", "user2");
Flux<String> data = users.flatMap(user -> Mono.just("Данные для " + user).delayElement(Duration.ofSeconds(1))); // Асинхронный подпоток с задержкой
data.subscribe(System.out::println); // Вывод через секунды: "Данные для user1", "Данные для user2" (параллельно, если scheduler позволяет)
FlatMap уважает backpressure: запрашивает у подпотоков по мере нужды. Но осторожно: если подпотоки бесконечные — рискуете перегрузкой. Параметр concurrency (flatMap(func, concurrency)) ограничивает параллелизм.
Почему flatMap решает проблемы? В традиционных подходах (циклы с Future) вы ждёте каждый запрос, блокируя. Здесь — асинхронное слияние, без ожиданий и callback-ада: цепочка читаема.
Практические советы и подводные камни
Читаемость: цепочки операторов пишите по строкам для ясности: flux.filter(...).map(...).flatMap(...);
Ошибки: если в map/flatMap исключение — onError. Используйте handle() для условной обработки.
Производительность: в flatMap устанавливайте concurrency (default 256) для контроля параллелизма: flatMap(func, 4) — max 4 подпотока одновременно.
Камень: блокирующий код в лямбдах — сломает асинхронность. Для IO — используйте flatMap с Mono.fromCallable и publishOn(Schedulers.boundedElastic()).
Тестирование: StepVerifier.create(flux.map(...)).expectNext("ЯБЛОКО").verifyComplete();
#Java #middle #Reactor #map #filter #flatMap
👍3
Коллекции в Java
Глава 3. Set — множества
Интерфейс Set. Особенности множеств
Интерфейс Set<E> — это часть Java Collections Framework (JCF), который представляет коллекцию уникальных элементов без дубликатов. Set является подинтерфейсом Collection, но с ключевым отличием: он не позволяет хранить одинаковые элементы. Если вы пытаетесь добавить дубликат, операция игнорируется.
Основные характеристики Set:
Уникальность элементов: Да, дубликаты не хранятся.
Упорядоченность: В общем случае нет (зависит от реализации).
Сортировка: Нет по умолчанию, но возможна в подтипах.
Время доступа (Big O): Для основных операций (add, remove, contains) — O(1) в HashSet, O(log n) в TreeSet.
Set моделирует математическое множество: элементы уникальны, нет индексации, фокус на наличии/отсутствии.
Методы Set (наследуются от Collection, но с нюансами):
boolean add(E e): Добавляет элемент, если его нет (возвращает true, если добавлен).
boolean remove(Object o): Удаляет элемент, если он есть.
boolean contains(Object o): Проверяет наличие.
int size(): Размер.
boolean isEmpty(): Пустота.
Iterator<E> iterator(): Для перебора (нет порядка по умолчанию).
void clear(): Очистка.
Нюанс: Set не имеет get(int index) — нет индексации, как в List. Перебор через Iterator или for-each.
Особенности множеств в Java
Множества в Java имеют несколько важных особенностей, которые определяют их использование:
Уникальность элементов:
Set автоматически предотвращает дубликаты на основе методов equals() и hashCode() (для HashSet) или compareTo() (для TreeSet).
Если добавить существующий элемент, add() возвращает false, коллекция не меняется.
Нюанс: Для custom классов обязательно переопределите equals() и hashCode() (используйте Objects.equals() и Objects.hash()). Без этого уникальность по ссылке, не по значению.
Отсутствие гарантированного порядка:
В HashSet порядок непредсказуем (зависит от хэша).
В LinkedHashSet — порядок вставки.
В TreeSet — сортированный порядок.
Нюанс: Не полагайтесь на порядок в HashSet — он может измениться при ресайзе.
Null элементы:
HashSet и LinkedHashSet позволяют один null.
TreeSet — нет (NullPointerException, так как сравнивает).
Нюанс: Null в Set — редко рекомендуется, но возможно.
Итерация и модификация:
Перебор через for-each или Iterator.
Нюанс: Во время итерации нельзя модифицировать Set (ConcurrentModificationException). Используйте Iterator.remove() для удаления.
Производительность:
HashSet: O(1) для add/remove/contains (средний случай).
TreeSet: O(log n), но с автосортировкой.
Нюанс: HashSet требует хорошего hashCode() — плохой приводит к O(n) worst-case.
Синхронизация:
Стандартные реализации не thread-safe. Для многопоточности: Collections.synchronizedSet(Set set) или ConcurrentHashSet (из Guava).
#Java #для_новичков #beginner #Collections #Set
Глава 3. Set — множества
Интерфейс Set. Особенности множеств
Интерфейс Set<E> — это часть Java Collections Framework (JCF), который представляет коллекцию уникальных элементов без дубликатов. Set является подинтерфейсом Collection, но с ключевым отличием: он не позволяет хранить одинаковые элементы. Если вы пытаетесь добавить дубликат, операция игнорируется.
Основные характеристики Set:
Уникальность элементов: Да, дубликаты не хранятся.
Упорядоченность: В общем случае нет (зависит от реализации).
Сортировка: Нет по умолчанию, но возможна в подтипах.
Время доступа (Big O): Для основных операций (add, remove, contains) — O(1) в HashSet, O(log n) в TreeSet.
Set моделирует математическое множество: элементы уникальны, нет индексации, фокус на наличии/отсутствии.
Методы Set (наследуются от Collection, но с нюансами):
boolean add(E e): Добавляет элемент, если его нет (возвращает true, если добавлен).
boolean remove(Object o): Удаляет элемент, если он есть.
boolean contains(Object o): Проверяет наличие.
int size(): Размер.
boolean isEmpty(): Пустота.
Iterator<E> iterator(): Для перебора (нет порядка по умолчанию).
void clear(): Очистка.
Нюанс: Set не имеет get(int index) — нет индексации, как в List. Перебор через Iterator или for-each.
Особенности множеств в Java
Множества в Java имеют несколько важных особенностей, которые определяют их использование:
Уникальность элементов:
Set автоматически предотвращает дубликаты на основе методов equals() и hashCode() (для HashSet) или compareTo() (для TreeSet).
Если добавить существующий элемент, add() возвращает false, коллекция не меняется.
Нюанс: Для custom классов обязательно переопределите equals() и hashCode() (используйте Objects.equals() и Objects.hash()). Без этого уникальность по ссылке, не по значению.
Отсутствие гарантированного порядка:
В HashSet порядок непредсказуем (зависит от хэша).
В LinkedHashSet — порядок вставки.
В TreeSet — сортированный порядок.
Нюанс: Не полагайтесь на порядок в HashSet — он может измениться при ресайзе.
Null элементы:
HashSet и LinkedHashSet позволяют один null.
TreeSet — нет (NullPointerException, так как сравнивает).
Нюанс: Null в Set — редко рекомендуется, но возможно.
Итерация и модификация:
Перебор через for-each или Iterator.
Нюанс: Во время итерации нельзя модифицировать Set (ConcurrentModificationException). Используйте Iterator.remove() для удаления.
Производительность:
HashSet: O(1) для add/remove/contains (средний случай).
TreeSet: O(log n), но с автосортировкой.
Нюанс: HashSet требует хорошего hashCode() — плохой приводит к O(n) worst-case.
Синхронизация:
Стандартные реализации не thread-safe. Для многопоточности: Collections.synchronizedSet(Set set) или ConcurrentHashSet (из Guava).
#Java #для_новичков #beginner #Collections #Set
👍6
Примеры использования Set
HashSet: Для быстрых операций без порядка.
LinkedHashSet: С сохранением порядка вставки.
TreeSet: С автосортировкой.
Нюанс: Для custom классов в TreeSet реализуйте Comparable или используйте Comparator при создании.
Как создать и использовать Set в IntelliJ IDEA
Импорт: В коде напишите Set — IDE предложит import java.util.Set; и реализацию (HashSet и т.д.).
Generics: Используйте Set для типобезопасности.
Автодополнение: При add() IDE подскажет параметры.
Отладка: В debug смотрите содержимое Set — IDE покажет элементы.
Конвертация: Из List в Set: new HashSet<>(list) — для удаления дубликатов.
Полезные советы для новичков
Выбор реализации: HashSet для скорости, LinkedHashSet для порядка, TreeSet для сортировки.
equals() и hashCode(): Всегда переопределяйте в custom классах для Set/Map (используйте @Override и Objects.hash()).
Удаление дубликатов: Set — быстрый способ: new HashSet<>(list).
Null: Избегайте в TreeSet; в HashSet — осторожно.
Итерация: For-each безопасен для чтения, но не модифицируйте во время перебора.
#Java #для_новичков #beginner #Collections #Set
HashSet: Для быстрых операций без порядка.
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<String> fruits = new HashSet<>();
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Яблоко"); // Игнорируется
System.out.println(fruits.size()); // 2
System.out.println(fruits.contains("Банан")); // true
for (String fruit : fruits) {
System.out.println(fruit); // Порядок непредсказуем
}
}
}
Вывод: Размер 2, содержит "Банан", элементы в случайном порядке.
LinkedHashSet: С сохранением порядка вставки.
import java.util.LinkedHashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<String> fruits = new LinkedHashSet<>();
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Апельсин");
for (String fruit : fruits) {
System.out.println(fruit); // Яблоко, Банан, Апельсин — порядок вставки
}
}
}
TreeSet: С автосортировкой.
import java.util.TreeSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<Integer> numbers = new TreeSet<>();
numbers.add(5);
numbers.add(1);
numbers.add(3);
for (Integer num : numbers) {
System.out.println(num); // 1, 3, 5 — отсортировано
}
}
}
Нюанс: Для custom классов в TreeSet реализуйте Comparable или используйте Comparator при создании.
Как создать и использовать Set в IntelliJ IDEA
Импорт: В коде напишите Set — IDE предложит import java.util.Set; и реализацию (HashSet и т.д.).
Generics: Используйте Set для типобезопасности.
Автодополнение: При add() IDE подскажет параметры.
Отладка: В debug смотрите содержимое Set — IDE покажет элементы.
Конвертация: Из List в Set: new HashSet<>(list) — для удаления дубликатов.
Полезные советы для новичков
Выбор реализации: HashSet для скорости, LinkedHashSet для порядка, TreeSet для сортировки.
equals() и hashCode(): Всегда переопределяйте в custom классах для Set/Map (используйте @Override и Objects.hash()).
Удаление дубликатов: Set — быстрый способ: new HashSet<>(list).
Null: Избегайте в TreeSet; в HashSet — осторожно.
Итерация: For-each безопасен для чтения, но не модифицируйте во время перебора.
#Java #для_новичков #beginner #Collections #Set
👍4
Реактивное программирование
Комбинации потоков в Reactor: concat, merge и другие
Комбинации потоков — это как сборка пазла: вы берёте отдельные потоки событий и сливаете в один, управляя порядком, параллелизмом и обработкой. Операторы уважают жизненный цикл и обратное давление: если подписчик не успевает, запросы распределяются по источникам. Это делает системы масштабируемыми — под нагрузкой не тонут в очередях.
Concat: последовательное объединение потоков
Concat — оператор для слияния потоков по очереди: сначала все элементы первого, потом второго и так далее. Он ждёт завершения предыдущего (onComplete), прежде чем перейти к следующему. Идеален, когда порядок важен и параллелизм не нужен: например, загрузка данных по шагам.
Пример на Flux:
На Mono:
Почему concat полезен? В традиционных подходах (thenCompose в CompletableFuture) вы пишете цепочки вручную, рискуя callback-адом. Здесь — декларативно, с автоматическим backpressure: запросы идут к текущему потоку. Минус: медленный, если источники асинхронные — ждёт завершения.
Вариант: concatWith(other) на одном Flux для добавления.
Merge: параллельное слияние по готовности
Merge — для объединения потоков параллельно: элементы выдаются по мере готовности, без ожидания завершения. Порядок не гарантирован — зависит от скорости источников. Идеален для независимых событий: например, слияние логов из сервисов.
Пример:
На Mono: merge работает с Mono как с Flux'ом одного элемента.
Почему merge лучше потоков? В старых моделях (join в Executor) вы ждёте всех, блокируя. Здесь — асинхронно, с backpressure: merge распределяет запросы по источникам пропорционально. Параметр: merge(func, concurrency) для ограничения параллелизма.
Вариант: mergeWith(other) на одном Flux.
#Java #middle #Reactor #Concat #Merge #Zip #CombineLatest
Комбинации потоков в Reactor: concat, merge и другие
Комбинации потоков — это как сборка пазла: вы берёте отдельные потоки событий и сливаете в один, управляя порядком, параллелизмом и обработкой. Операторы уважают жизненный цикл и обратное давление: если подписчик не успевает, запросы распределяются по источникам. Это делает системы масштабируемыми — под нагрузкой не тонут в очередях.
Concat: последовательное объединение потоков
Concat — оператор для слияния потоков по очереди: сначала все элементы первого, потом второго и так далее. Он ждёт завершения предыдущего (onComplete), прежде чем перейти к следующему. Идеален, когда порядок важен и параллелизм не нужен: например, загрузка данных по шагам.
Пример на Flux:
import reactor.core.publisher.Flux;
import java.time.Duration;
Flux<String> first = Flux.just("Шаг 1a", "Шаг 1b").delayElements(Duration.ofSeconds(1)); // Задержка для симуляции
Flux<String> second = Flux.just("Шаг 2a", "Шаг 2b");
Flux<String> combined = Flux.concat(first, second);
combined.subscribe(System.out::println); // Вывод: "Шаг 1a" (через 1с), "Шаг 1b" (ещё 1с), "Шаг 2a", "Шаг 2b"
Здесь concat гарантирует последовательность: второй Flux стартует только после onComplete первого. Если ошибка в первом — весь поток прервётся onError.
На Mono:
Mono<String> m1 = Mono.just("A").delayElement(Duration.ofSeconds(1));
Mono<String> m2 = Mono.just("B");
Flux<String> seq = Flux.concat(m1, m2); // Mono как Flux с одним элементом
Почему concat полезен? В традиционных подходах (thenCompose в CompletableFuture) вы пишете цепочки вручную, рискуя callback-адом. Здесь — декларативно, с автоматическим backpressure: запросы идут к текущему потоку. Минус: медленный, если источники асинхронные — ждёт завершения.
Вариант: concatWith(other) на одном Flux для добавления.
Merge: параллельное слияние по готовности
Merge — для объединения потоков параллельно: элементы выдаются по мере готовности, без ожидания завершения. Порядок не гарантирован — зависит от скорости источников. Идеален для независимых событий: например, слияние логов из сервисов.
Пример:
Flux<String> slow = Flux.just("Медленный 1", "Медленный 2").delayElements(Duration.ofSeconds(2));
Flux<String> fast = Flux.just("Быстрый A", "Быстрый B").delayElements(Duration.ofMillis(500));
Flux<String> merged = Flux.merge(slow, fast);
merged.subscribe(System.out::println); // Возможный вывод: "Быстрый A" (0.5с), "Быстрый B" (ещё 0.5с), "Медленный 1" (2с), "Медленный 2" (ещё 2с)
Здесь merge отдает элементы, как только они готовы — параллельно. Если ошибка в одном — весь поток onError (по умолчанию), но можно настроить.
На Mono: merge работает с Mono как с Flux'ом одного элемента.
Почему merge лучше потоков? В старых моделях (join в Executor) вы ждёте всех, блокируя. Здесь — асинхронно, с backpressure: merge распределяет запросы по источникам пропорционально. Параметр: merge(func, concurrency) для ограничения параллелизма.
Вариант: mergeWith(other) на одном Flux.
#Java #middle #Reactor #Concat #Merge #Zip #CombineLatest
👍2
Zip: попарная комбинация элементов
Zip — объединяет элементы из потоков попарно: берёт первый от первого, первый от второго и т.д., применяя функцию для слияния. Завершается, когда любой поток исчерпан. Идеален для синхронизации: например, zip координат X и Y в точки.
Пример:
Почему zip решает проблемы? Вместо ручных семафоров или ожиданий в циклах, декларативно комбинируете асинхронные источники. Backpressure: запрашивает у всех равномерно.
CombineLatest: комбинация последних элементов
CombineLatest — выдаёт комбинацию последних элементов от каждого потока, как только любой обновляется. Не ждёт пар — всегда использует свежие. Идеален для реального времени: например, комбинация курсов валют.
Пример:
В отличие от zip (строгие пары), здесь — динамика. Backpressure: как в merge.
Другие комбинации: withLatestFrom и concatMap
WithLatestFrom: похож на combineLatest, но "master"-поток (основной) триггерит выдачу, беря последние из второстепенных. Пример: flux.withLatestFrom(other, (main, other) -> main + other).
ConcatMap: как flatMap, но последовательный (как concat внутри). Для orderly асинхронных подпотоков.
Эти дополняют: выбирайте по сценарию — последовательность (concat/concatMap), параллелизм (merge/flatMap) или синхронизация (zip/combineLatest).
Практические советы и подводные камни
Ошибки: по умолчанию onError останавливает всё — используйте onErrorResume для продолжения.
Параллелизм: в merge/flatMap устанавливайте prefetch (буфер) или concurrency для тюнинга.
Камень: бесконечные потоки в merge — рискуете OOM; добавьте take() или limitRate().
Тестирование: StepVerifier.create(Flux.merge(f1, f2)).expectNextMatches(...).verify();
#Java #middle #Reactor #Concat #Merge #Zip #CombineLatest
Zip — объединяет элементы из потоков попарно: берёт первый от первого, первый от второго и т.д., применяя функцию для слияния. Завершается, когда любой поток исчерпан. Идеален для синхронизации: например, zip координат X и Y в точки.
Пример:
Flux<Integer> xCoords = Flux.just(1, 2, 3);
Flux<Integer> yCoords = Flux.just(10, 20, 30, 40); // Лишний элемент игнорируется
Flux<String> points = Flux.zip(xCoords, yCoords, (x, y) -> "(" + x + ", " + y + ")");
points.subscribe(System.out::println); // Вывод: "(1, 10)", "(2, 20)", "(3, 30)"
Здесь zip ждёт пару: если один медленный — задерживает. Для >2 потоков: zip(tuple -> ..., flux1, flux2, flux3).
Почему zip решает проблемы? Вместо ручных семафоров или ожиданий в циклах, декларативно комбинируете асинхронные источники. Backpressure: запрашивает у всех равномерно.
CombineLatest: комбинация последних элементов
CombineLatest — выдаёт комбинацию последних элементов от каждого потока, как только любой обновляется. Не ждёт пар — всегда использует свежие. Идеален для реального времени: например, комбинация курсов валют.
Пример:
Flux<String> stockA = Flux.just("A:100", "A:110").delayElements(Duration.ofSeconds(1));
Flux<String> stockB = Flux.just("B:200").delayElements(Duration.ofSeconds(2));
Flux<String> latest = Flux.combineLatest(stockA, stockB, (a, b) -> a + " + " + b);
latest.subscribe(System.out::println); // Вывод примерно: "A:100 + B:200" (после 2с), "A:110 + B:200" (ещё 1с после)
Здесь combineLatest реагирует на изменения: при обновлении A использует последний B. Для >2: combineLatest(tuple -> ..., fluxes).
В отличие от zip (строгие пары), здесь — динамика. Backpressure: как в merge.
Другие комбинации: withLatestFrom и concatMap
WithLatestFrom: похож на combineLatest, но "master"-поток (основной) триггерит выдачу, беря последние из второстепенных. Пример: flux.withLatestFrom(other, (main, other) -> main + other).
ConcatMap: как flatMap, но последовательный (как concat внутри). Для orderly асинхронных подпотоков.
Эти дополняют: выбирайте по сценарию — последовательность (concat/concatMap), параллелизм (merge/flatMap) или синхронизация (zip/combineLatest).
Практические советы и подводные камни
Ошибки: по умолчанию onError останавливает всё — используйте onErrorResume для продолжения.
Параллелизм: в merge/flatMap устанавливайте prefetch (буфер) или concurrency для тюнинга.
Камень: бесконечные потоки в merge — рискуете OOM; добавьте take() или limitRate().
Тестирование: StepVerifier.create(Flux.merge(f1, f2)).expectNextMatches(...).verify();
#Java #middle #Reactor #Concat #Merge #Zip #CombineLatest
👍3
Коллекции в Java
Глава 3. Set — множества
Реализации: HashSet, LinkedHashSet, TreeSet.
Применение множеств: удаление дубликатов, проверка уникальности
Интерфейс Set<E> имеет несколько реализаций в JCF, каждая оптимизирована под разные сценарии. Все они обеспечивают уникальность элементов, но отличаются по порядку, сортировке и времени операций.
HashSet<E>
Описание: HashSet — самая распространенная реализация Set, основанная на хэш-таблице (HashMap внутри). Она хранит элементы в "корзинах" (buckets) на основе их хэш-кода, что обеспечивает быстрый поиск и добавление.
Особенности:
Уникальность: Да, дубликаты игнорируются.
Порядок: Нет гарантированного порядка (зависит от хэша, может меняться при ресайзе).
Сортировка: Нет.
Null: Разрешен один null.
Big O: Средний случай — O(1) для add, remove, contains (константное время). Worst-case — O(n) при коллизиях хэшей, но редко с хорошим hashCode().
Внутренняя работа: Элементы хранятся как ключи в HashMap (значения — dummy объект). Хэш-код определяет бакет, equals() — проверку уникальности.
Нюансы:
Зависит от hashCode() и equals(): Плохо реализованные методы приводят к коллизиям и снижению производительности.
Ресайз: При заполнении >75% (load factor) таблица удваивается, что может занять O(n) времени.
Thread-safety: Не безопасен для многопоточности — используйте Collections.synchronizedSet(new HashSet<>()).
Память: Занимает больше, чем ArrayList, из-за хэш-таблицы.
Пример кода:
LinkedHashSet<E>
Расширение HashSet с двусвязным списком для сохранения порядка вставки. Внутри — HashMap с LinkedHashMap-логикой.
Особенности:
Уникальность: Да.
Порядок: Сохраняет порядок вставки (insertion order).
Сортировка: Нет.
Null: Разрешен один null.
Big O: O(1) для add/remove/contains (как HashSet), но с overhead на ссылки списка.
Внутренняя работа: Каждый элемент имеет ссылки prev/next для списка, плюс хэш-таблица для уникальности.
Нюансы:
Больше памяти, чем HashSet (из-за ссылок).
Итерация: O(n), но предсказуема по порядку вставки.
Полезен для кэшей с LRU (least recently used), но для простого порядка — эффективен.
Thread-safety: Нет, как у HashSet.
Пример кода:
#Java #для_новичков #beginner #Collections #HashSet #LinkedHashSet #TreeSet
Глава 3. Set — множества
Реализации: HashSet, LinkedHashSet, TreeSet.
Применение множеств: удаление дубликатов, проверка уникальности
Интерфейс Set<E> имеет несколько реализаций в JCF, каждая оптимизирована под разные сценарии. Все они обеспечивают уникальность элементов, но отличаются по порядку, сортировке и времени операций.
HashSet<E>
Описание: HashSet — самая распространенная реализация Set, основанная на хэш-таблице (HashMap внутри). Она хранит элементы в "корзинах" (buckets) на основе их хэш-кода, что обеспечивает быстрый поиск и добавление.
Особенности:
Уникальность: Да, дубликаты игнорируются.
Порядок: Нет гарантированного порядка (зависит от хэша, может меняться при ресайзе).
Сортировка: Нет.
Null: Разрешен один null.
Big O: Средний случай — O(1) для add, remove, contains (константное время). Worst-case — O(n) при коллизиях хэшей, но редко с хорошим hashCode().
Внутренняя работа: Элементы хранятся как ключи в HashMap (значения — dummy объект). Хэш-код определяет бакет, equals() — проверку уникальности.
Нюансы:
Зависит от hashCode() и equals(): Плохо реализованные методы приводят к коллизиям и снижению производительности.
Ресайз: При заполнении >75% (load factor) таблица удваивается, что может занять O(n) времени.
Thread-safety: Не безопасен для многопоточности — используйте Collections.synchronizedSet(new HashSet<>()).
Память: Занимает больше, чем ArrayList, из-за хэш-таблицы.
Пример кода:
javaimport java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<String> fruits = new HashSet<>();
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Яблоко"); // Игнорируется
System.out.println(fruits.contains("Банан")); // true
fruits.remove("Яблоко");
for (String fruit : fruits) {
System.out.println(fruit); // Порядок непредсказуем, например: Банан
}
}
}
Вывод: contains вернет true, размер 1 после удаления, порядок случайный.
LinkedHashSet<E>
Расширение HashSet с двусвязным списком для сохранения порядка вставки. Внутри — HashMap с LinkedHashMap-логикой.
Особенности:
Уникальность: Да.
Порядок: Сохраняет порядок вставки (insertion order).
Сортировка: Нет.
Null: Разрешен один null.
Big O: O(1) для add/remove/contains (как HashSet), но с overhead на ссылки списка.
Внутренняя работа: Каждый элемент имеет ссылки prev/next для списка, плюс хэш-таблица для уникальности.
Нюансы:
Больше памяти, чем HashSet (из-за ссылок).
Итерация: O(n), но предсказуема по порядку вставки.
Полезен для кэшей с LRU (least recently used), но для простого порядка — эффективен.
Thread-safety: Нет, как у HashSet.
Пример кода:
javaimport java.util.LinkedHashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<String> fruits = new LinkedHashSet<>();
fruits.add("Яблоко");
fruits.add("Банан");
fruits.add("Апельсин");
for (String fruit : fruits) {
System.out.println(fruit); // Яблоко, Банан, Апельсин — порядок вставки
}
}
}
Вывод: Элементы в том порядке, в котором добавлены.
#Java #для_новичков #beginner #Collections #HashSet #LinkedHashSet #TreeSet
👍3
TreeSet<E>
Реализация SortedSet<E>, основанная на красно-черном дереве. Автоматически сортирует элементы.
Особенности:
Уникальность: Да.
Порядок: Нет (игнорирует порядок вставки), но отсортирован по натуральному порядку или Comparator.
Сортировка: Да, всегда отсортирован.
Null: Не разрешен (NullPointerException при сравнении).
Big O: O(log n) для add/remove/contains (дерево балансировано).
Внутренняя работа: Элементы хранятся в узлах дерева, сравниваются через Comparable.compareTo() или Comparator.
Нюансы:
Элементы должны реализовывать Comparable<E> или предоставить Comparator при создании: new TreeSet<>(comparator).
Для custom классов: Реализуйте Comparable или Comparator, иначе ClassCastException.
Итерация: O(n), в отсортированном порядке.
Дополнительные методы: first(), last(), headSet(E to), tailSet(E from) — для подмножеств.
Thread-safety: Нет, используйте Collections.synchronizedSortedSet(new TreeSet<>()).
Память: Больше, чем HashSet, из-за структуры дерева.
Пример кода:
Применение множеств: Удаление дубликатов и проверка уникальности
Множества идеальны для задач, где нужна уникальность без дубликатов.
Удаление дубликатов:
Преобразуйте List или массив в Set — дубликаты автоматически удалятся.
Нюанс: Порядок может потеряться (используйте LinkedHashSet для сохранения).
Пример кода:
Проверка уникальности:
Используйте contains() для быстрой проверки наличия (O(1) в HashSet).
Или add() — если false, элемент уже есть.
Нюанс: Для больших данных Set эффективнее, чем перебор List (O(n)).
Пример кода:
Полезные советы для новичков
HashSet по умолчанию: Для большинства случаев уникальности.
Custom классы: Всегда реализуйте equals() и hashCode() (используйте IDE: Generate → equals() and hashCode()).
Comparator в TreeSet: Для custom сортировки: new TreeSet<>((a, b) -> a.compareTo(b)).
Удаление дубликатов: Удобно для списков из файлов или БД.
Память: TreeSet дороже по памяти, HashSet — оптимален.
Ошибки: ClassCastException в TreeSet без Comparable; ConcurrentModificationException при модификации во время итерации (используйте Iterator.remove()).
#Java #для_новичков #beginner #Collections #HashSet #LinkedHashSet #TreeSet
Реализация SortedSet<E>, основанная на красно-черном дереве. Автоматически сортирует элементы.
Особенности:
Уникальность: Да.
Порядок: Нет (игнорирует порядок вставки), но отсортирован по натуральному порядку или Comparator.
Сортировка: Да, всегда отсортирован.
Null: Не разрешен (NullPointerException при сравнении).
Big O: O(log n) для add/remove/contains (дерево балансировано).
Внутренняя работа: Элементы хранятся в узлах дерева, сравниваются через Comparable.compareTo() или Comparator.
Нюансы:
Элементы должны реализовывать Comparable<E> или предоставить Comparator при создании: new TreeSet<>(comparator).
Для custom классов: Реализуйте Comparable или Comparator, иначе ClassCastException.
Итерация: O(n), в отсортированном порядке.
Дополнительные методы: first(), last(), headSet(E to), tailSet(E from) — для подмножеств.
Thread-safety: Нет, используйте Collections.synchronizedSortedSet(new TreeSet<>()).
Память: Больше, чем HashSet, из-за структуры дерева.
Пример кода:
javaimport java.util.TreeSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<Integer> numbers = new TreeSet<>();
numbers.add(5);
numbers.add(1);
numbers.add(3);
for (Integer num : numbers) {
System.out.println(num); //отсортировано
}
// С Comparator для обратного порядка
Set<Integer> reverseNumbers = new TreeSet<>((a, b) -> b - a);
reverseNumbers.add(5);
reverseNumbers.add(1);
reverseNumbers.add(3);
System.out.println(reverseNumbers); // [5, 3, 1]
}
}
Вывод: Элементы всегда отсортированы.
Применение множеств: Удаление дубликатов и проверка уникальности
Множества идеальны для задач, где нужна уникальность без дубликатов.
Удаление дубликатов:
Преобразуйте List или массив в Set — дубликаты автоматически удалятся.
Нюанс: Порядок может потеряться (используйте LinkedHashSet для сохранения).
Пример кода:
javaimport java.util.ArrayList;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
List<String> duplicates = new ArrayList<>();
duplicates.add("Яблоко");
duplicates.add("Банан");
duplicates.add("Яблоко");
Set<String> unique = new HashSet<>(duplicates);
System.out.println(unique); // [Банан, Яблоко]
// С сохранением порядка
Set<String> orderedUnique = new LinkedHashSet<>(duplicates);
System.out.println(orderedUnique); // [Яблоко, Банан]
}
}
Вывод: Уникальные элементы, без дубликатов.
Проверка уникальности:
Используйте contains() для быстрой проверки наличия (O(1) в HashSet).
Или add() — если false, элемент уже есть.
Нюанс: Для больших данных Set эффективнее, чем перебор List (O(n)).
Пример кода:
javaimport java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<String> users = new HashSet<>();
users.add("user1");
if (users.contains("user2")) {
System.out.println("Пользователь существует");
} else {
users.add("user2");
System.out.println("Новый пользователь добавлен");
}
if (!users.add("user1")) {
System.out.println("Дубликат не добавлен"); // add возвращает false
}
}
}
Вывод: Проверка и добавление без дубликатов.
Полезные советы для новичков
HashSet по умолчанию: Для большинства случаев уникальности.
Custom классы: Всегда реализуйте equals() и hashCode() (используйте IDE: Generate → equals() and hashCode()).
Comparator в TreeSet: Для custom сортировки: new TreeSet<>((a, b) -> a.compareTo(b)).
Удаление дубликатов: Удобно для списков из файлов или БД.
Память: TreeSet дороже по памяти, HashSet — оптимален.
Ошибки: ClassCastException в TreeSet без Comparable; ConcurrentModificationException при модификации во время итерации (используйте Iterator.remove()).
#Java #для_новичков #beginner #Collections #HashSet #LinkedHashSet #TreeSet
👍3
Реактивное программирование
Обработка ошибок в реактивных стримах
Представьте ошибки как пороги в потоке событий: без обработки они останавливают течение, но с правильными операторами — поток продолжается, минимизируя простои. Это решает проблемы из первого поста: вместо жёстких сбоев в потоках или Future, где ошибка рушит всё, реактивный подход даёт контроль и устойчивость.
Обработка ошибок в Reactor строится на жизненном цикле: когда исключение возникает в потоке (в map, flatMap или источнике), срабатывает onError, прерывая onNext и onComplete. Но вместо того чтобы "падать", вы можете трансформировать ошибку в данные, повторить попытку или логировать.
Операторы — декларативные: добавляете в цепочку, и Reactor управляет асинхронностью, backpressure и распространением ошибок. Это делает код resilient (устойчивым): приложение не крашится, а адаптируется.
Базовая реакция: doOnError и onErrorMap
Сначала — простые операторы для наблюдения и модификации ошибок, без изменения потока.
- doOnError: дополнительная реакция на ошибку, как "хук" (зацепка). Полезен для логирования или метрик, не влияет на основной onError.
- onErrorMap: преобразует исключение в другое, для кастомизации (например, оборачивает в бизнес-ошибку).
Пример на Flux:
На Mono: аналогично, но для одиночного элемента.
Эти операторы решают проблему традиционных try-catch: вместо разбросанных блоков, всё в конвейере, читаемо и централизовано.
Восстановление: onErrorReturn и onErrorResume
Когда ошибка — не конец света, используйте fallback.
- onErrorReturn: возвращает фиксированное значение вместо ошибки. Простой запасной вариант.
- onErrorResume: более гибкий — заменяет ошибку новым Publisher (Mono/Flux). Можно генерировать динамически, в зависимости от исключения.
Пример с onErrorReturn:
С onErrorResume — условно:
onErrorResume позволяет ветвление: проверь тип ошибки и верни альтернативный поток. Это асинхронно: если fallback — Mono.delay, оно подождёт без блокировки.
Почему лучше CompletableFuture.handle?
Нет вложенных колбэков — цепочка линейна, ошибки интегрированы в конвейер.
#Java #middle #Reactor #doOnError #onErrorMap
Обработка ошибок в реактивных стримах
Представьте ошибки как пороги в потоке событий: без обработки они останавливают течение, но с правильными операторами — поток продолжается, минимизируя простои. Это решает проблемы из первого поста: вместо жёстких сбоев в потоках или Future, где ошибка рушит всё, реактивный подход даёт контроль и устойчивость.
Обработка ошибок в Reactor строится на жизненном цикле: когда исключение возникает в потоке (в map, flatMap или источнике), срабатывает onError, прерывая onNext и onComplete. Но вместо того чтобы "падать", вы можете трансформировать ошибку в данные, повторить попытку или логировать.
Операторы — декларативные: добавляете в цепочку, и Reactor управляет асинхронностью, backpressure и распространением ошибок. Это делает код resilient (устойчивым): приложение не крашится, а адаптируется.
Базовая реакция: doOnError и onErrorMap
Сначала — простые операторы для наблюдения и модификации ошибок, без изменения потока.
- doOnError: дополнительная реакция на ошибку, как "хук" (зацепка). Полезен для логирования или метрик, не влияет на основной onError.
- onErrorMap: преобразует исключение в другое, для кастомизации (например, оборачивает в бизнес-ошибку).
Пример на Flux:
import reactor.core.publisher.Flux;
import java.io.IOException;
Flux<String> riskyFlux = Flux.just("данные1", "данные2").map(data -> {
if (data.equals("данные2")) throw new IOException("Сбой ввода-вывода");
return data.toUpperCase();
});
riskyFlux
.doOnError(e -> System.err.println("Лог: " + e.getMessage())) // Логируем
.onErrorMap(e -> new RuntimeException("Обёрнутая ошибка: " + e)) // Преобразуем
.subscribe(
System.out::println,
error -> System.err.println("Финальная ошибка: " + error) // onError в подписке
);
// Вывод: "ДАННЫЕ1", потом лог "Сбой ввода-вывода", и финальная "Обёрнутая ошибка: ..."
Здесь doOnError срабатывает перед onErrorMap, а подписка ловит модифицированную ошибку. Это асинхронно: если ошибка в асинхронном подпотоке (flatMap), Reactor передаёт её downstream (дальше по цепи) без блокировок.
На Mono: аналогично, но для одиночного элемента.
Эти операторы решают проблему традиционных try-catch: вместо разбросанных блоков, всё в конвейере, читаемо и централизовано.
Восстановление: onErrorReturn и onErrorResume
Когда ошибка — не конец света, используйте fallback.
- onErrorReturn: возвращает фиксированное значение вместо ошибки. Простой запасной вариант.
- onErrorResume: более гибкий — заменяет ошибку новым Publisher (Mono/Flux). Можно генерировать динамически, в зависимости от исключения.
Пример с onErrorReturn:
Mono<String> httpMono = Mono.fromCallable(() -> {
// Симулируем HTTP-запрос
throw new RuntimeException("Сервер не отвечает");
}).onErrorReturn("Кэшированные данные");
httpMono.subscribe(System.out::println); // Вывод: "Кэшированные данные", потом onComplete
Здесь ошибка преобразуется в значение, поток завершается успешно.
С onErrorResume — условно:
Flux<Integer> calcFlux = Flux.range(1, 5).map(i -> {
if (i == 3) throw new ArithmeticException("Деление на ноль");
return 10 / (i - 3); // Симуляция
}).onErrorResume(e -> {
if (e instanceof ArithmeticException) {
return Flux.just(0, 0); // Fallback на нули
} else {
return Flux.error(e); // Пропустить другие ошибки
}
});
calcFlux.subscribe(System.out::println); // Вывод: элементы до ошибки, потом 0, 0, onComplete
onErrorResume позволяет ветвление: проверь тип ошибки и верни альтернативный поток. Это асинхронно: если fallback — Mono.delay, оно подождёт без блокировки.
Почему лучше CompletableFuture.handle?
Нет вложенных колбэков — цепочка линейна, ошибки интегрированы в конвейер.
#Java #middle #Reactor #doOnError #onErrorMap
Retry: повтор попыток при ошибке
Retry — для transient (временных) ошибок: сеть, временный сбой. Повторяет upstream (источник) заданное число раз.
- retry(long times): простая повторка.
- retryWhen(Retry strategy): с кастомной логикой (backoff — задержка, условия).
Пример базовый:
С retryWhen для экспоненциальной задержки (backoff):
Это предотвращает "молоток" (hammering) сервера: ждёт перед retry. Асинхронно: задержки не блокируют поток.
Retry решает боли блокировок: вместо цикла с sleep в традиционном коде, декларативно и эффективно.
Практические советы и подводные камни
- Комбинируйте: riskyFlux.doOnError(log).onErrorResume(fallback).retry(2) — лог + retry + fallback.
- Условия: в onErrorResume используйте instanceof для типов ошибок, чтобы не catch всё подряд.
- Глобально: используйте Hooks.onErrorDropped для непойманных ошибок (редко).
- Камень: retry на бесконечных потоках — вечный цикл; добавьте timeout() или maxAttempts.
- Тестирование: StepVerifier.create(flux).expectErrorMatches(e -> e instanceof IOException).verify();
В практике: в WebFlux — контроллер возвращает Mono с retry для внешних API, onErrorReturn для кэша.
плавному восстановлению, экономя ресурсы и упрощая код.
#Java #middle #Reactor #doOnError #onErrorMap
Retry — для transient (временных) ошибок: сеть, временный сбой. Повторяет upstream (источник) заданное число раз.
- retry(long times): простая повторка.
- retryWhen(Retry strategy): с кастомной логикой (backoff — задержка, условия).
Пример базовый:
Mono<String> flakyMono = Mono.defer(() -> {
if (Math.random() > 0.3) throw new RuntimeException("Временный сбой");
return Mono.just("Успех");
}).retry(3); // Повторить 3 раза
flakyMono.subscribe(System.out::println, Throwable::printStackTrace);
Здесь retry повторяет весь Mono при ошибке, до успеха или исчерпания попыток. Если все попытки fail — финальный onError.
С retryWhen для экспоненциальной задержки (backoff):
import reactor.util.retry.Retry;
flakyMono.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))); // 3 попытки с задержкой 1с, 2с, 4с
Это предотвращает "молоток" (hammering) сервера: ждёт перед retry. Асинхронно: задержки не блокируют поток.
Retry решает боли блокировок: вместо цикла с sleep в традиционном коде, декларативно и эффективно.
Практические советы и подводные камни
- Комбинируйте: riskyFlux.doOnError(log).onErrorResume(fallback).retry(2) — лог + retry + fallback.
- Условия: в onErrorResume используйте instanceof для типов ошибок, чтобы не catch всё подряд.
- Глобально: используйте Hooks.onErrorDropped для непойманных ошибок (редко).
- Камень: retry на бесконечных потоках — вечный цикл; добавьте timeout() или maxAttempts.
- Тестирование: StepVerifier.create(flux).expectErrorMatches(e -> e instanceof IOException).verify();
В практике: в WebFlux — контроллер возвращает Mono с retry для внешних API, onErrorReturn для кэша.
плавному восстановлению, экономя ресурсы и упрощая код.
#Java #middle #Reactor #doOnError #onErrorMap