Java | Фишки и трюки
前往频道在 Telegram
Java: примеры кода, интересные фишки и полезные трюки Купить рекламу: https://telega.in/c/java_tips_and_tricks ✍️По всем вопросам: @Pascal4eg Менеджер по рекламе: @shmyzna
显示更多6 954
订阅者
-724 小时
+117 天
+830 天
帖子存档
6 954
🗑Внутренности JVM — кто убирает мусор и как
JVM — это не просто виртуальная машина.
Это механика управления памятью, где каждый сборщик мусора решает свои задачи и влияет на производительность.
Разберёмся👇
❌ Антипаттерн: полагаться на default GC без понимания
// просто запускаем JVM и забываем
⚠️ Разные приложения — разные потребности: latency vs throughput.
➡️ Выбор GC = контроль над паузами и скоростью.
✅ Мини-примеры для понимания GC
👍 Создание большого массива объектов
public class GCDemo {
public static void main(String[] args) {
for (int i = 0; i < 1_000_000; i++) {
byte[] block = new byte[1024 * 1024]; // 1MB
}
System.out.println("Создано много объектов");
}
}
➡️Позволяет увидеть работу сборщика мусора, если включить лог GC:
-XX:+PrintGCDetails
👍Проверка поведения G1
public class G1Demo {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
int[] arr = new int[10_000_000]; // ~40MB
Thread.sleep(50); // медленная генерация объектов
}
System.out.println("Демонстрация работы G1");
}
}
➡️ JVM с G1 будет постепенно очищать регионы, минимизируя паузы.
👍 Принудительный вызов GC (для экспериментов, не для продакшена)
System.gc();
⚠️Позволяет проверить, как JVM реагирует, но не гарантирует моментальный сбор.
🗣 Запомни:
Сборщик мусора — это механика JVM, а не магия.
Выбор GC, размер heap и режим работы напрямую влияют на паузы и производительность.
Этот код поможет увидеть GC в действии и понять разницу между Serial, Parallel и G1.6 954
💻 Java Memory Model (JMM) - не просто volatile, а гарантии видимости между потоками
Многие воспринимают
volatile и synchronized как «какие-то ключевые слова», но на самом деле это контракт между потоками и процессором.
Контракт простой:
➡️Изменения одного потока должны быть видимы другим потокам, иначе код ломается на кэшах и reorder’ах.
Разберёмся👇
❌ Антипаттерн: обычная переменная между потоками
class Counter {
int count = 0;
void increment() { count++; }
}
Counter counter = new Counter();
Runnable r = () -> {
for(int i = 0; i < 1000; i++) counter.increment();
};
new Thread(r).start();
new Thread(r).start();
⚠️ Без volatile или synchronized значения могут теряться, потому что потоки кэшируют count локально.
➡️Даже ++ не атомарен - CPU может reorder инструкции.
✅ Как правильно
👍 volatile - видимость изменений
class Counter {
volatile int count = 0;
void set(int value) { count = value; } // запись видна другим потокам
int get() { return count; } // чтение гарантированно актуально
}
👍 synchronized - атомарность + видимость
class Counter {
private int count = 0;
synchronized void increment() { count++; }
synchronized int get() { return count; }
}
➡️ Любая синхронизированная операция создаёт happens-before между потоками.
👍happens-before - фундамент JMM
😱 volatile write → volatile read
Гарантирует, что запись видна всем потокам после прочтения
😱synchronized unlock → lock
Выход из synchronized у одного потока → вход в synchronized у другого
😱 Thread start / join
start() → run()
run() → join()
⚠️ Без этих правил видимость не гарантируется, даже если код компилируется и выглядит «правильно».
❌ Пример ломающегося кода без volatile (CPU кэши)
class Flag {
boolean running = true;
}
Flag flag = new Flag();
new Thread(() -> {
while(flag.running) {
// ждем события
}
System.out.println("Stopped");
}).start();
// другой поток через 1 секунду
flag.running = false;
⚠️ Поток в while может никогда не увидеть изменение из-за кэширования процессора.
➡️ Решение — volatile boolean running.
🗣️ Запомни:
JMM — это не про синтаксис, это про гарантии между потоками и CPU.
👍volatile → видимость
👍synchronized → атомарность + видимость
👍happens-before → контракт, гарантирующий порядок
Используй эти инструменты не ради ключевых слов, а чтобы код был предсказуемым и безопасным в многопоточности.6 954
Почему при зарплате 400к денег всё равно нет?
Замечал, как люди с доходом 80к умудряются копить на отпуск и покупать айфоны?
А у многих айтишников при 300-400к — в конце месяца ноль. Знакомо?
Есть 5 конкретных причин, почему так происходит при высоком доходе.
Снял видео — разбираю каждую. 5 минут, без воды.
Посмотри, возможно узнаешь себя или коллег!
Смотреть
#реклама 16+
sbsite.pro
О рекламодателе
6 954
💻ThreadLocalRandom — не просто Random, а быстрый для многопоточки
Используешь
new Random() или Math.random() в многопоточном коде?
Это не только медленно, но и создаёт конфликты потоков, снижая производительность.
Разберёмся👇
❌ Антипаттерн: общий Random для всех потоков
// Общий инстанс Random — синхронизация под капотом!
static Random globalRandom = new Random();
int randomNum = globalRandom.nextInt(100); // Потоки дерутся за монитор
⚠️ Под капотом Random синхронизирован
➡️ В многопотоке — contestion, падение скорости.
✅ Правильный способ (Java 7+)
int randomNum = ThreadLocalRandom.current().nextInt(100);
😀 Что происходит:
👍Каждый поток получает свой генератор
👍Нет синхронизации → нет contention
👍Скорость в разы выше в многопоточной среде
👍API такой же, есть удобные методы: nextInt(origin, bound), nextLong() и др.
👾 Для ForkJoinPool и параллельных стримов
int[] numbers = ThreadLocalRandom.current()
.ints(1000, 0, 100)
.parallel()
.toArray();
➡️ Быстро, параллельно, без блокировок.
⚠️Ловушки и советы
👍Не создавай новый инстанс каждый раз — используй current()
👍Не передавай между потоками — потеряется смысл
👍Для криптографии только SecureRandom
🗣️ Запомни:
ThreadLocalRandom — это дефолтный выбор для многопоточной генерации.
Меньше блокировок, больше скорость.
Просто работает.6 954
💻 CompletableFuture в Java - не просто get(), а другой уровень асинхронности
Многие воспринимают CompletableFuture как «асинхронный Future» и используют только get().
На самом деле это мощный инструмент для построения цепочек и обработки ошибок.
Контракт простой:
➡️ Операции выполняются асинхронно — и ты обязан обрабатывать результат, ошибки и таймауты.
Разберёмся👇
❌ Антипаттерн:
get() ради get()
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
String result = future.get(); // блокирует поток
⚠️ Это тот же синхронный вызов, только с обёрткой.
➡️ Если ты пишешь get() без нужды - CompletableFuture теряет смысл.
✅ Как правильно
👍 thenApply() - трансформация результата
future.thenApply(String::toUpperCase)
.thenAccept(System.out::println);
👍 thenCompose() - для вложенных CompletableFuture
future.thenCompose(this::callAnotherService);
👍 allOf() / anyOf() - комбинаторы
CompletableFuture.allOf(f1, f2, f3)
.thenRun(() -> System.out.println("Все завершены"));
👍 handle() / exceptionally() - обработка ошибок
future.handle((res, ex) -> res != null ? res : "fallback");
future.exceptionally(ex -> "ошибка: " + ex.getMessage());
👍 complete() / obtrudeValue() - ручное завершение
future.complete("готово");
future.obtrudeValue("принудительно");
👍 Кастомный Executor - контроль потоков
ExecutorService executor = Executors.newFixedThreadPool(5);
CompletableFuture.supplyAsync(() -> "task", executor);
⚠️ Особенности и осторожности
❌ get() - блокирует поток, может выбросить ExecutionException
⚠️ join() - не требует checked исключений, но может бросить CompletionException
➡️ Используй then*() цепочки для неблокирующей работы.
➡️ Таймауты обрабатывай через orTimeout() или completeOnTimeout().
🗣️ Запомни:
CompletableFuture - это не просто Future.
Это способ сказать коду:
«результат будет, ошибки будут, таймауты будут - разберись с этим прямо сейчас и не блокируй поток».6 954
⌨️ Helpful NullPointerException: Java наконец-то «заговорила»
Раньше
NullPointerException (NPE) был похож на игру в «Сапёра».
Вы получали ошибку: NullPointerException at com.app.Service.process(Service.java:45).
Смотрим в код на строку 45:
person.getAddress().getCountry().getIsoCode().toLowerCase();
👀 Кто здесь null?
person?
Address?
Country?
IsoCode?
Приходилось разбивать строку на части или запускать дебаггер, чтобы понять, где именно произошел взрыв.
💡 Как это работает сейчас (Java 14+):
Java научилась анализировать байт-код в момент падения и формировать информативное сообщение.
Теперь в логах вы увидите:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Country.getIsoCode()" because the return value of "Address.getCountry()" is null🗣 Java прямым текстом говорит: «Я не могу вызвать
getIsoCode(), потому что метод getCountry() вернул null».
Эта фича включена по умолчанию в современных версиях Java. Мелочь, а как приятно!6 954
⌨️ Compact Strings: Как String похудел в 2 раза
Строки занимают огромное количество места в куче (Heap) любого приложения. До Java 9 устройство строки выглядело примерно так:
public final class String {
private final char[] value; // Массив символов
}
В чем была проблема?
Тип char в Java занимает 2 байта (UTF-16). Это нужно, чтобы хранить иероглифы, эмодзи и сложные символы.
Но статистика показала, что 90% строк в западных приложениях — это обычная латиница, цифры и спецсимволы (ASCII / ISO-8859-1).
Для хранения буквы 'A' (которая весит 1 байт) мы тратили 2 байта: 00 41. Один байт просто заполнялся нулями. Это чудовищная трата памяти.
🛠 Что сделали в Java 9 (Compact Strings):
Разработчики JDK переписали класс String. Теперь он выглядит так:
public final class String {
private final byte[] value; // ❗️ Теперь это массив байтов
private final byte coder; // Флаг кодировки
}
Как это работает?
Когда вы создаете строку, Java проверяет её содержимое:
1️⃣ Режим LATIN1: Если в строке только символы, помещающиеся в 1 байт (английский, цифры, стандартные знаки), флаг coder ставится в 0, и строка занимает 1 байт на символ.
2️⃣ Режим UTF16: Если встречается хоть одна кириллица, иероглиф или эмодзи, флаг coder меняется, и Java использует 2 байта на символ (как раньше).
Результат:
Если ваше приложение гоняет JSON-ы, HTTP-заголовки или технические логи (а они обычно на английском), потребление памяти под строки сокращается ровно в 2 раза автоматически. Без переписывания кода.6 954
🔴 Завтра тестовое собеседование с Java-разработчиком
24 декабря(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.
Как это будет:
📂 Сергей Чамкин, старший разработчик из Uzum, ex-WildBerries, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Cергей будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Сергею
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама.
О рекламодателе.
6 954
⌨️ Внутренности
HashMap: Когда список превращается в дерево
Все знают, что HashMap работает на основе хеширования. Мы кладем ключ, вычисляется хеш, и элемент попадает в определенную ячейку (bucket) массива.
Но что происходит, если у многих ключей совпадает хеш (коллизия)?
🐢 До Java 8 (Эпоха LinkedList):
Все элементы с одинаковым хешем выстраивались в простой связный список (LinkedList) внутри одной ячейки.
Если у вас плохой алгоритм хеширования, поиск элемента в такой мапе превращался из мгновенного O(1) в медленный перебор списка O(n). Это могло «повесить» сервер.
⚡️С Java 8 (Эпоха Red-Black Tree):
Разработчики JDK внедрили механизм Treeification (одеревенение).
Как только в одной ячейке (bucket) скапливается слишком много элементов, Java автоматически превращает этот простой список в Красно-Черное дерево (Red-Black Tree).
Магия чисел:
Порог 8 (TREEIFY_THRESHOLD): Если в цепочке становится 8 или больше элементов - список превращается в дерево.
Порог 6 (UNTREEIFY_THRESHOLD): Если при удалении элементов их становится меньше 6 - дерево разжалуется обратно в список (чтобы экономить память, так как дерево занимает больше места).
🌳 Как HashMap сравнивает ключи в дереве (Tree Bin)
Когда корзина превращается в Красно-Черное дерево, Java нужна сортировка, чтобы работала навигация (бинарный поиск). Но ключи могут быть любыми объектами.
Вот иерархия проверок, которую использует HashMap, чтобы понять, «больше» ключ или «меньше» текущего узла:
1️⃣ Сравнение хешей (hash):
Даже если ключи попали в одну корзину (индекс массива), их полные 32-битные хеши могут отличаться.
Пример: У нас корзина №5. Туда попал ключ с хешем 5 и ключ с хешем 21 (при размере массива 16: 5 % 16 = 5, 21 % 16 = 5).
HashMap видит: 5 < 21. Ага, значит, идем влево или вправо. Это самая частая проверка.
2️⃣ Интерфейс Comparable:
Если полные хеши абсолютно одинаковы (это редкая коллизия), Java проверяет: «А реализует ли ключ интерфейс Comparable?».
Если да (например, ключи это String или Integer), то используется их метод compareTo(). Это позволяет идеально рассортировать элементы внутри дерева.
3️⃣ Последний шанс (tieBreakOrder):
Если хеши одинаковы и ключи НЕ реализуют Comparable (или сравнение не удалось), Java использует System.identityHashCode() (адрес в памяти) или имя класса, чтобы хоть как-то их упорядочить.
Это «аварийный» метод, просто чтобы дерево оставалось деревом и не ломалась структура.
🚀 Почему это быстрее?
В обычном списке (LinkedList) вы вызываете equals() для каждого элемента (100 элементов = 100 проверок).
В дереве (Red-Black Tree), используя правила выше (хеш > comparable > identity), мы на каждом шаге отсекаем половину вариантов.
Мы смотрим на корень: наш хеш меньше? Идем влево. (Правая ветка со всеми ее элементами сразу отбрасывается). Смотрим следующий узел: больше? Идем вправо.
В итоге, даже если в корзине 1000 элементов, в списке мы бы сделали 1000 вызовов equals. А в дереве мы спустимся по веткам примерно за 10 шагов (log2(1000) ≈ 10).
Итог:
equals() всё равно вызывается, но только когда мы уже нашли кандидата (или идем по пути с абсолютно идентичными хешами). Мы не "опрашиваем" всех соседей подряд.6 954
Привет. Вот тебе самые топовые каналы по IT!
⚙️ Free Znanija (IT) — Самая огромная коллекция платных курсов, которые можно скачать бесплатно;
👩💻 IT Books — Самая огромная библиотека книг;
💻 Hacking & InfoSec Base — Крутой блог белого хакера;
🛡 CyberGuard — Всё про ИБ;
🤔 ИБ Вакансии — Всё, чтобы найти работу в ИБ;
👩💻 linux administration — Всё про Линукс;
👩💻 Программистика — Python, python и ещё раз python;
👩💻 GameDev Base — Всё про GameDev;
😆 //code — Самые топовые мемы по IT:
Подпишись, чтобы не потерять!
6 954
⌨️ Java Unnamed Variables: Сила символа _
Все мы сталкивались с ситуацией, когда синтаксис требует объявить переменную, но она нам совершенно не нужна.
😒 Как мы выкручивались раньше:
Приходилось придумывать имя переменной, чтобы компилятор был доволен, а потом IDE ругалась: "Variable 'e' is never used".
try {
int number = Integer.parseInt(input);
} catch (NumberFormatException e) { // <-- Зачем нам 'e'?
// Мы и так знаем, что это не число, детали ошибки нам не важны
System.out.println("Это не число!");
}
Или в циклах:
for (var s : list) { // <-- Нам нужно просто посчитать количество, 's' не нужна
count++;
}
✅ Как стало с Java 22 (Unnamed Variables):
Теперь можно использовать символ подчеркивания _. Это сигнал компилятору: "Здесь должна быть переменная, но я не собираюсь её использовать".
try {
int number = Integer.parseInt(input);
} catch (NumberFormatException _) { // Красота!
System.out.println("Это не число!");
}
Или в паттерн-матчинге (для instanceof и switch), если нам важен только тип, а не само значение:
if (obj instanceof String _) {
System.out.println("Да, это строка (но читать её я не буду)");
}
🔥 Почему это круто?
1️⃣ Чистота намерений: Читая код, другой разработчик сразу понимает: эта переменная игнорируется намеренно, а не по ошибке.
2️⃣ Спокойствие IDE: Анализаторы кода больше не спамят предупреждениями "Unused variable".
3️⃣ Меньше когнитивной нагрузки: Не нужно придумывать имена вроде ignored, unused или dummy.6 954
🔥 БЕСПЛАТНЫЙ КУРС ПО СОЗДАНИЮ НЕЙРО-СОТРУДНИКОВ НА GPT И ДРУГИХ LLM 🔥
Ищете практический и углубленный курс, чтобы освоить создание нейро-сотрудников? Мы создали курс из 5 объемных занятий. Это именно то, что нужно, чтобы прокачать свои навыки абсолютно бесплатно!
📌 Темы занятий:
1. Введение в мир нейро-сотрудников
2. Как работают LLM и их аналоги
3. Создание базы знаний для нейро-сотрудника (RAG)
4. Тестирование и отладка нейро-сотрудников
5. Интеграция нейро-сотрудников в Production
Вот 5 тем курса - он максимально простой и доступный, общеобразовательный, без какого-либо сложного программирования 📚Прохождение этого курса, скорее всего, займет у вас от 1 до 3 часов
🤖 Присоединяйтесь к нашему бесплатному курсу и разберитесь в этой увлекательной теме с нами!
6 954
⌨️ Sequenced Collections: Конец мучений с list.get(size - 1)
Признайтесь, сколько раз за свою карьеру вы писали этот уродливый код, чтобы достать последний элемент списка?
😫 Старая школа (до Java 21):
var list = List.of("A", "B", "C");
// Чтобы взять последний элемент:
String last = list.get(list.size() - 1);
// А если это Set? (LinkedHashSet)
// Приходилось использовать итератор или перегонять в список... 😱
String lastInSet = set.iterator()... // Ой, всё, лень писать.
Это было неудобно, нечитаемо и чревато ошибками (привет, -1).
🥳 Java 21 (Sequenced Collections):
В Java наконец-то добавили общий интерфейс для всех коллекций, у которых есть порядок элементов — SequencedCollection.
Теперь у List, Deque, SortedSet и LinkedHashSet появились единые методы:
list.getFirst(); // Взять первый
list.getLast(); // Взять последний (Наконец-то!)
list.addFirst("Z"); // Добавить в начало
list.addLast("X"); // Добавить в конец
list.reversed(); // Получить представление коллекции в обратном порядке
🔥 Почему это круто?
1️⃣ Единый стандарт. Раньше у Deque были методы getFirst, у List — get(0), у SortedSet — first(). Теперь везде одинаково.
2️⃣ Работает с Set. Теперь можно легко взять первый или последний элемент из LinkedHashSet или TreeSet, не прибегая к итераторам.
3️⃣ Безопасность типов. Метод reversed() возвращает «живое» представление. Изменения в нем отразятся на оригинале (для мутабельных коллекций).
Вроде мелочь, а код становится намного чище.6 954
💻 Пишешь на Python или JavaScript и всё чаще ловишь себя на мысли: «А может, попробовать Go?»
Такой момент рано или поздно наступает у многих.
Про Go говорят как о языке для серьёзных backend-задач, высоких нагрузок и стабильных проектов, но непонятно главное — как на него перейти без боли.
В Kata Academy вышла подробная статья:
— чем Go принципиально отличается от Python и JavaScript;
— в каких моментах будет непривычно в начале;
— какие навыки реально пригодятся при переходе;
— кому Go подойдёт лучше всего.
Кстати, это ещё и одна из статей новогоднего розыгрыша 🎄
В декабрьских материалах Kata Academy спрятаны слова. Соберёшь фразу — можно выиграть сертификат Ozon.
Читать про переход на Go 👈🏻
#реклама
О рекламодателе
6 954
⌨️ Sealed Classes: Фейсконтроль для наследования
В ООП всегда была дилемма.
Если вы делаете класс
public, от него может наследоваться кто угодно.
Если делаете final, от него не может наследоваться никто.
А что, если я хочу, чтобы от моего класса Payment могли наследоваться только CashPayment и CardPayment, и больше никто?
До Java 17 приходилось придумывать костыли с пакетами. Теперь у нас есть Sealed Classes (Запечатанные классы).
✔️ Как это работает:
Вы используете ключевое слово sealed и через permits перечисляете, кому "можно".
public sealed interface Shape permits Circle, Square, Rectangle {
// Общие методы
}
Теперь Java (и компилятор) гарантирует: никаких других фигур, кроме этих трех, в программе существовать не может.
➡️ Правила игры:
Наследники (Circle, Square...) должны выбрать свою судьбу и указать один из модификаторов:
1️⃣ final — от меня наследовать нельзя (конец цепочки).
2️⃣ sealed — я тоже строгий, вот мой список наследников.
3️⃣ non-sealed — ладно, от меня можно наследовать всем (открываем шлюз).
🔥 Зачем это нужно? (Главная фишка)
Это идеально работает в связке с новым Switch.
Поскольку компилятор точно знает все возможные варианты наследников, он не потребует от вас ветку default!
String result = switch (shape) {
case Circle c -> "Это круг радиусом " + c.radius();
case Square s -> "Это квадрат";
case Rectangle r -> "Это прямоугольник";
// default не нужен! Java знает, что других фигур нет.
};
Это делает моделирование бизнес-логики (статусы заказов, типы ошибок) невероятно надежным. Если вы добавите новую фигуру, код перестанет компилироваться, пока вы не обработаете её в свитче.6 954
🔍 Завтра тестовое собеседование с Java-разработчиком
17 декабря(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.
Как это будет:
📂 Сергей Чамкин, старший разработчик из Uzum, ex-WildBerries, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Cергей будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Сергею
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама.
О рекламодателе.
6 954
⌨️ Java Collection Factories: List.of и Map.of
Если вы пишете на Java дольше 5 лет, вы помните эту боль. Вам просто нужно создать
Map с двумя значениями. Но Java требовала от вас целый ритуал.
🐢 Как это было раньше (Java 8 и старее):
// Для списка:
List<String> list = Arrays.asList("a", "b", "c");
// Вроде ок, но этот список можно менять (set), но нельзя менять размер (add/remove).
// Для Map — вообще ужас:
Map<String, Integer> map = new HashMap<>();
map.put("one", 1);
map.put("two", 2);
map = Collections.unmodifiableMap(map); // Если хотим защитить от изменений
🚀 Как это делается с Java 9:
List<String> list = List.of("a", "b", "c");
Map<String, Integer> map = Map.of("one", 1, "two", 2);
Set<String> set = Set.of("a", "b", "c");
Преимущества:
1️⃣ Неизменяемость (Immutability): Эти коллекции нельзя менять. Попытка сделать .add() или .put() сразу выбросит UnsupportedOperationException. Это делает код безопаснее (особенно в многопоточности).
2️⃣ Лаконичность: Map создается в одну строку (до 10 пар ключ-значение можно писать через запятую, дальше — через Map.ofEntries).
3️⃣ Никаких null: Если вы попытаетесь положить null в List.of или Map.of, вы сразу получите ошибку. Java приучает нас не использовать null в коллекциях.
💡 Лайфхак для Java 16+:
Если вы работаете со стримами, забудьте про .collect(Collectors.toList()).
Теперь можно писать просто:
List<String> result = stream.filter(s -> s.length() > 3).toList();
Обратите внимание: toList() возвращает неизменяемый список, в отличие от коллектора.6 954
Напоминаем про наш канал с тестами по Java ⌨️
Самое время проверить свои знания и понять, что стоит подтянуть.
➡️ Java | Tests
6 954
⌨️ HashMap и TreeMap: Когда и как использовать
В Java коллекции Map предоставляют возможность хранить пары "ключ-значение". Два популярных варианта —
HashMap и TreeMap. Давайте разберем их ключевые особенности и когда их лучше использовать.
📚 Kраткие определения:
- HashMap:
- Неупорядоченная коллекция.
- Основан на хэш-таблице.
- TreeMap:
- Упорядоченная коллекция.
- Основан на красно-черном дереве.
- Поддерживает сортировку по ключам.
⚖️ Когда использовать:
- HashMap:
- Если важна производительность и порядок хранения не имеет значения.
- Когда нужно быстро получать значения по ключу
- TreeMap:
- Если необходимо хранить элементы в отсортированном порядке.
- Для использования функционала "примитивного" поиска (например, firstKey() или lastKey()).
📌 Подведение итогов:
Выбор между HashMap и TreeMap зависит от ваших требований к производительности и порядку элементов. Помните, что HashMap лучше подходит для большинства случаев, когда необходим быстрый доступ, а TreeMap — для упорядоченного хранения данных.
#java #HashMap #TreeMap6 954
☕️ Spring AI: LLM прямо внутри твоего Java-сервиса
Никаких отдельных пайплайнов, очередей и костылей.
Spring AI даёт готовый слой для работы с LLM: модели, промпты, memory, embeddings — всё как в Python, только нативно для Java.
⚙️ 1. Подключаем Spring AI
build.gradle
dependencies {
implementation 'org.springframework.ai:spring-ai-openai-spring-boot-starter'
}
➡️ Starter тащит автоконфигурацию, модельный клиент и готовые бинды.
🔑 2. Настройка доступа к модели
application.yml
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
model: gpt-4.1
➡️ Осталось только дернуть bean — Spring сам соберёт клиент.
💬 3. Генерация ответа из Java-кода
@RestController
@RequiredArgsConstructor
public class AiController {
private final ChatClient chatClient;
@GetMapping("/explain")
public String explain(@RequestParam String code) {
return chatClient
.prompt("Объясни этот код кратко:\n" + code)
.call()
.content();
}
}
➡️ ChatClient → prompt → call().
Минимум связующей логики.
🧩 4. Структурированный вывод (JSON → Java)
record Result(String summary, int score) {}
var response = chatClient
.prompt("""
Проанализируй текст.
Верни: summary, score (0-10).
""")
.responseAs(Result.class);
➡️ LLM сразу мапится в твой record-класс.
Никаких JSON-ручек.
📚 5. Prompts как ресурсы
@AiClient
public interface ReviewAi {
@SystemPrompt("prompts/system.txt")
@UserPrompt("prompts/review.txt")
String analyze(String text);
}
➡️ Промпты лежат в папке, модель вызывается как обычный Java-метод.
🔍 6. Embeddings для поиска
var embedding = embeddingClient.embed("Spring AI makes Java smart");
vectorStore.add("doc-1", embedding);
➡️ Vector store — часть Spring AI: Chroma, PGVector, Redis.
Подходит для RAG в микросервисах.
🧠 7. RAG: ответ с учётом своих данных
var answer = ragClient
.query("Как работает наш биллинг?")
.call()
.content();
➡️ ragClient сам:
1. берёт эмбеддинг вопроса, 2. ищет релевантные документы, 3. подаёт всё в LLM.🪢 8. Streaming-ответы (chunk за chunk’ом)
chatClient
.prompt("Сгенерируй длинное объяснение")
.stream()
.forEach(chunk -> System.out.print(chunk.content()));
➡️ Нужен, если делаешь real-time UI или аналитический сервис.
🔌 9. Используем несколько моделей одновременно
spring:
ai:
clients:
local:
base-url: http://localhost:1234/v1
chat:
model: llama3
openai:
api-key: ${KEY}
chat:
model: gpt-4.1
➡️ Можно запрашивать локальную LLaMA и облачную OpenAI в одном приложении.
🧵 10. Memory (контекст диалога)
chatClient
.withConversation("session-42")
.prompt("Продолжи диалог")
.call();
➡️ Хранит состояние переписки без твоих ручных костылей.
🗣️ Запомни:Spring AI — это мост между классическим Java-стеком и LLM-миром: минимум клея, максимум интеграций, всё по-джавовски строго и предсказуемо.
现已上线!2025 年 Telegram 研究 — 年度关键洞察 
