Future: обещание результата, но с подвохом
В Java 5 ввели Future — это как "чек" на будущий результат.
Вы подаёте задачу в executor и получаете объект Future, который обещает: "когда-нибудь я дам тебе ответ".
Пример:
Плюс в том, что вы можете продолжать работу, не дожидаясь: пока задача крутится в фоне, основной код идёт дальше. Но чтобы забрать результат, нужно вызвать future.get(). И вот здесь засада: get() блокирует текущий поток до тех пор, пока задача не завершится. Если задача задерживается — скажем, из-за сети, — ваш поток тоже висит в ожидании. Получается, асинхронность иллюзорна: да, запуск асинхронный, но использование результата синхронное и блокирующее. Это как заказать еду по доставке, но стоять у двери, не отходя, пока курьер не приедет. Выигрыш минимален, особенно в веб-приложениях, где запросы должны обрабатываться быстро.
Ещё Future неудобен в композиции: если нужно объединить результаты нескольких задач, приходится вручную ждать каждого get(), что приводит к спагетти-коду с try-catch для ошибок и таймаутами.
CompletableFuture: цепочки действий, но без избавления от ада
Java 8 принесла CompletableFuture — улучшенную версию Future, которая позволяет строить цепочки асинхронных операций без блокировок на get(). Теперь результат можно обрабатывать через "колбэки" — функции, которые вызываются автоматически по завершении.
Пример:
Есть методы для комбинации: thenCompose для последовательных цепочек, thenCombine для параллельного объединения результатов, handle для обработки ошибок. Это шаг вперёд: код становится declarative (описательным), вы фокусируетесь на "что сделать", а не "как ждать". Нет нужды в ручном get() — всё течёт само.
Но радость недолговечна. Когда приложение усложняется — например, нужно асинхронно запросить данные из базы, потом из внешнего API, обработать ошибки и объединить, — цепочки лямбд растут в "callback-ад" (ад колбэков): вложенные функции, которые трудно читать, отлаживать и тестировать. Один уровень — ок, но пять-шесть — и код превращается в пирамиду, где сложно отследить поток выполнения.
Ещё хуже: под капотом блокировки никуда не делись. Если в цепочке есть блокирующий вызов — например, Thread.sleep() для симуляции задержки или JDBC-драйвер, который ждёт ответа от базы, блокируя поток, — весь CompletableFuture теряет преимущество. Поток из пула всё равно занят ожиданием, и под нагрузкой система снова захлёбывается. Плюс, управление ошибками в цепочках требует осторожности: одна ошибка может сломать всю последовательность, если не обработать timely.
В итоге, CompletableFuture дал выразительный синтаксис и удобство для простых сценариев, но не решил системные проблемы: ресурсы тратятся впустую на блокировки, сложность растёт, а масштабируемость под вопросом.
#Java #middle #Reactor
В Java 5 ввели Future — это как "чек" на будущий результат.
Вы подаёте задачу в executor и получаете объект Future, который обещает: "когда-нибудь я дам тебе ответ".
Пример:
ExecutorService executor = Executors.newFixedThreadPool(10); Future<String> future = executor.submit(() -> { Thread.sleep(1000); return "Привет"; });.
Плюс в том, что вы можете продолжать работу, не дожидаясь: пока задача крутится в фоне, основной код идёт дальше. Но чтобы забрать результат, нужно вызвать future.get(). И вот здесь засада: get() блокирует текущий поток до тех пор, пока задача не завершится. Если задача задерживается — скажем, из-за сети, — ваш поток тоже висит в ожидании. Получается, асинхронность иллюзорна: да, запуск асинхронный, но использование результата синхронное и блокирующее. Это как заказать еду по доставке, но стоять у двери, не отходя, пока курьер не приедет. Выигрыш минимален, особенно в веб-приложениях, где запросы должны обрабатываться быстро.
Ещё Future неудобен в композиции: если нужно объединить результаты нескольких задач, приходится вручную ждать каждого get(), что приводит к спагетти-коду с try-catch для ошибок и таймаутами.
CompletableFuture: цепочки действий, но без избавления от ада
Java 8 принесла CompletableFuture — улучшенную версию Future, которая позволяет строить цепочки асинхронных операций без блокировок на get(). Теперь результат можно обрабатывать через "колбэки" — функции, которые вызываются автоматически по завершении.
Пример:
CompletableFuture.supplyAsync(() -> { return "Данные"; }).thenApply(data -> { return data.toUpperCase(); }).thenAccept(System.out::println);.
Здесь supplyAsync запускает задачу асинхронно, thenApply преобразует результат (например, переводит в верхний регистр), thenAccept выводит его.
Есть методы для комбинации: thenCompose для последовательных цепочек, thenCombine для параллельного объединения результатов, handle для обработки ошибок. Это шаг вперёд: код становится declarative (описательным), вы фокусируетесь на "что сделать", а не "как ждать". Нет нужды в ручном get() — всё течёт само.
Но радость недолговечна. Когда приложение усложняется — например, нужно асинхронно запросить данные из базы, потом из внешнего API, обработать ошибки и объединить, — цепочки лямбд растут в "callback-ад" (ад колбэков): вложенные функции, которые трудно читать, отлаживать и тестировать. Один уровень — ок, но пять-шесть — и код превращается в пирамиду, где сложно отследить поток выполнения.
Ещё хуже: под капотом блокировки никуда не делись. Если в цепочке есть блокирующий вызов — например, Thread.sleep() для симуляции задержки или JDBC-драйвер, который ждёт ответа от базы, блокируя поток, — весь CompletableFuture теряет преимущество. Поток из пула всё равно занят ожиданием, и под нагрузкой система снова захлёбывается. Плюс, управление ошибками в цепочках требует осторожности: одна ошибка может сломать всю последовательность, если не обработать timely.
В итоге, CompletableFuture дал выразительный синтаксис и удобство для простых сценариев, но не решил системные проблемы: ресурсы тратятся впустую на блокировки, сложность растёт, а масштабируемость под вопросом.
#Java #middle #Reactor
👍3
Callback-ад и блокировки: кульминация проблем
Callback-ад — это когда колбэки (функции обратного вызова) наслаиваются друг на друга, делая код нечитаемым. В CompletableFuture это проявляется в глубоких цепочках: thenApply внутри thenCompose, с handle для ошибок. Отладка — кошмар: где именно сломалось? Тестирование — тоже, потому что асинхронность добавляет неопределённость в порядок выполнения.
Блокировки — это когда код "зависает" в ожидании внешнего события, не давая потоку работать с другими задачами. В Java многие библиотеки (как старые IO или JDBC) блокирующие по природе: они используют системные вызовы, которые стопорят поток. Даже в асинхронных конструкциях, если внутри лямбды такая блокировка, весь пул потоков может исчерпаться. Представьте сервер с 100 потоками: 100 запросов с задержкой — и новые ждут в очереди, вызывая таймауты.
Это приводит к неэффективности: CPU простаивает, память тратится на "спящие" потоки, а под пиковой нагрузкой система не масштабируется горизонтально.
Почему нужен новый подход: реактивное программирование
Мы дошли до предела традиционных моделей. Потоки хороши для CPU-bound задач (расчёты), но тяжёлые для IO-bound (сеть, диски). Future дал обещания, но не избавил от блокировок. CompletableFuture улучшил код, но оставил callback-ад и зависимость от неблокирующих библиотек.
Здесь на сцену выходит реактивное программирование — подход, где мы думаем в терминах потоков данных и событий, а не отдельных задач. Вместо "запрос → блокировка в потоке → ответ" мы строим конвейеры: данные приходят асинхронно по мере готовности, обработка идёт реактивно, без выделения потока на каждое ожидание. Это как перейти от конвейера с паузами к непрерывному потоку. В следующих постах разберём Reactive Streams, Flux/Mono в Project Reactor и как это решает проблемы.
#Java #middle #Reactor
Callback-ад — это когда колбэки (функции обратного вызова) наслаиваются друг на друга, делая код нечитаемым. В CompletableFuture это проявляется в глубоких цепочках: thenApply внутри thenCompose, с handle для ошибок. Отладка — кошмар: где именно сломалось? Тестирование — тоже, потому что асинхронность добавляет неопределённость в порядок выполнения.
Блокировки — это когда код "зависает" в ожидании внешнего события, не давая потоку работать с другими задачами. В Java многие библиотеки (как старые IO или JDBC) блокирующие по природе: они используют системные вызовы, которые стопорят поток. Даже в асинхронных конструкциях, если внутри лямбды такая блокировка, весь пул потоков может исчерпаться. Представьте сервер с 100 потоками: 100 запросов с задержкой — и новые ждут в очереди, вызывая таймауты.
Это приводит к неэффективности: CPU простаивает, память тратится на "спящие" потоки, а под пиковой нагрузкой система не масштабируется горизонтально.
Почему нужен новый подход: реактивное программирование
Мы дошли до предела традиционных моделей. Потоки хороши для CPU-bound задач (расчёты), но тяжёлые для IO-bound (сеть, диски). Future дал обещания, но не избавил от блокировок. CompletableFuture улучшил код, но оставил callback-ад и зависимость от неблокирующих библиотек.
Здесь на сцену выходит реактивное программирование — подход, где мы думаем в терминах потоков данных и событий, а не отдельных задач. Вместо "запрос → блокировка в потоке → ответ" мы строим конвейеры: данные приходят асинхронно по мере готовности, обработка идёт реактивно, без выделения потока на каждое ожидание. Это как перейти от конвейера с паузами к непрерывному потоку. В следующих постах разберём Reactive Streams, Flux/Mono в Project Reactor и как это решает проблемы.
#Java #middle #Reactor
👍4
Как часто вы используете многопоточку в своем коде?
Anonymous Poll
3%
Практически каждый день.
14%
Периодически приходится.
23%
Очень редко. Раз на 10-20 проектов.
26%
Вообще использовал 1 раз.
34%
Никогда не приходилось использовать.
👍1
Что выведет код?
#Tasks
import java.util.concurrent.*;
public class Task160925 {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(() -> {
Thread.sleep(1000);
return "Done";
});
System.out.println(future.get(500, TimeUnit.MILLISECONDS));
executor.shutdown();
}
}
#Tasks
👍2
👍1
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10🔥1
Вопрос с собеседований
Что такое string templates в Java?🤓
Ответ:
String templates — интерполяция строк с STR."Hello \{name}".
Пример:
String name = "Alice";
String greeting = STR."Hello \{name}!"; // Hello Alice!
Более безопасны, чем конкатенация, предотвращают инъекции.
#собеседование
Что такое string templates в Java?
Ответ:
String templates
Пример:
String name = "Alice";
String greeting = STR."Hello \{name}!"; // Hello Alice!
Более безопасны, чем конкатенация, предотвращают инъекции.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Константи́н Эдуа́рдович Циолко́вский (5 [17] сентября 1857, Ижевское, Рязанская губерния — 19 сентября 1935, Калуга) — российский и советский учёный-самоучка, разрабатывавший теоретические вопросы космонавтики, мыслитель эзотерической ориентации, занимавшийся философскими проблемами освоения космоса.
Майкл Фредрик Сипсер (родился 17 сентября 1954 года) — американский учёный-теоретик в области информатики, внёсший ранний вклад в теорию сложности вычислений. Он является профессором прикладной математики и был деканом факультета естественных наук Массачусетского технологического института.
Крейг Джентри (родился в 1973 году) — американский криптограф; известен тем, что предложил первый практически используемый схемы полностью гомоморфного шифрования.
1955 — первый полёт самолёта (NB-36) с работающим ядерным реактором на борту.
1991 — Линус Торвальдс опубликовал исходный код ядра операционной системы Linux 0.01 весом 64Kb.
#Biography #Birth_Date #Events #17Сентября
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Основы ООП в Java
Глава 6. Ключевые модификаторы ООП
Перечисления (enum)
Перечисление (enum) — это специальный класс, который представляет фиксированный набор констант (например, дни недели, цвета или статусы). Enum введен в Java 5 и является полноценным классом, наследующим от java.lang.Enum, но с особым синтаксисом.
Зачем нужен enum:
Типобезопасность: Вместо int или String для констант (где возможны ошибки, как 8 для дня недели), enum гарантирует только допустимые значения.
Читаемость: Код становится самодокументируемым (Day.MONDAY clearer, чем 1).
ООП-функции: Enum может иметь поля, методы, конструкторы.
Переключатели: Идеально для switch, без ошибок.
Неизменяемость: Enum константы immutable и final.
Enum — это абстракция для групп констант, делая код robust и maintainable.
Синтаксис и базовое использование enum
Enum объявляется как класс, но с enum вместо class. Константы перечисляются через запятую.
Синтаксис:
Пример для дней недели:
Использование:
Нюанс: Enum константы — это объекты, но сравниваются через == (не equals, так как уникальные).
Вывод:
Enum с полями, конструкторами и методами
Enum — полноценный класс, так что может иметь private конструкторы, поля и методы. Константы вызывают конструктор.
Пример с полями:
Использование:
Нюанс: Конструктор private (нельзя new Planet()). Константы — единственные экземпляры.
Методы: Могут быть абстрактными (каждая константа реализует) или обычными.
#Java #для_новичков #beginner #OOP #Enum
Глава 6. Ключевые модификаторы ООП
Перечисления (enum)
Перечисление (enum) — это специальный класс, который представляет фиксированный набор констант (например, дни недели, цвета или статусы). Enum введен в Java 5 и является полноценным классом, наследующим от java.lang.Enum, но с особым синтаксисом.
Зачем нужен enum:
Типобезопасность: Вместо int или String для констант (где возможны ошибки, как 8 для дня недели), enum гарантирует только допустимые значения.
Читаемость: Код становится самодокументируемым (Day.MONDAY clearer, чем 1).
ООП-функции: Enum может иметь поля, методы, конструкторы.
Переключатели: Идеально для switch, без ошибок.
Неизменяемость: Enum константы immutable и final.
Enum — это абстракция для групп констант, делая код robust и maintainable.
Синтаксис и базовое использование enum
Enum объявляется как класс, но с enum вместо class. Константы перечисляются через запятую.
Синтаксис:
public enum EnumName {
CONSTANT1,
CONSTANT2,
// ...
}
Пример для дней недели:
public enum Day {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
Использование:
public class Main {
public static void main(String[] args) {
Day today = Day.WEDNESDAY; // Константа
System.out.println(today); // WEDNESDAY
if (today == Day.WEDNESDAY) { // Сравнение через ==
System.out.println("Середина недели!");
}
}
}
Нюанс: Enum константы — это объекты, но сравниваются через == (не equals, так как уникальные).
Вывод:
System.out.println(today) вызывает toString(), который возвращает имя константы.
Enum с полями, конструкторами и методами
Enum — полноценный класс, так что может иметь private конструкторы, поля и методы. Константы вызывают конструктор.
Пример с полями:
public enum Planet {
MERCURY(3.303e+23, 2.4397e6),
VENUS(4.869e+24, 6.0518e6),
EARTH(5.976e+24, 6.37814e6);
private final double mass; // Масса в кг
private final double radius; // Радиус в м
// Конструктор (private по умолчанию)
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
// Метод
public double surfaceGravity() {
final double G = 6.67300E-11; // Константа гравитации
return G * mass / (radius * radius);
}
}
Использование:
Planet earth = Planet.EARTH;
System.out.println("Гравитация на Земле: " + earth.surfaceGravity()); // ~9.8
Нюанс: Конструктор private (нельзя new Planet()). Константы — единственные экземпляры.
Методы: Могут быть абстрактными (каждая константа реализует) или обычными.
#Java #для_новичков #beginner #OOP #Enum
👍3
Методы Enum-класса и полезные функции
Enum наследует от Enum<E>, предоставляя методы:
name(): Возвращает имя константы как String.
ordinal(): Порядковый номер (начиная с 0).
valueOf(String name): Возвращает enum по имени.
values(): Массив всех констант.
Пример:
Нюанс: ordinal() не рекомендуется для логики — лучше поля, так как порядок может измениться.
Switch с enum
Enum идеален для switch — безопасно и читаемо.
Нюанс: Нет default, если все случаи покрыты — компилятор не требует.
Все нюансы enum
Наследование и реализация:
Enum не может extends класс (уже extends Enum), но может implements интерфейсы.
Нюанс: public enum MyEnum implements Interface { ... }
Конструкторы и поля:
Конструкторы private.
Константы должны быть первыми в enum, за ними — поля/методы.
Нюанс: Константы с параметрами: CONSTANT(params),
Иммутабельность: Enum константы final и immutable — идеально для singleton.
Сериализация: Enum сериализуется по имени, безопасно.
Ошибки:
Enum с public конструктором — ошибка.
valueOf("INVALID") — IllegalArgumentException.
Enum не может быть abstract, но может иметь abstract методы (константы реализуют).
Дизайн:
Используйте для фиксированных наборов (статусы, типы).
Добавляйте методы для логики.
Нюанс: Nested enum — static по умолчанию.
Как создать это в IntelliJ IDEA
Enum: New → Enum → Planet.
Константы: Напишите MERCURY(3.303e+23, 2.4397e6), — IDE подскажет конструктор.
Методы: Добавьте surfaceGravity() — IDE поможет с override для abstract.
Switch: Напишите switch (day) — IDE сгенерирует cases.
Полезные советы для новичков
Замените int/String константы: Enum safer.
Добавляйте toString(): Переопределите для custom вывода.
Используйте в коллекциях: Set для уникальных.
ordinal() осторожно: Лучше поля для значений.
Ресурсы: Oracle Tutorials on Enum Types.
#Java #для_новичков #beginner #OOP #Enum
Enum наследует от Enum<E>, предоставляя методы:
name(): Возвращает имя константы как String.
ordinal(): Порядковый номер (начиная с 0).
valueOf(String name): Возвращает enum по имени.
values(): Массив всех констант.
Пример:
Day[] days = Day.values();
for (Day d : days) {
System.out.println(d + " ordinal: " + d.ordinal());
}
Вывод: MONDAY ordinal: 0, etc.
Нюанс: ordinal() не рекомендуется для логики — лучше поля, так как порядок может измениться.
Switch с enum
Enum идеален для switch — безопасно и читаемо.
public void printDayType(Day day) {
switch (day) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
System.out.println("Рабочий день");
break;
case SATURDAY:
case SUNDAY:
System.out.println("Выходной");
break;
}
}
Нюанс: Нет default, если все случаи покрыты — компилятор не требует.
Все нюансы enum
Наследование и реализация:
Enum не может extends класс (уже extends Enum), но может implements интерфейсы.
Нюанс: public enum MyEnum implements Interface { ... }
Конструкторы и поля:
Конструкторы private.
Константы должны быть первыми в enum, за ними — поля/методы.
Нюанс: Константы с параметрами: CONSTANT(params),
Иммутабельность: Enum константы final и immutable — идеально для singleton.
Сериализация: Enum сериализуется по имени, безопасно.
Ошибки:
Enum с public конструктором — ошибка.
valueOf("INVALID") — IllegalArgumentException.
Enum не может быть abstract, но может иметь abstract методы (константы реализуют).
Дизайн:
Используйте для фиксированных наборов (статусы, типы).
Добавляйте методы для логики.
Нюанс: Nested enum — static по умолчанию.
Как создать это в IntelliJ IDEA
Enum: New → Enum → Planet.
Константы: Напишите MERCURY(3.303e+23, 2.4397e6), — IDE подскажет конструктор.
Методы: Добавьте surfaceGravity() — IDE поможет с override для abstract.
Switch: Напишите switch (day) — IDE сгенерирует cases.
Полезные советы для новичков
Замените int/String константы: Enum safer.
Добавляйте toString(): Переопределите для custom вывода.
Используйте в коллекциях: Set для уникальных.
ordinal() осторожно: Лучше поля для значений.
Ресурсы: Oracle Tutorials on Enum Types.
#Java #для_новичков #beginner #OOP #Enum
👍3
Во скольких телеграм - каналах по Java, вы состоите? А во скольких постоянно общаетесь?
Anonymous Poll
15%
Больше 10 точно. Нигде не общаюсь, только читаю.
4%
Больше 10 точно. Везде активно переписываюсь.
19%
От 5 до 10. Просто читаю, не люблю печатать.
0%
От 5 до 10. Во всех успел засветиться и являюсь живым пользователем чатов.
15%
Менее 5. Только читаю.
7%
Менее 5. Редко но пишу.
37%
Только этот! Самый лучший ❤️
4%
Вообще нигде и никогда и никому и ничего не пишу. 💪
👍1
Что выведет код?
#Tasks
enum Status {
ACTIVE, INACTIVE;
Status() {
System.out.print(this.name() + " ");
}
}
public class Task170925 {
public static void main(String[] args) {
System.out.print("Start ");
Status status = Status.ACTIVE;
System.out.print("End");
}
}
#Tasks
🔥2
Варианты ответа:
Anonymous Quiz
63%
Start ACTIVE End
19%
Start End
9%
Start ACTIVE INACTIVE End
9%
ACTIVE INACTIVE Start End
🔥3
Вопрос с собеседований
Что такое interface segregation principle (ISP)?🤓
Ответ:
ISP (из SOLID) — принцип, что клиенты не должны зависеть от ненужных методов интерфейса. Лучше несколько маленьких интерфейсов.
Пример: Вместо большого Worker разделить на Eater, Worker.
Улучшает гибкость.
#собеседование
Что такое interface segregation principle (ISP)?
Ответ:
ISP (из SOLID)
Пример: Вместо большого Worker разделить на Eater, Worker.
Улучшает гибкость.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Жан Берна́р Лео́н Фуко́ (фр. Jean Bernard Léon Foucault; 18 сентября 1819, Париж — 11 февраля 1868, там же) — французский физик, механик и астроном, член Парижской Академии наук (1865). Состоял членом Берлинской академии наук, был членом-корреспондентом Петербургской Академии наук (1860), иностранным членом Лондонского королевского общества (1864).
Владимир Александрович Сухомлин (род. 18 сентября 1945, Москва) — доктор технических наук, профессор, заведующий лабораторией открытых информационных технологий факультета ВМК МГУ.
Ю́рий Васи́льевич Гуля́ев (род. 18 сентября 1935) — советский и российский физик, научный руководитель Института радиотехники и электроники РАН (ИРЭ РАН), директор Института нанотехнологий микроэлектроники РАН (ИНМЭ РАН), академик и член Президиума РАН, профессор и заведующий кафедрой твердотельной электроники и радиофизики ФФКЭ МФТИ. Лауреат двух Государственных премий СССР. Почётный доктор Новгородского государственного университета им. Ярослава Мудрого (2013).
Томас Г. Лейн (род. 18 сентября 1955) — специалист по информатике, занимающийся разработкой программного обеспечения с открытым исходным кодом . В опросе 2000 года он был включён в десятку ведущих разработчиков программного обеспечения с открытым исходным кодом, внёс 0,782% от общего объёма кода.
Джеймс Уильям Кули (18 сентября 1926 г. — 29 июня 2016 г.) — американский математик. Кули получил степень бакалавра в 1949 году в Манхэттенском колледже в Бронксе, штат Нью-Йорк, степень магистра в 1951 году в Колумбийском университете в Нью-Йорке, штат Нью-Йорк, и степень доктора философии в 1961 году по прикладной математике в Колумбийском университете. С 1953 по 1956 год он работал программистом на компьютере Джона фон Неймана в Институте перспективных исследований в Принстоне, штат Нью-Джерси, где, в частности, запрограммировал преобразование Блэкмана — Тьюки.
1961 — в лаборатории люминесценции Физического института АН СССР начал работать первый советский лазер на искусственном кристалле рубина.
1989 — Релиз NeXTSTEP 1.0 (NeXT, ОС, разработанная командой Стива Джобса).
2024 — число статьей в Русской Википедии достигло 2 миллиона.
#Biography #Birth_Date #Events #18Сентября
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Реактивное программирование
Что такое потоки данных в реактивном мире?
В реактивном программировании данные — это не статичный объект, который вы запрашиваете и ждёте. Это динамичный поток: последовательность элементов (событий), которые могут приходить в любое время, в любом количестве. Поток может быть бесконечным (как лента новостей) или конечным (как результаты поиска). Главное — обработка идёт реактивно: программа "подписывается" на поток и реагирует на каждый элемент по мере его появления, без блокировок.
Это решает боли из предыдущего поста: вместо выделения потока на ожидание, мы используем неблокирующий механизм. Если данных нет — ничего не происходит, ресурсы свободны. Когда данные приходят — срабатывают реакции. Это как подписка на уведомления: телефон не висит в ожидании, а просто пиликает при новом сообщении.
В основе лежит спецификация Reactive Streams — стандарт, который определяет, как строить такие потоки.
Он включает четыре ключевых интерфейса:
Издатель (Publisher): источник данных. Он "публикует" элементы потока. Например, это может быть база данных, генерирующая записи по запросу.
Подписчик (Subscriber): получатель данных. Он "подписывается" на издателя и реагирует на элементы: onNext (получил элемент), onError (ошибка), onComplete (поток завершён).
Подписка (Subscription): связь между издателем и подписчиком. Через неё подписчик может запросить больше данных (request(n)) или отменить подписку (cancel). Это вводит "обратное давление" — механизм, чтобы подписчик не захлёбывался данными, если не успевает их обрабатывать.
Процессор (Processor): комбинация издателя и подписчика, для промежуточной обработки (как фильтр в конвейере).
Эти интерфейсы — основа. Они обеспечивают асинхронность без блокировок: всё работает на основе событий, а не ожиданий.
События как река: метафора и практика
Представьте реку событий: вода (данные) течёт непрерывно, иногда бурно (пиковая нагрузка), иногда спокойно. Ваша программа — не плотина, которая блокирует поток, а турбина, которая генерирует энергию по мере течения. Если река слишком быстрая — турбина сигнализирует "замедлить" (обратное давление), чтобы не перегрузиться.
В Project Reactor это реализовано через два типа потоков:
Mono: поток для нуля или одного элемента. Идеален для одиночных операций, как HTTP-запрос, возвращающий один ответ.
Пример:
Flux: поток для нуля, одного или многих элементов, возможно бесконечных. Для последовательностей, как стриминг данных.
Пример:
Flux "выдаёт" элементы по одному: 1, 2, 3... Это как река: элементы приходят последовательно, но асинхронно.
Почему это лучше традиционных подходов?
В CompletableFuture вы строите цепочки, но рискуете блокировками внутри.
В реактивном стиле всё неблокирующее: используйте операторы вроде map (преобразовать элемент), filter (отфильтровать), flatMap (развернуть в подпотоки).
Пример цепочки:
#Java #middle #Reactor #data_stream
Что такое потоки данных в реактивном мире?
В реактивном программировании данные — это не статичный объект, который вы запрашиваете и ждёте. Это динамичный поток: последовательность элементов (событий), которые могут приходить в любое время, в любом количестве. Поток может быть бесконечным (как лента новостей) или конечным (как результаты поиска). Главное — обработка идёт реактивно: программа "подписывается" на поток и реагирует на каждый элемент по мере его появления, без блокировок.
Это решает боли из предыдущего поста: вместо выделения потока на ожидание, мы используем неблокирующий механизм. Если данных нет — ничего не происходит, ресурсы свободны. Когда данные приходят — срабатывают реакции. Это как подписка на уведомления: телефон не висит в ожидании, а просто пиликает при новом сообщении.
В основе лежит спецификация Reactive Streams — стандарт, который определяет, как строить такие потоки.
Он включает четыре ключевых интерфейса:
Издатель (Publisher): источник данных. Он "публикует" элементы потока. Например, это может быть база данных, генерирующая записи по запросу.
Подписчик (Subscriber): получатель данных. Он "подписывается" на издателя и реагирует на элементы: onNext (получил элемент), onError (ошибка), onComplete (поток завершён).
Подписка (Subscription): связь между издателем и подписчиком. Через неё подписчик может запросить больше данных (request(n)) или отменить подписку (cancel). Это вводит "обратное давление" — механизм, чтобы подписчик не захлёбывался данными, если не успевает их обрабатывать.
Процессор (Processor): комбинация издателя и подписчика, для промежуточной обработки (как фильтр в конвейере).
Эти интерфейсы — основа. Они обеспечивают асинхронность без блокировок: всё работает на основе событий, а не ожиданий.
События как река: метафора и практика
Представьте реку событий: вода (данные) течёт непрерывно, иногда бурно (пиковая нагрузка), иногда спокойно. Ваша программа — не плотина, которая блокирует поток, а турбина, которая генерирует энергию по мере течения. Если река слишком быстрая — турбина сигнализирует "замедлить" (обратное давление), чтобы не перегрузиться.
В Project Reactor это реализовано через два типа потоков:
Mono: поток для нуля или одного элемента. Идеален для одиночных операций, как HTTP-запрос, возвращающий один ответ.
Пример:
Mono<string> mono = Mono.just("Привет из реки"); // Создаём простой Mono с одним элементом
mono.subscribe(
value -> System.out.println("Получено: " + value), // onNext: реакция на элемент
error -> System.err.println("Ошибка: " + error), // onError
() -> System.out.println("Завершено") // onComplete
);
Здесь subscribe — это подписка. Mono "течёт" асинхронно: если элемент готов — срабатывает onNext, потом onComplete. Нет блокировок: код continue после subscribe.
Flux: поток для нуля, одного или многих элементов, возможно бесконечных. Для последовательностей, как стриминг данных.
Пример:
Flux<integer> flux = Flux.range(1, 5); // Поток чисел от 1 до 5
flux.subscribe(
value -> System.out.println("Элемент: " + value),
error -> System.err.println("Ошибка: " + error),
() -> System.out.println("Поток завершён")
);
Flux "выдаёт" элементы по одному: 1, 2, 3... Это как река: элементы приходят последовательно, но асинхронно.
Почему это лучше традиционных подходов?
В CompletableFuture вы строите цепочки, но рискуете блокировками внутри.
В реактивном стиле всё неблокирующее: используйте операторы вроде map (преобразовать элемент), filter (отфильтровать), flatMap (развернуть в подпотоки).
Пример цепочки:
Flux.fromIterable(List.of("яблоко", "банан", "вишня"))
.filter(fruit -> fruit.startsWith("б")) // Фильтруем
.map(String::toUpperCase) // Преобразуем
.subscribe(System.out::println); // Подписываемся
Результат: "БАНАН". Всё течёт как конвейер, без callback-ада: код читаем, как последовательный, но работает асинхронно.
#Java #middle #Reactor #data_stream
👍3
Обратное давление: контроль
Одна из ключевых фишек — backpressure (обратное давление). В традиционных системах, если производитель данных быстрее потребителя, буфер переполняется, и система падает. В Reactive Streams подписчик через Subscription.request(n) говорит: "Дай мне n элементов". Издатель выдаёт ровно столько, сколько запрошено. Это как шлюзы на реке: предотвращают наводнение.
Пример в Flux:
Подписчик контролирует темп, избегая перегрузки.
Почему это новый подход, который нам нужен?
Реактивное программирование не просто добавляет инструменты — оно меняет мышление. Вместо "императивного" кода (делай то, жди это), мы пишем "декларативно": опиши, как реагировать на поток. Это масштабируется: на сервере с 4 ядрами можно обрабатывать тысячи подключений, потому что нет блокирующих потоков. Библиотеки вроде Reactor интегрируются с неблокирующими драйверами (например, reactive JDBC или WebFlux для веб), решая боли блокировок.
#Java #middle #Reactor #data_stream
Одна из ключевых фишек — backpressure (обратное давление). В традиционных системах, если производитель данных быстрее потребителя, буфер переполняется, и система падает. В Reactive Streams подписчик через Subscription.request(n) говорит: "Дай мне n элементов". Издатель выдаёт ровно столько, сколько запрошено. Это как шлюзы на реке: предотвращают наводнение.
Пример в Flux:
Flux<integer> fastFlux = Flux.range(1, 1000); // Быстрый источник
fastFlux.subscribe(new BaseSubscriber<integer>() {
@Override
protected void hookOnSubscribe(Subscription subscription) {
request(10); // Запрашиваем только 10 элементов сначала
}
@Override
protected void hookOnNext(Integer value) {
System.out.println(value);
if (value % 10 == 0) request(10); // Запрашиваем ещё по мере обработки
}
});
Подписчик контролирует темп, избегая перегрузки.
Почему это новый подход, который нам нужен?
Реактивное программирование не просто добавляет инструменты — оно меняет мышление. Вместо "императивного" кода (делай то, жди это), мы пишем "декларативно": опиши, как реагировать на поток. Это масштабируется: на сервере с 4 ядрами можно обрабатывать тысячи подключений, потому что нет блокирующих потоков. Библиотеки вроде Reactor интегрируются с неблокирующими драйверами (например, reactive JDBC или WebFlux для веб), решая боли блокировок.
#Java #middle #Reactor #data_stream
👍4
Что выведет код?
#Tasks
import java.util.HashSet;
import java.util.Set;
public class Task180925 {
public static void main(String[] args) {
Point p1 = new Point(1, 2);
Point p2 = new Point(1, 3);
Set<Point> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println(set.size());
}
public record Point(int x, int y) {
public Point {
if (x < 0) throw new IllegalArgumentException("x < 0");
if (y < 0) throw new IllegalArgumentException("y < 0");
}
public boolean equals(Object o) {
return o instanceof Point p && p.x == x;
}
public int hashCode() {
return x;
}
}
}
#Tasks
👍2
👍2