Java | Фишки и трюки
7.21K subscribers
182 photos
29 videos
6 files
40 links
Java: примеры кода, интересные фишки и полезные трюки

Купить рекламу: https://telega.in/c/java_tips_and_tricks

✍️По всем вопросам: @Pascal4eg
Download Telegram
🧩 Sealed Classes — контроль наследования в Java

Java 17 принесла sealed classes — способ держать наследование под контролем. Больше никаких «кто угодно расширил класс и сделал фигню». Теперь ты сам решаешь, кто может быть наследником.

📦 Базовый sealed-класс

public sealed class Shape
permits Circle, Rectangle, Triangle {}

➡️ Только три класса могут расширять Shape.

🔑 Наследники обязаны выбрать модификатор

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 — снимает ограничения, разрешает наследование всем.

🧠 Идеально для switch

static String type(Shape s) {
return switch (s) {
case Circle c -> "Окружность";
case Rectangle r -> "Прямоугольник";
case Triangle t -> "Треугольник";
};
}

➡️ Компилятор знает все варианты и не требует default. Ты не забудешь обработать новый тип.

🛠 Контроль API

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 фиксирует список: никакой самодеятельности.

➡️ Это не про обязательные методы, а про ограничение иерархии.

🧱 Комбо с record

public sealed interface Shape permits Circle, Rectangle {}

public record Circle(double r) implements Shape {}
public record Rectangle(double w, double h) implements Shape {}

➡️ Получаешь компактные immutable-модели и полную предсказуемость.


🗣️ Запомни: Используй в API, моделях и ролях, где важна безопасность и прозрачность.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍115🔥4👏1
🧪 Records с Validation: immutable данные с проверкой на входе

В Java record — это идеальный инструмент для работы с неизменяемыми объектами. Но часто нам нужно не просто хранить данные, а проверять их корректность при создании. Хорошая новость: в record это встроено прямо в конструктор.

🔒 Простая неизменяемость
public record User(String name, int age) {}

➡️ Все поля final, сеттеров нет, объект полностью immutable. Но можно передать хоть отрицательный возраст — компилятор не остановит.


⚖️ Валидация в compact-конструкторе
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

➡️ Теперь ошибка «грязных данных» невозможна: объект даже не создастся.

🔄 Компактность vs. явный конструктор

Можно написать и полный конструктор:
public User(String name, int age) {
if (age < 18) throw new IllegalArgumentException("Только 18+");
this.name = name;
this.age = age;
}

➡️ Но компактная форма предпочтительнее — она короче и сразу показывает, что мы в record.

📦 Валидация коллекций внутри record
public record Order(List<String> items) {
public Order {
if (items == null || items.isEmpty()) {
throw new IllegalArgumentException("Заказ пуст");
}
items = List.copyOf(items); // защитная копия
}
}

➡️ Даже список внутри immutable — теперь никто не сможет изменить заказ после создания.

🚦 Использование с аннотациями Bean Validation
public record Customer(
@NotBlank String name,
@Email String email
) {}

➡️Если подключён Hibernate Validator или Jakarta Validation, поля проверятся автоматически.


🧩 Кейс: «валидный объект» как контракт
public record Email(String value) {
public Email {
if (!value.matches("^[\\w.-]+@[\\w.-]+\\.[a-z]{2,}$")) {
throw new IllegalArgumentException("Некорректный email");
}
}
}

➡️ Теперь в коде невозможно передать «плохую строку» туда, где нужен Email.


🗣️ Запомни: record — это не просто сокращённый POJO, а мощный способ гарантировать неизменяемость и корректность данных ещё при создании объекта.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1310🔥5
Media is too big
VIEW IN TELEGRAM
☕️Основные концепции ООП

В этом видео автор знакомит с фундаментальными принципами объектно-ориентированного программирования (ООП) на примере Java. Объясняется, для чего нужно ООП и каковы его ключевые концепции, без глубокого вникания в код.


🤩 Java Фишки и трюки || #Видео
Please open Telegram to view this post
VIEW IN TELEGRAM
3
🔄 CompletableFuture + ExecutorService — сложные асинхронные цепочки

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 позволяет объединять результаты из разных потоков.

🎭 AnyOf и AllOf
CompletableFuture<Object> any =
CompletableFuture.anyOf(a, b);

CompletableFuture<Void> all =
CompletableFuture.allOf(a, b);

➡️ anyOf завершится, когда готов хотя бы один, allOf — когда готовы все.

🧩 ExecutorService как фундамент
Важно передавать свой ExecutorService в supplyAsync и runAsync. Иначе будет использоваться общий ForkJoinPool, который не всегда предсказуем.



🗣️ Запомни: CompletableFuture + ExecutorService дают асинхронность, цепочки и контроль ошибок прямо из коробки. Умение строить такие цепи позволяет писать быстрые и надёжные сервисы без Reactive или сторонних решений.
Please open Telegram to view this post
VIEW IN TELEGRAM
111👍9🔥1
🛠 Memory-mapped Files — работа с файлами через ByteBuffer

Обычно мы читаем и пишем файлы через стандартные IO-потоки. Но у этого подхода есть потолок скорости. Memory-mapped files позволяют работать с файлами напрямую через память, минуя часть «лишних движений».

📂 Простое чтение через FileChannel
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

➡️ Общая память = быстрый обмен, но придётся синхронизировать доступ.


🗣️ Запомни:Memory-mapped files — это способ работать с файлами так, будто они часть оперативки. Он даёт скорость и гибкость, но требует аккуратного контроля памяти и синхронизации.
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍93
🏗 Java Module System (JPMS) — модульность приложения

С Java 9 появилась модульная система (JPMS), которая помогает разделять проект на независимые блоки и жёстко контролировать зависимости. Это шаг дальше от обычных JAR-файлов и классического classpath.

📦 Создание модуля через module-info.java
module 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;
// внутренние классы не экспортируются
}

➡️Код внутри модуля по умолчанию недоступен извне. Это защита от случайного использования «внутренних» 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: строгие границы, меньше сюрпризов при рефакторинге.


🔧 Совместимость и migration
🔴 Старые JAR можно подключать через automatic modules.
🔴 Постепенно можно переносить проект на полноценные модули.


🗣️ Запомни: JPMS — это не только про разбиение проекта, но и про контроль связей. Модули делают систему чище, безопаснее и помогают строить большие приложения без хаоса зависимостей.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9👍54
Media is too big
VIEW IN TELEGRAM
☕️Функции в языке Java

В этом видео автор подробно разбирает, как создавать и использовать функции (методы) в Java.
Пошагово объясняется, как объявлять методы, передавать параметры, возвращать значения и вызывать их из других частей программы.


🤩 Java Фишки и трюки || #Видео
Please open Telegram to view this post
VIEW IN TELEGRAM
3
⌨️ Что такое default методы интрефейса?

Java 8 позволяет добавлять неабстрактные реализации методов в интерфейс, используя ключевое слово default:

interface Example {
int process(int a);
default void show() {
System.out.println("default show()");
}
}



✔️Если класс реализует интерфейс, он может, но не обязан, реализовать методы по-умолчанию, уже реализованные в интерфейсе. Класс наследует реализацию по умолчанию.

✔️Если некий класс реализует несколько интерфейсов, которые имеют одинаковый метод по умолчанию, то класс должен реализовать метод с совпадающей сигнатурой самостоятельно. Ситуация аналогична, если один интерфейс имеет метод по умолчанию, а в другом этот же метод является абстрактным - никакой реализации по умолчанию классом не наследуется.

✔️Метод по умолчанию не может переопределить метод класса java.lang.Object.

✔️Помогают реализовывать интерфейсы без страха нарушить работу других классов.

✔️Позволяют избежать создания служебных классов, так как все необходимые методы могут быть представлены в самих интерфейсах.

✔️Дают свободу классам выбрать метод, который нужно переопределить.

✔️Одной из основных причин внедрения методов по умолчанию является возможность коллекций в Java 8 использовать лямбда-выражения.

Вызывать 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
👍103🔥2
🚀 Project Loom: виртуальные потоки вместо тяжёлых тредов

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!");
});

➡️ Здесь поток лёгкий. Java сама маппит тысячи виртуальных потоков на несколько системных.

🔄 Масштаб без боли

for (int i = 0; i < 1_000_000; i++) {
Thread.ofVirtual().start(() -> work());
}

➡️ Миллион потоков без OutOfMemoryError. На обычных тредах — краш.

⏸️ Блокировка без страха

Thread.ofVirtual().start(() -> {
Thread.sleep(1000); // блокируемся
System.out.println("Done!");
});

➡️ Виртуальные треды спокойно блокируются. JVM паркует их, освобождая ресурсы.

🌐 Асинхронщина без Future/CompletableFuture

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> fetchFromApi());
executor.submit(() -> readFromDb());
}

➡️ Каждая задача — в своём виртуальном треде. Асинхронность пишется синхронно.

🔧 Работа с I/O

Files.lines(Path.of("data.txt"))
.forEach(line -> System.out.println(line));

➡️ Даже блокирующее I/O больше не страшно. В Loom оно «кажется» блокирующим, но реально не тормозит.

📊 Где использовать

🟢 сетевые сервисы (много коннектов);
🟢 микросервисы;
🟢 парсеры/скрейперы;
🟢 всё, где тысячи одновременных задач.


🗣️ Запомни: Про «тредпулы и колбэки» скоро можно будет забыть.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍105🔥1
⌨️ Почему 1==1 это true, а 128==128 это false?


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
👍182
Media is too big
VIEW IN TELEGRAM
☕️Конструкторы класса

В этом видео автор объясняет, что такое конструкторы в Java и зачем они нужны.
Пошагово показывается, как создавать конструкторы, передавать параметры при инициализации объектов и использовать перегрузку для разных вариантов создания экземпляров класса.


🗣️Запомни: конструкторы упрощают инициализацию объектов и делают код более удобным и читаемым.

🤩 Java Фишки и трюки || #Видео
Please open Telegram to view this post
VIEW IN TELEGRAM
5👍2🔥2
🚀 Spring Boot 3 + AOT компиляция — реальная производительность

Spring Boot 3 принёс AOT (Ahead-Of-Time) компиляцию. Теперь можно собирать приложение в нативный бинарь через GraalVM и запускать его почти мгновенно. Это не просто оптимизация — это новый уровень для микросервисов.

Разберём, что это даёт в реальных проектах 👇

⚡️ Запуск в миллисекунды
./mvnw -Pnative native:compile
./target/demo

➡️ Обычный Spring Boot на JVM стартует 2–5 секунд. AOT-бинарь поднимается за 30–50 мс. Это критично для serverless и микросервисов, где холодный старт решает.

📦 Минимальные Docker-образы
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"]

➡️ В Docker-контейнере не нужна JVM. Итоговый образ весит 30–50 MB вместо 300–400 MB.

🧠 Экономия памяти
JVM-приложение:     150–200 MB RAM  
AOT-приложение: 20–40 MB RAM

➡️ GraalVM вырезает неиспользуемые классы, рефлексию и динамику. Сервер держит десятки сервисов вместо пары.

🔗 Поддержка Spring Boot Native
@SpringBootApplication
public class DemoApp {
public static void main(String[] args) {
SpringApplication.run(DemoApp.class, args);
}
}

➡️ Код не меняется. Но при сборке Spring генерирует hint'ы (reflect-config.json), чтобы GraalVM понял, какие классы реально нужны.


🧩 Работа с рефлексией и прокси
@TypeHint(types = {MyEntity.class}, access = AccessBits.ALL)
public class MyHints implements NativeConfiguration {}

➡️ В AOT режиме всё статично. Если библиотека лезет через Class.forName(), нужно явно подсказать GraalVM. Иначе приложение упадёт.

⚙️ Async, WebFlux, Security — всё работает
@GetMapping("/hello")
public Mono<String> hello() {
return Mono.just("Привет из AOT!");
}

➡️ Reactive-приложения и Spring Security совместимы. Но стоит проверить на совместимость сторонние библиотеки — они не всегда поддерживают AOT.


📊 JIT vs AOT: производительность под нагрузкой
🟢 JIT (обычная JVM) быстрее на долгих задачах, потому что оптимизирует байткод во время работы.
🟢 AOT быстрее стартует и экономнее в памяти, но чуть медленнее на горячем коде.

➡️ Для микросервисов и API важнее быстрый старт и лёгкость. Для больших вычислений — JVM.

🔁 CI/CD и DevOps
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

➡️ AOT встраивается в pipeline. Сборка дольше (5–10 минут), но итоговый бинарь быстрый и лёгкий.

🚨 Ограничения AOT
👍 Долгая компиляция (в разы медленнее, чем обычный mvn package).
👍 Динамические фреймворки (Hibernate proxies, некоторые reflection API) требуют ручных hint'ов.
👍 Не всегда легко дебажить — стек-трейсы короче.


➡️ Но в продакшене эти минусы окупаются.

🗣️ Запомни: AOT в Spring Boot 3 — это не замена JVM, а выбор под задачу. Если важен холодный старт, размер образа и память — используй AOT. Если нужен гибкий код с динамикой и быстрые билды — оставляй JVM. Лучшие проекты совмещают оба подхода.
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

Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.windowFixed(2))
.forEach(System.out::println);


➡️ Элементы пакуются по 2 штуки.

Вывод:

[1, 2]
[3, 4]
[5]


🔄 Sliding Window — скользящее окно

Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.windowSliding(3))
.forEach(System.out::println);

➡️ Формируются окна по 3 элемента с шагом в 1.

Вывод:

[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);

➡️ Группа закрывается, когда встречается "x".

Вывод:

[a, b, x]
[y, c, d]


⚙️ Свой Gatherer

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);

➡️ Числа суммируются, пока не превысят 10, потом сбрасываются.


12
13


📊 Gatherers.fold — промежуточная агрегация

Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.fold(0, Integer::sum))
.forEach(System.out::println);

➡️ Видим все промежуточные суммы.

Вывод:

1
3
6
10
15



⚖️ Gatherers 🆚 Collectors
Collectors работают в конце стрима (collect(...)).
Gatherers же действуют прямо в процессе, шаг за шагом.


➡️ Это позволяет писать stateful-преобразования, которые раньше выглядели громоздко.


🚀 Реальные применения
1. Буферизация при работе с большими потоками.
2. Batch-загрузки в БД.
3. Скользящие средние и метрики.
4. Stateful-пайплайны без ручного кода.

➡️ Всё это теперь можно писать нативно в Stream API.

🔗 Комбинации

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


🗣️ Запомни: Gatherers в Java 22 выводят стримы на новый уровень. Они позволяют обрабатывать данные «на лету» и строить stateful-пайплайны, которые раньше были невозможны без костылей.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍129🔥62
Media is too big
VIEW IN TELEGRAM
☕️ Создание классов и объектов

В этом видео автор показывает, как в Java создавать собственные классы и объекты.
Разбирается структура класса, определение полей и методов, а также процесс создания экземпляров через ключевое слово new. Это практическая основа объектно-ориентированного программирования, без которой невозможно двигаться дальше.


🗣️Запомни: умение создавать классы и объекты — фундамент для понимания всей работы с ООП в Java.

🤩 Java Фишки и трюки || #Видео
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5💊53🔥1
⚡️ Panama API + MemorySegment — работа с off-heap памятью без Unsafe

Когда нужен прямой доступ к памяти без Unsafe и JNI — помогает Panama API.
MemorySegment и MemorySession позволяют работать с off-heap памятью безопасно, быстро и управляемо.

📦 Выделение памяти вне heap
try (var session = MemorySession.openConfined()) {
MemorySegment segment = MemorySegment.allocateNative(100, session);
System.out.println("Размер: " + segment.byteSize());
}

➡️ Память выделяется за пределами JVM heap, но управляется сессией.

🔗 Запись и чтение примитивов
try (var session = MemorySession.openConfined()) {
MemorySegment seg = MemorySegment.allocateNative(8, session);
seg.set(ValueLayout.JAVA_INT, 0, 42);
int x = seg.get(ValueLayout.JAVA_INT, 0);
System.out.println(x);
}

➡️ Читаем и пишем напрямую, как в массив, только в off-heap памяти.

⚡️ Работа с массивами
try (var session = MemorySession.openConfined()) {
MemorySegment seg = MemorySegment.allocateNative(4 * 5, session);
for (int i = 0; i < 5; i++) {
seg.setAtIndex(ValueLayout.JAVA_INT, i, i * 10);
}
System.out.println(seg.getAtIndex(ValueLayout.JAVA_INT, 3));
}

➡️ setAtIndex и getAtIndex делают сегмент похожим на массив.

🌀 MemorySession = контроль жизни

MemorySegment seg;
try (var session = MemorySession.openConfined()) {
seg = MemorySegment.allocateNative(16, session);
}
System.out.println(seg.isAlive()); // false

➡️ После закрытия сессии память освобождается, сегмент становится "мертвым".

📑 Slice и view памяти
try (var session = MemorySession.openConfined()) {
MemorySegment seg = MemorySegment.allocateNative(16, session);
MemorySegment part = seg.asSlice(4, 8);
System.out.println("Slice: " + part.byteSize());
}

➡️ Можно создавать "представления" (slice) на часть памяти.

🚀 Обмен с native-кодом
try (var session = MemorySession.openConfined()) {
MemorySegment seg = MemorySegment.allocateNative(4, session);
seg.set(ValueLayout.JAVA_INT, 0, 123);
long addr = seg.address();
System.out.println("Адрес: " + addr);
}

➡️ Сегмент даёт доступ к "сырому" адресу для интеграции с нативными API.

🗣️ Запомни: Off-heap ускоряет работу с большими данными и нативным кодом без GC overhead.
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍54🤔1
🛡 Java Flight Recorder + JFR Events API — профилирование прямо в коде

Обычно JFR включают флагами JVM, но начиная с Java 14 появился Events API. Теперь можно описывать и логировать свои события прямо из кода, без тяжёлых логгеров.

📦 Определение кастомного события
import jdk.jfr.Event;
import jdk.jfr.Label;

class LoginEvent extends Event {
@Label("User")
String username;

@Label("Success")
boolean success;
}

➡️ Наследуем Event и описываем поля — это как лог-сообщение, но встроенное в JVM.

🚀 Публикация события
LoginEvent e = new LoginEvent();
e.username = "ivan";
e.success = true;
e.commit();

➡️ commit() записывает событие в JFR-файл, который потом анализируется в Mission Control.

🖥 Запуск записи профиля
java -XX:StartFlightRecording=filename=recording.jfr,duration=20s -jar app.jar

➡️ Даже без кода можно собрать JFR-трассу, но с Events API появляются свои бизнес-события.

📊 Использование категорий
import jdk.jfr.Category;

@Category("Auth")
class LoginEvent extends Event {
String username;
}

➡️События группируются в категории, и в Mission Control они будут выделены как отдельный блок.

⚡️ События с таймингом
class CalcEvent extends Event {}

CalcEvent e = new CalcEvent();
e.begin();
// тяжёлая операция
e.end();
e.commit();

➡️ begin()/end() автоматически меряют длительность и пишут её в метрику.

🔗 Комбинация JFR с логами
LoginEvent e = new LoginEvent();
e.username = "alex";
e.success = false;
e.commit();

System.out.println("Login attempt failed: alex");

➡️ Лог для человека, JFR-событие для анализа и графиков — два уровня контроля.

🧩 Интеграция с потоками
class TaskEvent extends Event {
String thread;
}

TaskEvent e = new TaskEvent();
e.thread = Thread.currentThread().getName();
e.commit();

➡️Можно писать, какие потоки нагружают систему и когда они стартуют.

📑 Анализ в Mission Control
Открываешь recording.jfr — и видишь всё: GC, потоки, задержки и свои кастомные события.

🗣️ Запомни: JFR Events API — это быстрый встроенный профайлинг. Свои события почти не грузят систему, пишутся дешевле логов и помогают находить проблемы прямо в продакшене.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍41
📦 Records 🆚 Sealed Classes — компактность и контроль

Record убирает шаблонный код и делает объект иммутабельным.
Sealed Class закрывает иерархию и контролирует наследование.
Разные задачи, но вместе решают боль старой Java.


🧩 Record вместо POJO

public record User(String name, int age) {}

➡️ Всё сгенерировано: геттеры, equals, hashCode, toString.
Лаконичность, минимум шаблонов.
Изменять поля нельзя.

📜 Класс вручную vs record

class User {
private final String name;
private final int age;

public User(String name, int age) {
this.name = name;
this.age = age;
}

public String name() { return name; }
public int age() { return age; }
}

➡️ Один record = десяток строк кода.

⚙️ Record с валидацией

public record Product(String name, int price) {
public Product {
if (price < 0) throw new IllegalArgumentException("price < 0");
}
}

➡️ Можно добавлять логику в compact-конструкторе.

🔒 Sealed Class: ограниченная иерархия

public sealed class Shape permits Circle, Rectangle {}

public final class Circle extends Shape {}
public final class Rectangle extends Shape {}

➡️ Никто не добавит новый Shape без изменения sealed-класса.

🎯 Record внутри sealed

public sealed interface Payment
permits CardPayment, CashPayment {}

public record CardPayment(String number) implements Payment {}
public record CashPayment(double amount) implements Payment {}

➡️ Удобное комбо: варианты — records, интерфейс — sealed.

🔎 Pattern Matching по sealed

Payment p = new CardPayment("1234");

switch (p) {
case CardPayment c -> System.out.println("💳 " + c.number());
case CashPayment c -> System.out.println("💵 " + c.amount());
}

➡️ Компилятор гарантирует, что обработаны все варианты.

📐 Sealed vs enum

sealed interface Command permits Reboot, Shutdown {}

record Reboot() implements Command {}
record Shutdown() implements Command {}

➡️ Как enum, но гибче: каждая ветка со своей структурой.

🧮 Комбинации records + sealed

sealed interface Event permits Login, Logout {}

record Login(String user) implements Event {}
record Logout(String user) implements Event {}


➡️ Модель событий простая, неизменяемая и закрытая.

🗣️Запомни:Record = компактный неизменяемый объект вместо шаблонного класса. Sealed Class = закрытая иерархия и контроль наследников.Вместе дают лаконичность и безопасность, снимая боль старой Java.
Please open Telegram to view this post
VIEW IN TELEGRAM
7👍5🔥3
⌨️ Что такое текстовые блоки (Text Blocks)?

Text Blocks (""") позволяют удобно работать с многострочными строками без необходимости экранировать кавычки.

Улучшают читаемость кода.
Поддерживают форматирование и перенос строк.
Упрощают работу с JSON, SQL и HTML.

✔️ Пример:


String json = """
{
"name": "Alice",
"age": 30
}
""";

System.out.println(json);


💡 Совет: Используйте Text Blocks для удобного написания больших строковых данных.

#java #textblocks #java15
Please open Telegram to view this post
VIEW IN TELEGRAM
👍103🔥3
⚡️ Scoped Values в Java — альтернатива ThreadLocal для виртуальных потоков

С появлением виртуальных потоков (Project Loom) старый ThreadLocal стал проблемой. Он держит данные на весь поток, что для миллионов lightweight-тредов дорого. Решение — ScopedValue.

📦 Объявление значения
import java.lang.ScopedValue;

public class Demo {
static final ScopedValue<String> USER = ScopedValue.newInstance();
}

➡️ Создаём "контейнер" для данных, которые будут жить только в заданной области.

🧩 Передача значения в scope
ScopedValue.where(USER, "Alice").run(() -> {
System.out.println("Hello, " + USER.get());
});

➡️ Значение доступно только внутри run(). Вышел из scope — данных нет.

⚡️ Виртуальные потоки + ScopedValue
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() ->
ScopedValue.where(USER, "Bob").run(() -> {
System.out.println("User in virtual thread: " + USER.get());
})
);
}

➡️ Локальные данные для каждого виртуального потока без утечек.

🔍 Сравнение с ThreadLocal
ThreadLocal<String> local = new ThreadLocal<>();
local.set("Alice");

➡️ Удобно, но значение живёт, пока жив поток. Виртуальные потоки → миллионы копий → утечки памяти.

🛡ScopedValue — иммутабельность
ScopedValue.where(USER, "Charlie").run(() -> {
// USER.set("Dave"); нельзя
System.out.println(USER.get());
});

➡️ Нельзя изменить внутри scope. Значение задаётся один раз, это делает код безопаснее.

🧠 Наследование scope
ScopedValue.where(USER, "Admin").run(() -> {
new Thread(() -> System.out.println(USER.get())).start();
});

➡️ Новый поток не видит значение. Данные строго локальны. Нет "магического" наследования, как у InheritableThreadLocal.


📊 Где применять
🟢 Контекст запроса (user id, trace id).
🟢 Логирование.
🟢 Передача настроек без проброса аргументов.


Минусы ScopedValue
🔴 Только read-only.
🔴 Живёт только в рамках scope.
🔴 Работает начиная с Java 20+.



🗣 Запомни: ThreadLocal опасен для виртуальных потоков — утечки и лишняя память. ScopedValue проще: задаёшь значение в scope, читаешь только там. Без наследования, без мутабельности, идеально для миллионов lightweight-тредов.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍63