Java | Фишки и трюки
Kanalga Telegram’da o‘tish
Java: примеры кода, интересные фишки и полезные трюки Купить рекламу: https://telega.in/c/java_tips_and_tricks ✍️По всем вопросам: @Pascal4eg
Ko'proq ko'rsatish6 820
Obunachilar
+724 soatlar
-97 kunlar
-6030 kunlar
Postlar arxiv
6 821
⌨️ Java CompletableFuture: Прокачанный Future
Помните интерфейс
Future из Java 5? Он был похож на чемодан без ручки. Вы могли отправить задачу в другой поток, но чтобы получить результат, приходилось делать future.get() и блокировать текущий поток, ожидая ответа. Смысл асинхронности терялся.
В Java 8 появился `CompletableFuture`. Это Future, который можно "программировать".
🔗 Цепочки вызовов (Pipelining)
Вместо того чтобы ждать результат, вы говорите Java: *"Когда результат будет готов, сделай с ним вот это, а потом вот это"*.
CompletableFuture.supplyAsync(() -> {
return fetchUser(123); // 1. Идем в базу (в другом потоке)
}).thenApply(user -> {
return user.getEmail(); // 2. Преобразуем (когда данные пришли)
}).thenAccept(email -> {
sendEmail(email); // 3. Отправляем письмо
});
System.out.println("Я не заблокирован!"); // Эта строка выполнится мгновенно
💪 Суперсила: Комбинация задач
Представьте, что вам нужно получить данные пользователя И его последние заказы из разных сервисов одновременно.
var userFuture = CompletableFuture.supplyAsync(() -> getUser());
var ordersFuture = CompletableFuture.supplyAsync(() -> getOrders());
// Ждем оба, комбинируем результаты и возвращаем отчет
userFuture.thenCombine(ordersFuture, (user, orders) -> {
return new Report(user, orders);
}).thenAccept(report -> show(report));
Оба запроса выполняются параллельно. Время выполнения равно времени самого медленного запроса, а не их сумме.
⚠️ Опасная ловушка
По умолчанию CompletableFuture использует общий пул потоков ForkJoinPool.commonPool().
Если вы запустите там "тяжелую" блокирующую задачу (например, долгий запрос к БД без асинхронного драйвера), вы можете забить все потоки и "повесить" всё приложение.
Совет: Всегда передавайте свой собственный Executor вторым аргументом:
CompletableFuture.supplyAsync(() -> heavyTask(), myCustomExecutor);6 821
🔴 Завтра тестовое собеседование с Java-разработчиком
21 января(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.
Как это будет:
📂 Сергей Чамкин, старший разработчик из Uzum, ex-WildBerries, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Cергей будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Сергею
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама.
О рекламодателе.
6 821
👻 Java `volatile`: Призрак в памяти
Спросите на собеседовании: «Зачем нужен
volatile?», и 80% ответят: «Чтобы потоки не конфликтовали».
Это не совсем так. volatile не спасает от конфликтов (race conditions). Он решает проблему видимости.
Давайте разберемся, почему ваш код может зависнуть навечно без этого слова.
🛑 Ситуация: Бесконечный цикл
Представьте простой код:
public class Worker {
private boolean running = true; // ❌ Забыли volatile
public void run() {
while (running) {
// Крутимся в цикле и ждем команды стоп
}
System.out.println("Stopped!");
}
public void stop() {
running = false;
}
}
Вы запускаете run() в Потоке А, а через секунду вызываете stop() из Потока Б.
Ожидание: Цикл остановится.
Реальность: Программа может работать вечно.
Почему? (Кэши процессора)
Современные процессоры супер-быстрые, а оперативная память (RAM) — медленная.
Чтобы не ждать память, каждое ядро процессора имеет свой локальный кэш (L1/L2 Cache).
1. Поток А (Ядро 1) скачал переменную running = true в свой кэш. Он проверяет её там.
2. Поток Б (Ядро 2) меняет значение на false в *своем* кэше и даже записывает в RAM.
3. Но Поток А этого не видит! Он продолжает читать true из своего изолированного кэша. Это называется проблема видимости.
✨ Магия `volatile`
Если мы напишем:
private volatile boolean running = true;
Мы говорим JVM и процессору: «Эту переменную нельзя кэшировать локально».
1. Любая запись в volatile переменную сразу пробрасывается (flush) в общую память (RAM).
2. Любое чтение volatile переменной идет напрямую из общей памяти.
Поток А сразу увидит изменение, и цикл остановится.
⚠️ Главная ловушка: Атомарность
Многие новички думают: «О, volatile синхронизирует данные! Значит можно делать счетчик».
volatile int count = 0;
count++; // ⛔️ ОПАСНО!
НЕТ! Операция count++ — это три действия: считать, прибавить, записать.
volatile гарантирует, что вы считаете свежее значение, но он не блокирует других от изменения этой переменной в тот же момент. Два потока могут одновременно считать 5, оба добавят 1 и запишут 6. Мы потеряем данные.
Итог:
👉 Используйте volatile только для флагов состояния (вкл/выкл) или когда запись делает только один поток.
👉 Для счетчиков используйте AtomicInteger или synchronized.6 821
Твой английский тормозит карьеру в IT?
Привет! Меня зовут Диана, я основатель Fluent Biz — школы делового английского. Знакомо, когда:
• Теряешь нить на митинге с foreign colleagues?
• Не можешь уверенно представить свой проект на английском?
• Заказчики просят «уточнить детали», а ты не знаешь, как объяснить?
Мы решаем эти проблемы — быстро, без воды и скучных учебников!
🔥 Дарим тебе БЕСПЛАТНЫЙ пробный урок, на котором:
✅ Определим твой уровень английского
✅ Разберём твои слабые места в речи и переписке
✅ Расскажу о том, каким образом и за какое время ты сможешь выйти на новый уровень языка
✅ Докажу, что говорить на английском с самого первого урока — реально
🎁 + Бонус для первых 5 записавшихся: консультация по подготовке к собеседованию на английском.
Что делать?
1️⃣ Подпишись на наш канал: @fluent_biz
2️⃣ Напиши @blhdiair cо словом «JAVA» — и мы согласуем удобное время для урока.
Промокод на бесплатный урок сгорит ровно через неделю, успей написать до 25.07📑
Не дай незнанию английского стать твоим потолком в карьере!
6 821
⌨️ Java ClassLoader: Серый кардинал JVM
Когда вы нажимаете "Run", вы, возможно, думаете, что Java загружает все ваши классы в память и начинает работу. Вовсе нет.
Java — ленивая. Она грузит классы только тогда, когда они реально понадобятся. И отвечает за этот процесс ClassLoader.
Это именно тот механизм, который позволяет серверам приложений (Tomcat, Jetty) перезапускать веб-приложения без остановки сервера, а Spring'у — творить свою магию.
🏗 Иерархия загрузчиков (Три кита)
В Java существует строгая иерархия. Когда нужно найти класс, работает принцип Delegation Model (Модель делегирования): «Сначала спроси у родителя».
1️⃣ Bootstrap ClassLoader (Первородный)
Это самый главный загрузчик. Он написан на C++ (поэтому, если вызвать
String.class.getClassLoader(), вы получите null - Java его просто не видит как объект).
Что грузит: Ядро Java (java.lang.*, java.util.* - всё из rt.jar или модулей java.base).
2️⃣ Platform ClassLoader (ранее Extension)
Наследник Bootstrap'а.
Что грузит: Расширения JDK (SQL, XML парсеры и прочие системные модули).
3️⃣ Application (System) ClassLoader
Тот самый, с которым мы работаем 99% времени.
Что грузит: Всё, что лежит в вашем CLASSPATH (`-cp`). Ваш код и ваши библиотеки.
🔄 Как работает поиск класса?
Допустим, в коде встретилось new User().
1. AppClassLoader не бросается искать файл User.class сам. Он звонит родителю: «Слушай, Platform, у тебя есть User?»
2. PlatformClassLoader тоже ленив и звонит своему родителю: «Bootstrap, у тебя есть User?»
3. Bootstrap смотрит в ядре Java. «Нет, это не стандартный класс».
4. Только тогда управление возвращается вниз. Platform смотрит у себя — нет.
5. И только в конце AppClassLoader ищет класс в ваших jar-файлах.
Если не найдет и он — привет, ClassNotFoundException.
👹 Ад зависимостей (Jar Hell) и Custom ClassLoaders
Зачем знать про ClassLoaders? Чтобы понимать сложные баги и архитектуру.
Представьте, что ваше приложение использует библиотеку Lib A версии 1.0, а другая ваша библиотека требует Lib A версии 2.0.
Стандартный загрузчик сломается: он не может загрузить два класса с одинаковым именем (`com.example.Lib`). Выиграет тот, кто первый попался в classpath, а второй упадет с ошибкой NoSuchMethodError.
Как это решают Tomcat или Maven?
Они пишут свои собственные ClassLoader'ы, нарушая стандартную иерархию.
В Tomcat каждое веб-приложение (`war`) имеет свой изолированный загрузчик. Поэтому два разных приложения могут использовать разные версии Spring на одном сервере, и они не подерутся.
Итог:
ClassLoader — это мост между файловой системой и памятью JVM. Понимание модели делегирования спасает часы при отладке странных ошибок, когда класс вроде бы есть, но Java его «не видит».6 821
🗑Внутренности 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 821
💻 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 821
Почему при зарплате 400к денег всё равно нет?
Замечал, как люди с доходом 80к умудряются копить на отпуск и покупать айфоны?
А у многих айтишников при 300-400к — в конце месяца ноль. Знакомо?
Есть 5 конкретных причин, почему так происходит при высоком доходе.
Снял видео — разбираю каждую. 5 минут, без воды.
Посмотри, возможно узнаешь себя или коллег!
Смотреть
#реклама 16+
sbsite.pro
О рекламодателе
6 821
💻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 821
💻 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 821
⌨️ 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 821
⌨️ 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 821
🔴 Завтра тестовое собеседование с Java-разработчиком
24 декабря(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.
Как это будет:
📂 Сергей Чамкин, старший разработчик из Uzum, ex-WildBerries, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Cергей будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Сергею
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама.
О рекламодателе.
6 821
⌨️ Внутренности
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 821
Привет. Вот тебе самые топовые каналы по IT!
⚙️ Free Znanija (IT) — Самая огромная коллекция платных курсов, которые можно скачать бесплатно;
👩💻 IT Books — Самая огромная библиотека книг;
💻 Hacking & InfoSec Base — Крутой блог белого хакера;
🛡 CyberGuard — Всё про ИБ;
🤔 ИБ Вакансии — Всё, чтобы найти работу в ИБ;
👩💻 linux administration — Всё про Линукс;
👩💻 Программистика — Python, python и ещё раз python;
👩💻 GameDev Base — Всё про GameDev;
😆 //code — Самые топовые мемы по IT:
Подпишись, чтобы не потерять!
6 821
⌨️ 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 821
🔥 БЕСПЛАТНЫЙ КУРС ПО СОЗДАНИЮ НЕЙРО-СОТРУДНИКОВ НА GPT И ДРУГИХ LLM 🔥
Ищете практический и углубленный курс, чтобы освоить создание нейро-сотрудников? Мы создали курс из 5 объемных занятий. Это именно то, что нужно, чтобы прокачать свои навыки абсолютно бесплатно!
📌 Темы занятий:
1. Введение в мир нейро-сотрудников
2. Как работают LLM и их аналоги
3. Создание базы знаний для нейро-сотрудника (RAG)
4. Тестирование и отладка нейро-сотрудников
5. Интеграция нейро-сотрудников в Production
Вот 5 тем курса - он максимально простой и доступный, общеобразовательный, без какого-либо сложного программирования 📚Прохождение этого курса, скорее всего, займет у вас от 1 до 3 часов
🤖 Присоединяйтесь к нашему бесплатному курсу и разберитесь в этой увлекательной теме с нами!
6 821
⌨️ 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 821
💻 Пишешь на Python или JavaScript и всё чаще ловишь себя на мысли: «А может, попробовать Go?»
Такой момент рано или поздно наступает у многих.
Про Go говорят как о языке для серьёзных backend-задач, высоких нагрузок и стабильных проектов, но непонятно главное — как на него перейти без боли.
В Kata Academy вышла подробная статья:
— чем Go принципиально отличается от Python и JavaScript;
— в каких моментах будет непривычно в начале;
— какие навыки реально пригодятся при переходе;
— кому Go подойдёт лучше всего.
Кстати, это ещё и одна из статей новогоднего розыгрыша 🎄
В декабрьских материалах Kata Academy спрятаны слова. Соберёшь фразу — можно выиграть сертификат Ozon.
Читать про переход на Go 👈🏻
#реклама
О рекламодателе
6 821
⌨️ 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 знает, что других фигур нет.
};
Это делает моделирование бизнес-логики (статусы заказов, типы ошибок) невероятно надежным. Если вы добавите новую фигуру, код перестанет компилироваться, пока вы не обработаете её в свитче.