Когда у тебя миллион запросов и сервер начинает задыхаться — обычный
Spring MVC с потоками умирает. Тут выходит на сцену Spring WebFlux и движок Project Reactor. Асинхронщина, backpressure, и код, который реально дышит под нагрузкой.pom.xml:<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
DispatcherServlet — реактивный движок Netty.@RestController
public class HelloController {
@GetMapping("/hello")
public Mono<String> hello() {
return Mono.just("👋 Привет, реактивный мир!");
}
}
Mono = 0..1 значение (как Future). Flux = поток значений.📛 Пример со стримом чисел:
@GetMapping("/numbers")
public Flux<Integer> numbers() {
return Flux.range(1, 5).delayElements(Duration.ofSeconds(1));
}Flux.just("A", "B", "C")
.map(s -> s.toLowerCase())
.filter(s -> !s.equals("b"))
.subscribe(System.out::println);Flux.interval(Duration.ofMillis(10))
.onBackpressureDrop()
.subscribe(i -> System.out.println("⚡️ " + i));
@Repository
public interface UserRepo extends ReactiveCrudRepository<User, String> {}
@GetMapping("/users")
public Flux<User> getUsers() {
return userRepo.findAll();
}
Чем отличается WebFlux 🆚 Project Reactor?
👍 Project Reactor = библиотека реактивных стримов (Mono, Flux, операторы).👍 WebFlux = фреймворк на базе Reactor, чтобы писать контроллеры, API и сервисы.
📚 Документация:
👉 Reactor👈 👉 WebFlux👈
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤5👍5
Media is too big
VIEW IN TELEGRAM
В этом видео разбираются основы работы с массивами в Java. Автор показывает, как создавать и использовать одномерные и многомерные массивы, как обращаться к их элементам и перебирать данные. Это базовый инструмент, без которого невозможно работать с большими наборами информации.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4
Если ты думаешь, что GC — это просто «собрал мусор и пошёл дальше», то добро пожаловать в ад продакшена с кучей тредов и 200 ГБ heap’а.
Обычный GC может стопнуть мир на секунду — и твой сервис превращается в «слайдшоу». Для маркетплейса или финтеха — смерть.
Тут на сцену выходит ZGC — сборщик мусора, который обещает паузы меньше 1 мс, даже на огромных хипах.
Запускаешь Java-приложение с флагами:
java -XX:+UseZGC -Xmx16g -Xms16g -jar app.jar
-XX:+UseZGC активирует Z Garbage Collector.Обычный GC работает так: стопнул мир → пересобрал кучу → отпустил.
ZGC же:
🟢 Делает конкурентную маркировку и компактификацию (почти весь GC идёт параллельно с твоим кодом).🟢 Использует цветные указатели (colored pointers), чтобы отслеживать состояние объектов.🟢 Работает без длинных стопов — паузы \~0.5–2 мс даже при 100+ ГБ heap.
Базовые:
-XX:+UseZGC
-XX:ZUncommitDelay=300
-XX:SoftMaxHeapSize=12g
🟢 ZUncommitDelay — через сколько секунд освобождать неиспользуемую память (по умолчанию 300).🟢 SoftMaxHeapSize — мягкий лимит кучи (GC будет стараться держаться в нём, даже если Xmx больше).
Мониторинг:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintGC -Xlog:gc*:gc.log
[2.345s][info][gc,start ] GC(0) Pause Mark Start
[2.345s][info][gc ] GC(0) Concurrent Mark
[2.348s][info][gc ] GC(0) Pause Mark End 0.2ms
[2.349s][info][gc ] GC(0) Concurrent Relocate
Когда брать ZGC, а когда нет
🟢 У тебя огромные хипы (16–500 ГБ).🟢 Важны низкие latency (игры, трейдинг, API).🟢 Ты не хочешь тратить дни на ручной тюнинг старых GC.
🟢 Если у тебя микросервис на 512 МБ heap (Overkill).🟢 Если нужен максимальный throughput (G1 иногда быстрее на маленьких heap’ах).
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤4🔥4👏1
Media is too big
VIEW IN TELEGRAM
В этом видео автор подробно объясняет, как работает Collections Framework в Java.
Пошагово демонстрируются основные интерфейсы и реализации: List, Set, Map, а также важные классы вроде ArrayList, HashSet, HashMap. Показано, как правильно выбирать подходящую коллекцию, когда использовать список, а когда — отображение, как добавлять, удалять и перебирать элементы.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍3🔥3😁1
В обычном Java-коде всё блокируется: запрос ждёт ответ, поток простаивает.
В реактивщине (Spring WebFlux) — данные текут как река.
Ты подписался на поток → получил элементы по мере готовности.
📛 Пример: возвращаем поток данных
import reactor.core.publisher.Flux;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@GetMapping("/users")
public Flux<String> getUsers() {
return Flux.just("Alice", "Bob", "Charlie");
}
}
List, а Flux.Клиент получит поэлементно: Alice → Bob → Charlie.
Mono<T> → 0..1 элементFlux<T> → 0..N элементов@GetMapping("/user")
public Mono<String> getUser() {
return Mono.just("Alice");
}Flux.range(1, 5)
.map(i -> i * 2)
.filter(i -> i > 5)
.subscribe(System.out::println);
@GetMapping("/external")
public Mono<String> callApi(WebClient client) {
return client.get()
.uri("https://api.example.com/data")
.retrieve()
.bodyToMono(String.class);
}Mono<String> u1 = Mono.just("Alice");
Mono<String> u2 = Mono.just("Bob");
Flux<String> merged = Flux.merge(u1, u2);Publisher шлёт данные → Subscriber сам говорит, сколько готов принять.
Нет перегрузки, система держится даже при большом потоке событий.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13❤🔥4❤3
ZGC 🆚 Shenandoah — битва без стоп-пауз
Java в 2025 уже умеет два монстра low-latency GC: ZGC и Shenandoah.
Оба обещают микросекундные паузы даже на кучах в сотни гигабайт.
Но у каждого свой характер. Давай разберёмся.
📦 Запуск
ZGC (JDK 15+):
Shenandoah (JDK 12+, лучше JDK 17+):
➡️ Оба вкл/выкл одним флагом.
🧠 Архитектура под капотом
🔵 ZGC:
🔴 Shenandoah:
⚙️ Пример приложения для сравнения
📊 Запускаем и смотрим логи
🔵 ZGC (
➡️ Паузы \~0.1–0.3 мс. Основная работа уходит в «Concurrent Relocate», но это в фоне.
🔴 Shenandoah (
➡️ Паузы больше (1–2 мс), но всё ещё очень маленькие.
🧪 Реальные различия (TL;DR)
🎯 Выводы
👍 ZGC → если нужна ультра-низкая задержка (финтех, realtime). Работает предсказуемо.
👍 Shenandoah → если heap до пары терабайт и важнее гибкость (много опций тюнинга).
🗣️ Запомни: ZGC = хирург с лазером, точный и быстрый.Shenandoah = механик с инструментами, чуть дольше, но чинит даже сложные ситуации.В продакшене пробуй оба — профит зависит от нагрузки и архитектуры.
Java в 2025 уже умеет два монстра low-latency GC: ZGC и Shenandoah.
Оба обещают микросекундные паузы даже на кучах в сотни гигабайт.
Но у каждого свой характер. Давай разберёмся.
📦 Запуск
ZGC (JDK 15+):
java -XX:+UseZGC -Xmx16g -Xms16g -jar app.jar
Shenandoah (JDK 12+, лучше JDK 17+):
java -XX:+UseShenandoahGC -Xmx16g -Xms16g -jar app.jar
🟢 Использует colored pointers (цветные указатели в 64-битных адресах).🟢 Разносит паузы на микросекундные stop-the-world (STW).🟢 Очень простая логика: mark → relocate, всё конкурентно.
🟢 Делает concurrent compacting: сжимает кучу без паузы.🟢 Использует brooks pointers (доп. уровень индирекции → каждый объект указывает на forwarder).🟢 Чуть медленнее из-за этой индирекции, но тоже держит паузы \~<10мс.
import java.util.ArrayList;
import java.util.List;
public class StressTest {
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
while (true) {
for (int i = 0; i < 100; i++) {
list.add(new byte[1024 * 1024]); // 100MB
}
if (list.size() > 2000) list.clear(); // держим heap живым
}
}
}
-XX:+UseZGC -Xlog:gc*):[2.345s][info][gc] GC(7) Pause Mark Start 0.2ms
[2.345s][info][gc] GC(7) Pause Mark End 0.1ms
[2.346s][info][gc] GC(7) Concurrent Relocate 45.6ms
-XX:+UseShenandoahGC -Xlog:gc*):[2.456s][info][gc] Pause Init Mark 1.2ms
[2.457s][info][gc] Concurrent Mark 38.9ms
[2.496s][info][gc] Pause Final Evac 1.5ms
🧪 Реальные различия (TL;DR)
Характеристика | ZGC 🟢 | Shenandoah 🔴
-------------------- | ---------------------------- | ----------------------------
⏱️ Паузы (latency) | ~0.2–2 мс (супер-стабильно) | ~1–10 мс (чуть длиннее)
⚡️ Throughput | Чуть выше (нет индирекции) | Чуть ниже (Brooks ptr)
🗂 Heap size | До 16 ТБ (!) | Ограничен ~4–8 ТБ
🧩 Сложность | Простая модель | Более гибкий, но сложнее
🎯 Подходит для | Trading, real-time API | Крупные JVM-сервисы, где не критичны 2–3 мс
Please open Telegram to view this post
VIEW IN TELEGRAM
❤4👍4🔥4
Java 17 принесла sealed classes — способ держать наследование под контролем. Больше никаких «кто угодно расширил класс и сделал фигню». Теперь ты сам решаешь, кто может быть наследником.
public sealed class Shape
permits Circle, Rectangle, Triangle {}
public final class Circle extends Shape {}
public non-sealed class Rectangle extends Shape {}
public sealed class Triangle extends Shape
permits Equilateral, Isosceles {}
final — тупик, дальше наследовать нельзя.sealed — можно, но только по твоему списку.non-sealed — снимает ограничения, разрешает наследование всем.
static String type(Shape s) {
return switch (s) {
case Circle c -> "Окружность";
case Rectangle r -> "Прямоугольник";
case Triangle t -> "Треугольник";
};
}
default. Ты не забудешь обработать новый тип.
public sealed interface Request
permits Login, Register, Ping {}
public final class Login implements Request {}
public final class Register implements Request {}
public final class Ping implements Request {}
📚 Отличие от abstract
🟢 abstract оставляет свободу: наследников сколько угодно.🟢 sealed фиксирует список: никакой самодеятельности.
public sealed interface Shape permits Circle, Rectangle {}
public record Circle(double r) implements Shape {}
public record Rectangle(double w, double h) implements Shape {}
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11❤5🔥4👏1
В Java
record — это идеальный инструмент для работы с неизменяемыми объектами. Но часто нам нужно не просто хранить данные, а проверять их корректность при создании. Хорошая новость: в record это встроено прямо в конструктор.public record User(String name, int age) {}final, сеттеров нет, объект полностью immutable. Но можно передать хоть отрицательный возраст — компилятор не остановит.public record User(String name, int age) {
public User {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Имя не может быть пустым");
}
if (age < 0) {
throw new IllegalArgumentException("Возраст не может быть отрицательным");
}
}
}new User(...), и данные сразу проверяются.User u = new User("Иван", 25); // ✅ всё ок
User bad = new User("", -5); // ❌ IllegalArgumentExceptionМожно написать и полный конструктор:
public User(String name, int age) {
if (age < 18) throw new IllegalArgumentException("Только 18+");
this.name = name;
this.age = age;
}public record Order(List<String> items) {
public Order {
if (items == null || items.isEmpty()) {
throw new IllegalArgumentException("Заказ пуст");
}
items = List.copyOf(items); // защитная копия
}
}🚦 Использование с аннотациями Bean Validation
public record Customer(
@NotBlank String name,
@Email String email
) {}
🧩 Кейс: «валидный объект» как контракт
public record Email(String value) {
public Email {
if (!value.matches("^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$")) {
throw new IllegalArgumentException("Некорректный email");
}
}
}Please open Telegram to view this post
VIEW IN TELEGRAM
👍13❤10🔥5
Media is too big
VIEW IN TELEGRAM
В этом видео автор знакомит с фундаментальными принципами объектно-ориентированного программирования (ООП) на примере Java. Объясняется, для чего нужно ООП и каковы его ключевые концепции, без глубокого вникания в код.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
Java даёт мощный инструмент для работы с асинхронностью без сторонних библиотек —
CompletableFuture вместе с ExecutorService. С их помощью можно строить цепочки вычислений, обрабатывать ошибки и настраивать таймауты.ExecutorService executor = Executors.newFixedThreadPool(4);
CompletableFuture<String> future =
CompletableFuture.supplyAsync(() -> {
sleep(500);
return "Результат";
}, executor);
System.out.println(future.join()); // блокируемся и получаем результат
executor.shutdown();
supplyAsync запускает задачу в отдельном потоке из пула.CompletableFuture<Integer> chain =
CompletableFuture.supplyAsync(() -> 10)
.thenApply(x -> x * 2)
.thenApply(x -> x + 5);
System.out.println(chain.join()); // 25
🚦 Обработка ошибок
CompletableFuture<String> safe =
CompletableFuture.supplyAsync(() -> {
if (true) throw new RuntimeException("Ошибка");
return "OK";
}).exceptionally(ex -> "Fallback");
System.out.println(safe.join()); // Fallback
exceptionally позволяет задать запасной путь при сбое.CompletableFuture<String> future =
CompletableFuture.supplyAsync(() -> {
sleep(2000);
return "Долго";
});
String result = future.orTimeout(1, TimeUnit.SECONDS)
.exceptionally(ex -> "Таймаут")
.join();
System.out.println(result); // Таймаут
orTimeout прерывает выполнение, если задача не завершилась за указанное время.🔀 Запуск нескольких задач
CompletableFuture<Integer> a = CompletableFuture.supplyAsync(() -> 1);
CompletableFuture<Integer> b = CompletableFuture.supplyAsync(() -> 2);
CompletableFuture<Integer> sum =
a.thenCombine(b, Integer::sum);
System.out.println(sum.join()); // 3
thenCombine позволяет объединять результаты из разных потоков.CompletableFuture<Object> any =
CompletableFuture.anyOf(a, b);
CompletableFuture<Void> all =
CompletableFuture.allOf(a, b);
anyOf завершится, когда готов хотя бы один, allOf — когда готовы все.Важно передавать свой ExecutorService в supplyAsync и runAsync. Иначе будет использоваться общий ForkJoinPool, который не всегда предсказуем.
Please open Telegram to view this post
VIEW IN TELEGRAM
1❤11👍9🔥1
Обычно мы читаем и пишем файлы через стандартные IO-потоки. Но у этого подхода есть потолок скорости. Memory-mapped files позволяют работать с файлами напрямую через память, минуя часть «лишних движений».
try (FileChannel channel = FileChannel.open(Path.of("data.txt"), StandardOpenOption.READ)) {
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
}ByteBuffer.try (FileChannel channel = FileChannel.open(Path.of("out.txt"),
StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
buffer.put("Hello world!".getBytes());
}long size = 10L * 1024 * 1024 * 1024; // 10 GB
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
buffer.put(0, (byte) 'X');
System.out.println((char) buffer.get(0));
Memory-mapped файлы зависят от ОС и архитектуры. Если закончилась виртуальная память — программа рухнет.
🧵 Использование в многопоточке
// Несколько потоков читают один и тот же MappedByteBuffer
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍9❤3
🏗 Java Module System (JPMS) — модульность приложения
С Java 9 появилась модульная система (JPMS), которая помогает разделять проект на независимые блоки и жёстко контролировать зависимости. Это шаг дальше от обычных JAR-файлов и классического classpath.
📦 Создание модуля через
➡️ Файл
🔒 Инкапсуляция пакетов
➡️ Код внутри модуля по умолчанию недоступен извне. Это защита от случайного использования «внутренних» API.
🔗 Транзитивные зависимости
➡️ Если модуль
🧩 Пример: приложение из трёх модулей
➡️ Модули явно описывают связи. Код не сможет обратиться к тому, что не экспортировано.
⚠️ Ошибка при неправильной зависимости
➡️ Компилятор выдаст ошибку:
🚦 Сравнение с классическим
🔧 Совместимость и migration
🗣️ Запомни: JPMS — это не только про разбиение проекта, но и про контроль связей. Модули делают систему чище, безопаснее и помогают строить большие приложения без хаоса зависимостей.
С Java 9 появилась модульная система (JPMS), которая помогает разделять проект на независимые блоки и жёстко контролировать зависимости. Это шаг дальше от обычных JAR-файлов и классического classpath.
module-info.javamodule com.example.app {
requires com.example.service;
exports com.example.app.api;
}module-info.java лежит в корне src/com.example.app и описывает, какие модули нужны (requires) и какие пакеты доступны наружу (exports). Всё остальное скрыто.module com.example.service {
exports com.example.service.api;
// внутренние классы не экспортируются
}module com.example.web {
requires transitive com.example.service;
}A требует B как transitive, то любой, кто подключает A, автоматически получает доступ к B.🧩 Пример: приложение из трёх модулей
com.example.model → только данные
com.example.service → бизнес-логика
com.example.app → точка входа
module com.example.model {
// ничего не экспортирует
}
module com.example.app {
requires com.example.model;
}package ... is not visible. Нужно явно exports.🚦 Сравнение с классическим
classpath👍 classpath: всё видно всем, легко словить «spaghetti» зависимостей.👍 modules: строгие границы, меньше сюрпризов при рефакторинге.
🔴 Старые JAR можно подключать через automatic modules.🔴 Постепенно можно переносить проект на полноценные модули.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9👍5❤4
Media is too big
VIEW IN TELEGRAM
В этом видео автор подробно разбирает, как создавать и использовать функции (методы) в Java.
Пошагово объясняется, как объявлять методы, передавать параметры, возвращать значения и вызывать их из других частей программы.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3
Java 8 позволяет добавлять неабстрактные реализации методов в интерфейс, используя ключевое слово
default:
interface Example {
int process(int a);
default void show() {
System.out.println("default show()");
}
}
java.lang.Object.Вызывать
default метод интерфейса в реализующем этот интерфейс классе можно используя ключевое слово super вместе с именем интерфейса:
interface Paper {
default void show() {
System.out.println("default show()");
}
}
class Licence implements Paper {
public void show() {
Paper.super.show();
}
}
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍6
Java является строго типизированным языком программирования, а это означает, то что каждое выражение и каждая переменная имеет строго определенный тип уже на момент компиляции. Однако определен механизм приведения типов (casting) - способ преобразования значения переменной одного типа в значение другого типа.
В Java существуют несколько разновидностей приведения:
✔️ Тождественное (identity). Преобразование выражения любого типа к точно такому же типу всегда допустимо и происходит автоматически.
✔️ Расширение (повышение, upcasting) примитивного типа (widening primitive). Означает, что осуществляется переход от менее емкого типа к более ёмкому. Например, от типа
byte (длина 1 байт) к типу int (длина 4 байта). Такие преобразование безопасны в том смысле, что новый тип всегда гарантировано вмещает в себя все данные, которые хранились в старом типе и таким образом не происходит потери данных. Этот тип приведения всегда допустим и происходит автоматически.✔️ Сужение (понижение, downcasting) примитивного типа (narrowing primitive). Означает, что переход осуществляется от более емкого типа к менее емкому. При таком преобразовании есть риск потерять данные. Например, если число типа
int было больше 127, то при приведении его к byte значения битов старше восьмого будут потеряны. В Java такое преобразование должно совершаться явным образом, при этом все старшие биты, не умещающиеся в новом типе, просто отбрасываются - никакого округления или других действий для получения более корректного результата не производится.✔️ Расширение объектного типа (widening reference). Означает неявное восходящее приведение типов или переход от более конкретного типа к менее конкретному, т.е. переход от потомка к предку. Разрешено всегда и происходит автоматически.
✔️ Сужение объектного типа (narrowing reference). Означает нисходящее приведение, то есть приведение от предка к потомку (подтипу). Возможно только если исходная переменная является подтипом приводимого типа. При несоответствии типов в момент выполнения выбрасывается исключение
ClassCastException. Требует явного указания типа.✔️ Преобразование к строке (to String). Любой тип может быть приведен к строке, т.е. к экземпляру класса
String.✔️ Запрещенные преобразования (forbidden). Не все приведения между произвольными типами допустимы. Например, к запрещенным преобразованиям относятся приведения от любого ссылочного типа к примитивному и наоборот (кроме преобразования к строке). Кроме того, невозможно привести друг к другу классы, находящиеся на разных ветвях дерева наследования и т.п.
При приведении ссылочных типов с самим объектом ничего не происходит, - меняется лишь тип ссылки, через которую происходит обращение к объекту.
Для проверки возможности приведения нужно воспользоваться оператором
instanceof:
Parent parent = new Child();
if (parent instanceof Child) {
Child child = (Child) parent;
}
#java #casting #upcasting #downcasting
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤3🔥2
Java всегда славилась многопоточкой, но классические треды прожорливы: на каждый создаётся отдельный системный поток. Loom это меняет — теперь у нас есть виртуальные потоки (virtual threads).
🧵 Старый способ — тяжёлые треды
Thread thread = new Thread(() -> {
System.out.println("Hello from thread!");
});
thread.start();
Thread vThread = Thread.ofVirtual().start(() -> {
System.out.println("Hello from virtual thread!");
});
for (int i = 0; i < 1_000_000; i++) {
Thread.ofVirtual().start(() -> work());
}
OutOfMemoryError. На обычных тредах — краш.
Thread.ofVirtual().start(() -> {
Thread.sleep(1000); // блокируемся
System.out.println("Done!");
});
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> fetchFromApi());
executor.submit(() -> readFromDb());
}
Files.lines(Path.of("data.txt"))
.forEach(line -> System.out.println(line));
🟢 сетевые сервисы (много коннектов);🟢 микросервисы;🟢 парсеры/скрейперы;🟢 всё, где тысячи одновременных задач.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤5🔥1
Integer a = 128;
Integer b = 128;
System.out.println(a == b); // false
Integer x = 1;
Integer y = 1;
System.out.println(x == y); // true
Это не просто волшебство! Это связано с целочисленным кэшированием в Java.
Для значений в диапазоне от -128 до 127 объекты типа
Integer кэшируются, и ссылки на них одинаковы, а для значений за пределами этого диапазона создаются новые объекты, и ссылки на них будут разными. Так как используется оператор ==, то сравниваются ссылки на объекты.#java #magic
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18❤2
Media is too big
VIEW IN TELEGRAM
В этом видео автор объясняет, что такое конструкторы в Java и зачем они нужны.
Пошагово показывается, как создавать конструкторы, передавать параметры при инициализации объектов и использовать перегрузку для разных вариантов создания экземпляров класса.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍2🔥2
Spring Boot 3 принёс AOT (Ahead-Of-Time) компиляцию. Теперь можно собирать приложение в нативный бинарь через GraalVM и запускать его почти мгновенно. Это не просто оптимизация — это новый уровень для микросервисов.
Разберём, что это даёт в реальных проектах
./mvnw -Pnative native:compile
./target/demo
FROM ghcr.io/graalvm/native-image:latest as builder
WORKDIR /app
COPY . .
RUN ./mvnw -Pnative native:compile
FROM ubuntu:22.04
COPY --from=builder /app/target/demo /demo
CMD ["/demo"]
JVM-приложение: 150–200 MB RAM
AOT-приложение: 20–40 MB RAM
@SpringBootApplication
public class DemoApp {
public static void main(String[] args) {
SpringApplication.run(DemoApp.class, args);
}
}
reflect-config.json), чтобы GraalVM понял, какие классы реально нужны.@TypeHint(types = {MyEntity.class}, access = AccessBits.ALL)
public class MyHints implements NativeConfiguration {}Class.forName(), нужно явно подсказать GraalVM. Иначе приложение упадёт.@GetMapping("/hello")
public Mono<String> hello() {
return Mono.just("Привет из AOT!");
}🟢 JIT (обычная JVM) быстрее на долгих задачах, потому что оптимизирует байткод во время работы.🟢 AOT быстрее стартует и экономнее в памяти, но чуть медленнее на горячем коде.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: ./mvnw -Pnative native:compile
- uses: actions/upload-artifact@v3
with:
name: demo-binary
path: target/demo
👍 Долгая компиляция (в разы медленнее, чем обычный mvn package).👍 Динамические фреймворки (Hibernate proxies, некоторые reflection API) требуют ручных hint'ов.👍 Не всегда легко дебажить — стек-трейсы короче.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍5🔥3
🌊 JEP 458: Stream Gatherers в Java 22
Java 22 добавила Gatherers — расширение Stream API, которое позволяет собирать элементы не только привычными map/filter/reduce, а своими кастомными способами. Теперь поток можно «собирать» прямо в процессе, без костылей.
📦 Простая идея Gatherer
➡️ Элементы пакуются по 2 штуки.
Вывод:
🔄 Sliding Window — скользящее окно
➡️ Формируются окна по 3 элемента с шагом в 1.
Вывод:
🗂 Группировка по условию
➡️ Группа закрывается, когда встречается "x".
Вывод:
⚙️ Свой Gatherer
➡️ Числа суммируются, пока не превысят 10, потом сбрасываются.
📊 Gatherers.fold — промежуточная агрегация
➡️ Видим все промежуточные суммы.
Вывод:
⚖️ Gatherers 🆚 Collectors
➡️ Это позволяет писать stateful-преобразования, которые раньше выглядели громоздко.
🚀 Реальные применения
➡️ Всё это теперь можно писать нативно в Stream API.
🔗 Комбинации
➡️ Сначала разбиваем на пары, потом суммируем каждую.
Вывод:
🗣️ Запомни: Gatherers в Java 22 выводят стримы на новый уровень. Они позволяют обрабатывать данные «на лету» и строить stateful-пайплайны, которые раньше были невозможны без костылей.
Java 22 добавила Gatherers — расширение Stream API, которое позволяет собирать элементы не только привычными map/filter/reduce, а своими кастомными способами. Теперь поток можно «собирать» прямо в процессе, без костылей.
Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.windowFixed(2))
.forEach(System.out::println);
Вывод:
[1, 2]
[3, 4]
[5]
Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.windowSliding(3))
.forEach(System.out::println);
Вывод:
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
Stream.of("a", "b", "x", "y", "c", "d")
.gather(Gatherers.groupUntil(s -> s.equals("x")))
.forEach(System.out::println);
Вывод:
[a, b, x]
[y, c, d]
Gatherer<Integer, ?, Integer> sumPairs =
Gatherer.of(
() -> new int[1], // state
(state, e, down) -> {
state[0] += e;
if (state[0] >= 10) {
down.push(state[0]);
state[0] = 0;
}
return true;
},
(state, down) -> {}
);
Stream.of(3, 4, 5, 6, 7)
.gather(sumPairs)
.forEach(System.out::println);
12
13
Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.fold(0, Integer::sum))
.forEach(System.out::println);
Вывод:
1
3
6
10
15
Collectors работают в конце стрима (collect(...)).
Gatherers же действуют прямо в процессе, шаг за шагом.
1. Буферизация при работе с большими потоками.
2. Batch-загрузки в БД.
3. Скользящие средние и метрики.
4. Stateful-пайплайны без ручного кода.
Stream.of(1, 2, 3, 4, 5, 6)
.gather(Gatherers.windowFixed(2))
.map(list -> list.stream().mapToInt(i -> i).sum())
.forEach(System.out::println);
Вывод:
3
7
11
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12❤9🔥6✍2