Java for Beginner
745 subscribers
714 photos
201 videos
12 files
1.16K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Аспектно-ориентированное программирование в Java (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
Пример: Аспект для логирования

Создадим сервис — класс с бизнес-логикой:
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:
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:
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
👍6
Примеры использования 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:
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 в точки.

Пример:
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, из-за хэш-таблицы.


Пример кода:
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, из-за структуры дерева.


Пример кода:
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:
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
👍1
Retry: повтор попыток при ошибке

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
👍1