Java for Beginner
744 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
Упорядоченность элементов

Упорядоченность — это сохранение порядка элементов (по вставке или сортировке).

Коллекции с порядком вставки:
- List<E>: Всегда упорядочены по индексу (порядок добавления).
- Пример: ArrayList, LinkedList.
- LinkedHashSet<E>: Set с порядком вставки.
- LinkedHashMap<K, V>: Map с порядком вставки.

Коллекции без порядка:
- HashSet<E>: Не гарантирует порядок (зависит от хэша).
- HashMap<K, V>: Не упорядочен.


Отсортированные коллекции: Подробнее в разделе о сортировке.

Пример:
import java.util.HashSet;
import java.util.LinkedHashSet;

public class Main {
public static void main(String[] args) {
// HashSet: Без порядка
Set<String> hashSet = new HashSet<>();
hashSet.add("Яблоко");
hashSet.add("Банан");
hashSet.add("Апельсин");
System.out.println(hashSet); // Может быть [Банан, Яблоко, Апельсин] — непредсказуемо

// LinkedHashSet: Порядок вставки
Set<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("Яблоко");
linkedSet.add("Банан");
linkedSet.add("Апельсин");
System.out.println(linkedSet); // [Яблоко, Банан, Апельсин] — сохраняет порядок
}
}


- Нюанс: Порядок в HashSet может меняться при ресайзе (rehashing), так что не полагайтесь на него.


Сортировка элементов

Сортировка — это автоматическое упорядочивание элементов по какому-то критерию (натуральный порядок или Comparator).

Отсортированные коллекции:
- SortedSet<E> (подинтерфейс Set): Уникальные, отсортированные элементы.
- Реализация: TreeSet<E> — использует красно-черное дерево.
- SortedMap<K, V> (подинтерфейс Map): Отсортированные ключи.
- Реализация: TreeMap<K, V>.


Как работает сортировка:
- Элементы должны быть Comparable<E> (метод compareTo()) для натурального порядка (числа по возрастанию, строки по алфавиту).
- Или используйте Comparator при создании: new TreeSet<>(comparator).
- Нюанс: Добавление/поиск — O(log n), так как дерево балансировано.


Пример 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);
System.out.println(numbers); // [1, 3, 5] — отсортировано

// Custom 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]
}
}


- Нюанс: TreeSet не позволяет null (NullPointerException), так как сравнивает элементы.
Для сортировки существующих коллекций используйте Collections.sort(List list) или List.sort(Comparator).



Полезные советы для новичков

- Big O в практике: Для малого n (сотни элементов) разница мала, но для миллионов — критична (выберите HashSet для быстрого contains).
- Уникальность: Set для словарей, List для списков с повторами.
- Упорядоченность: Linked* для сохранения вставки, Tree* для автосортировки.
- Сортировка: TreeSet/Map для постоянной сортировки; Collections.sort() для разовой.
- Ошибки: Null в TreeSet — NPE; дубли в Set — игнор.
- Ресурсы:
Java API docs для java.util.


#Java #для_новичков #beginner #Collections #BigO
👍4
Реактивное программирование

Подписка и жизненный цикл в Reactor: onNext, onError, onComplete

Reactor делает код декларативным — вы описываете поток событий, а не управляете ожиданиями вручную.
Но чтобы потоки "ожили", нужна подписка: это момент, когда издатель начинает передавать данные, а подписчик реагирует. Подписка и жизненный цикл в Reactor - это фундамент: без понимания, как срабатывают onNext (реакция на элемент), onError (обработка ошибки) и onComplete (завершение), ваши реактивные конвейеры останутся мёртвыми.
Представьте жизненный цикл как этапы реки: подписка — запуск потока, onNext — течение воды, onError — буря, onComplete — устье.


Подписка — это связь между издателем (Publisher, как Mono/Flux) и подписчиком (Subscriber). Она запускает поток данных асинхронно, без блокировок. Жизненный цикл определяет порядок событий: от старта до конца или ошибки. Это решает проблемы из первого поста — вместо callback-ада и ручных get(), вы получаете структурированные реакции. Всё построено на Reactive Streams API, с обратным давлением.


Подписка: запуск потока данных

В Reactor Mono или Flux — ленивые: они ничего не делают, пока не подпишетесь. Метод subscribe() — это триггер: он регистрирует подписчика и начинает передавать события (push, из поста 3).
Подписка асинхронна: после subscribe() код продолжается сразу, без ожидания завершения.


Базовые варианты subscribe():
subscribe(): без параметров — данные "проглатываются", но поток запускается. Полезно для fire-and-forget (запустил и забыл).
subscribe(Consumer<T> onNext): только реакция на элементы.
subscribe(Consumer<T> onNext, Consumer<Throwable> onError): плюс обработка ошибок.
subscribe(Consumer<T> onNext, Consumer<Throwable> onError, Runnable onComplete): полный цикл.
subscribe(Subscriber<T> subscriber): кастомный подписчик для полного контроля (с onSubscribe для Subscription).


Пример простейшей подписки на Flux:
import reactor.core.publisher.Flux;

Flux<String> fruitsFlux = Flux.just("яблоко", "банан", "вишня");
fruitsFlux.subscribe(
fruit -> System.out.println("Съедено: " + fruit), // onNext: реакция на каждый элемент
error -> System.err.println("Проблема: " + error.getMessage()), // onError: если ошибка
() -> System.out.println("Фрукты закончились") // onComplete: завершение
);

Вывод: "Съедено: яблоко", "Съедено: банан", "Съедено: вишня", "Фрукты закончились". Если добавить ошибку — Flux.just("яблоко").concatWith(Flux.error(new RuntimeException("Гнилой фрукт"))) — сработает onError, и onComplete не вызовется.


Почему это лучше традиционных? В CompletableFuture вы цепляете thenApply/thenAccept, но рискуете вложенностью. В Reactor subscribe() — точка входа, а реакции — в одном месте. Плюс, подписка возвращает Disposable: объект для отмены (dispose()) в любой момент.

Пример:
Disposable disposable = fruitsFlux.subscribe(...);
disposable.dispose(); // Отмена: поток остановится, onComplete не сработает.
Это полезно для UI или долгоживущих потоков: отпишись, когда компонент уничтожен, чтобы избежать утечек памяти.



#Java #middle #Reactor #Reactive_Streams_API #onNext #onError #onComplet
👍3
Жизненный цикл: этапы от старта до финиша

Жизненный цикл в Reactive Streams — последовательность вызовов: onSubscribe → (onNext)* → (onError | onComplete). Это правило: после onError или onComplete ничего не будет, и поток считается завершённым.

onSubscribe(Subscription s): первый вызов после subscribe(). Здесь подписчик получает Subscription для контроля (request(n) для backpressure или cancel()). Без request() данные не потекут — это защита от перегрузки.
onNext(T item): для каждого элемента. Здесь основная логика: обработка, логирование, трансформация. Может вызываться много раз (в Flux) или раз/никогда (в Mono).
Важно: держите onNext быстрым и неблокирующим — если медленный, перенесите на отдельный планировщик (Schedulers).
onError(Throwable t): если ошибка (исключение). Поток прерывается, onComplete не сработает. Обработайте, чтобы не потерять: логируйте, retry (повторите) или fallback (запасной вариант).
onComplete(): успешное завершение. Нет элементов после, но сигнал, что всё ок.


Пример полного цикла с кастомным подписчиком (BaseSubscriber в Reactor упрощает):

import reactor.core.publisher.BaseSubscriber;

Flux<Integer> numbersFlux = Flux.range(1, 10).map(i -> {
if (i == 5) throw new RuntimeException("Ошибка на 5"); // Симулируем ошибку
return i;
});

numbersFlux.subscribe(new BaseSubscriber<Integer>() {

@Override
protected void hookOnSubscribe(Subscription subscription) {
System.out.println("Подписка готова");
request(3); // Запрашиваем первые 3
}

@Override
protected void hookOnNext(Integer value) {
System.out.println("Элемент: " + value);
request(1); // Запрашиваем по одному дальше
}

@Override
protected void hookOnError(Throwable throwable) {
System.err.println("Ошибка: " + throwable.getMessage());
}

@Override
protected void hookOnComplete() {
System.out.println("Цикл завершён");
}
});

Вывод: "Подписка готова", "Элемент: 1", "Элемент: 2", "Элемент: 3", "Элемент: 4", "Ошибка: Ошибка на 5". onComplete не сработает, потому что ошибка. Без ошибки — все 10 элементов и "Цикл завершён".

Это демонстрирует контроль: request() управляет темпом, как в backpressure (пост 5). Если не request() — только "Подписка готова".


Обработка ошибок и завершения: стратегии для устойчивости

Ошибки — часть жизни: сеть упала, БД не ответила. В Reactor onError — ваш щит. Не игнорируйте: используйте операторы для восстановления.

doOnError(Consumer<Throwable>): дополнительная реакция перед onError подписчика.
onErrorReturn(T value): fallback — верни значение вместо ошибки.
onErrorResume(Function<Throwable, Publisher<T>>): замени на другой поток.
retry(long times): повтори попытку.


Пример с восстановлением:
Mono<String> riskyMono = Mono.fromCallable(() -> {
if (Math.random() > 0.5) throw new RuntimeException("Сбой");
return "Успех";
}).onErrorReturn("Fallback").retry(2); // Retry 2 раза

riskyMono.subscribe(
System.out::println,
error -> System.out.println("Не удалось: " + error),
() -> System.out.println("Завершено")
);

Если сбой — retry, потом fallback. onComplete сработает только при успехе или fallback.

Для onComplete: используйте doFinally(Runnable) — сработает всегда, после onComplete или onError. Полезно для закрытия ресурсов: doFinally(() -> connection.close()).


Практические советы и подводные камни

Всегда реализуйте все методы в Subscriber: иначе дефолтные могут "проглотить" ошибки (onError кидает RuntimeException, если не переопределён).
Используйте Hooks: doOnSubscribe, doOnNext для логирования без изменения потока.
Отмена: dispose() не гарантирует мгновенную остановку — upstream (источник) может продолжить, но данные не дойдут.
Камень: в onNext избегайте блокировок (sleep, IO) — используйте publishOn(Schedulers.boundedElastic()) для переноса.
Тестирование: StepVerifier.create(flux).expectSubscription().expectNext(1,2).expectError().verify(); — проверяет цикл.


В практике: в WebFlux сервис возвращает Flux, клиент subscribe() в контроллере — реакции на события в реальном времени.


#Java #middle #Reactor #Reactive_Streams_API #onNext #onError #onComplet
👍3
Раздел 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 с базовой структурой

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 #Практика
👍4🔥1
Аспектно-ориентированное программирование в 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