en
Feedback
Java | Фишки и трюки

Java | Фишки и трюки

Open in Telegram

Java: примеры кода, интересные фишки и полезные трюки Купить рекламу: https://telega.in/c/java_tips_and_tricks ✍️По всем вопросам: @Pascal4eg

Show more
6 826
Subscribers
+824 hours
+27 days
-5030 days
Posts Archive
☕️ Построение ПК приложений. Java Swing JFrame
В этом видео автор переходит от консольных проектов к созданию графических интерфейсов. Вы научитесь разрабатывать полноценное ПК-приложение с использованием библиотеки Java Swing, добавляя в него кнопки, надписи, текстовые поля и другие графические компоненты на основе JFrame.
👉 Ссылка на первоисточник 🗣️Запомни: умение создавать GUI-приложения — это ключевой шаг от учебных задач к разработке реальных программ, которые видят и используют люди. 🤩 Java Фишки и трюки || #Видео

String.format() 🆚 конкатенация vs StringBuilder — кто быстрее на самом деле? Каждый день ты склеиваешь строки. Но если это происходит в горячем цикле или в высоконагруженном микроссервисе — разница в подходах может стоить миллисекунд, а то и секунд процессорного времени. Давай расставим всё по местам с цифрами в руках. Вот бенчмарк-ликбез, после которого ты больше не будешь гадать 👇 🗂 Шаг 1: Подопытные (4 основных способа)
// 1. Конкатенация (+)
String result1 = "Привет, " + name + "! Тебе " + age + " лет.";

// 2. StringBuilder
String result2 = new StringBuilder()
    .append("Привет, ")
    .append(name)
    .append("! Тебе ")
    .append(age)
    .append(" лет.")
    .toString();

// 3. String.format()
String result3 = String.format("Привет, %s! Тебе %d лет.", name, age);

// 4. String.concat() (редко, но бывает)
String result4 = "Привет, ".concat(name).concat("! Тебе ").concat(String.valueOf(age)).concat(" лет.");
➡️ Все они дают один результат. Но под капотом — абсолютно разная механика и производительность. 🗂 Шаг 2: Бенчмарк (условные замеры на 100_000 итераций) Представим результаты в наносекундах на операцию (меньше = лучше):
Конкатенация (+) ........... ~50 нс
StringBuilder .............. ~15 нс
String.format() ............ ~1200 нс
String.concat() ............ ~70 нс
➡️ Главный вывод: String.format() проигрывает в скорости в десятки раз. Он делает парсинг шаблона, проверку типов — это дорого. StringBuilder — чемпион по скорости. 🗂 Шаг 3: Почему «+» иногда не хуже StringBuilder? Важный нюанс! Компилятор сам оптимизирует простую конкатенацию в StringBuilder.
// Ты пишешь:
String s = a + b + c;

// Компилятор делает под капотом:
String s = new StringBuilder().append(a).append(b).append(c).toString();
➡️ Но! Это работает только для литералов и финальных выражений в одной строке кода. В циклах — оптимизации нет! 🗂 Шаг 4: Цикл — место, где «+» убивает производительность
// ❌ ПЛОХО (создаётся новый StringBuilder на КАЖДОЙ итерации)
String result = "";
for (String part : parts) {
    result += part; // Скрытый new StringBuilder() каждый раз!
}

// ✅ ХОРОШО (один StringBuilder на весь цикл)
StringBuilder sb = new StringBuilder();
for (String part : parts) {
    sb.append(part);
}
String result = sb.toString();
➡️ В цикле на 1000 элементов разница будет в сотни раз. Это та самая ошибка, которая съедает CPU. 🗂 Шаг 5: А когда тогда использовать String.format()? Когда читаемость и поддержка важнее скорости: 1. Сложные шаблоны с числами, датами, округлением:
    // Читаемо vs Нечитаемо
    String.format("Сумма: %.2f руб., дата: %tD", sum, date); // ✅
    // vs ручное склеивание с DecimalFormat и SimpleDateFormat — кошмар
    
2. Логирование, где скорость не критична (но даже там лучше шаблонизатор). 3. Конфигурационные сообщения, выносимые в properties-файлы. ➡️ String.format() — это Rolls-Royce: удобно, красиво, но для поездки в магазин за хлебом — избыточно. 🗂 Шаг 6: StringJoiner — твой друг для списков
// Когда нужно собрать строку через разделитель
StringJoiner joiner = new StringJoiner(", ", "[", "]");
joiner.add("Java");
joiner.add("Kotlin");
joiner.add("Scala");
String result = joiner.toString(); // [Java, Kotlin, Scala]
➡️ Под капотом использует StringBuilder, но даёт элегантный API для склейки с разделителями. Используй вместо ручных проверок «не первый ли элемент?». 🗣 Запомни: Выбирай инструмент не по привычке, а по делу.

🕰️ Java Time API: Почему java.util.Date должен умереть В старых версиях Java работа со временем была настоящим квестом. Класс java.util.Date — это, пожалуй, самый неудачный дизайн в истории JDK. 🤦‍♂️ Зал славы проблем Date и Calendar: 1️⃣ Они изменяемые (Mutable): Вы передаете дату в метод, а он может тихо поменять ей год. Это ад для многопоточности. 2️⃣ Нумерация месяцев: Январь — это 0. Декабрь — 11. Сколько багов было написано из-за этого! 3️⃣ Годы: new Date(2023, ...) создаст дату в 3923 году (потому что отсчет идет с 1900). 4️⃣ Нейминг: java.sql.Date наследуется от java.util.Date, но не содержит времени. Путаница неизбежна. ✅ Спасение в Java 8+ (java.time) Java переняла опыт библиотеки Joda-Time и внедрила JSR 310. Теперь у нас есть строгие, неизменяемые и понятные типы. Шпаргалка, что выбрать: 1️⃣ Instant — Точка на временной шкале (Unix Timestamp). Для кого: Для машин, логов и баз данных. Внутри это просто количество секунд с 1970 года (UTC). Пример: Instant.now() 2️⃣ LocalDate / LocalTime — "Дата в календаре" и "Время на часах". Без часового пояса. Для кого: Дни рождения, праздники ("Новый год всегда 1 января", неважно, где вы). Пример: LocalDate.of(2023, Month.JANUARY, 1) 3️⃣ ZonedDateTime — Полный фарш: дата + время + часовой пояс. Для кого: Для организации встреч звонков между странами. Учитывает переход на летнее время! 💎 Неизменяемость (Immutability): Больше никаких сюрпризов. Методы изменения времени всегда возвращают новый объект.

LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1); // today остался прежним!
💡 Лайфхак: Если вам нужно посчитать разницу между датами, используйте Period (для дней/месяцев) или Duration (для секунд/наносекунд).

long days = ChronoUnit.DAYS.between(date1, date2);

🤖Уведомления в Android — это важный инструмент взаимодействия с пользователем, который напрямую влияет на вовлечённость, ret
🤖Уведомления в Android — это важный инструмент взаимодействия с пользователем, который напрямую влияет на вовлечённость, retention и пользовательский опыт. Ошибки в работе с уведомлениями — частая проблема у начинающих Android-разработчиков. 📆На открытом вебинаре OTUS разберём все виды уведомлений в Android: от базовых до более продвинутых сценариев. Покажем, какие типы уведомлений существуют, чем они отличаются и в каких случаях используются. Все примеры — с разбором кода и практической реализацией. За один вебинар вы получите системное понимание уведомлений в Android и научитесь внедрять их в свои проекты осознанно, а не методом проб и ошибок. Это базовый навык, без которого невозможно двигаться дальше в Android-разработке. 💥Встречаемся 27 января в 20:00 МСК в преддверии старта курса «Android Developer». Регистрация открыта: https://otus.pw/8KD0/ Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576

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

🔴 Завтра тестовое собеседование с Java-разработчиком 21 января(уже завтра!) в 19:00 по мск приходи онлайн на открытое собесе
🔴 Завтра тестовое собеседование с Java-разработчиком 21 января(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика. Как это будет: 📂 Сергей Чамкин, старший разработчик из Uzum, ex-WildBerries, будет задавать реальные вопросы и задачи разработчику-добровольцу 📂 Cергей будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью 📂 В конце можно будет задать любой вопрос Сергею Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы. Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot Реклама. О рекламодателе.

👻 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.

Твой английский тормозит карьеру в IT? Привет! Меня зовут Диана, я основатель Fluent Biz — школы делового английского. Знаком
Твой английский тормозит карьеру в IT? Привет! Меня зовут Диана, я основатель Fluent Biz — школы делового английского. Знакомо, когда: • Теряешь нить на митинге с foreign colleagues? • Не можешь уверенно представить свой проект на английском? • Заказчики просят «уточнить детали», а ты не знаешь, как объяснить? Мы решаем эти проблемы — быстро, без воды и скучных учебников! 🔥 Дарим тебе БЕСПЛАТНЫЙ пробный урок, на котором: ✅ Определим твой уровень английского ✅ Разберём твои слабые места в речи и переписке ✅ Расскажу о том, каким образом и за какое время ты сможешь выйти на новый уровень языка ✅ Докажу, что говорить на английском с самого первого урока — реально 🎁 + Бонус для первых 5 записавшихся: консультация по подготовке к собеседованию на английском. Что делать? 1️⃣ Подпишись на наш канал: @fluent_biz 2️⃣ Напиши @blhdiair cо словом «JAVA» — и мы согласуем удобное время для урока. Промокод на бесплатный урок сгорит ровно через неделю, успей написать до 25.07📑 Не дай незнанию английского стать твоим потолком в карьере!

⌨️ 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 его «не видит».

🗑Внутренности 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.

💻 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 → контракт, гарантирующий порядок Используй эти инструменты не ради ключевых слов, а чтобы код был предсказуемым и безопасным в многопоточности.

Почему при зарплате 400к денег всё равно нет? Замечал, как люди с доходом 80к умудряются копить на отпуск и покупать айфоны? А у многих айтишников при 300-400к — в конце месяца ноль. Знакомо? Есть 5 конкретных причин, почему так происходит при высоком доходе. Снял видео — разбираю каждую. 5 минут, без воды. Посмотри, возможно узнаешь себя или коллег! Смотреть #реклама 16+ sbsite.pro О рекламодателе

💻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 — это дефолтный выбор для многопоточной генерации. Меньше блокировок, больше скорость. Просто работает.

💻 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. Это способ сказать коду: «результат будет, ошибки будут, таймауты будут - разберись с этим прямо сейчас и не блокируй поток».

⌨️ 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. Мелочь, а как приятно!

⌨️ 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 раза автоматически. Без переписывания кода.

🔴 Завтра тестовое собеседование с Java-разработчиком 24 декабря(уже завтра!) в 19:00 по мск приходи онлайн на открытое собес
🔴 Завтра тестовое собеседование с Java-разработчиком 24 декабря(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика. Как это будет: 📂 Сергей Чамкин, старший разработчик из Uzum, ex-WildBerries, будет задавать реальные вопросы и задачи разработчику-добровольцу 📂 Cергей будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью 📂 В конце можно будет задать любой вопрос Сергею Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы. Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot Реклама. О рекламодателе.

⌨️ Внутренности 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() всё равно вызывается, но только когда мы уже нашли кандидата (или идем по пути с абсолютно идентичными хешами). Мы не "опрашиваем" всех соседей подряд.

Привет. Вот тебе самые топовые каналы по IT! ⚙️ Free Znanija (IT) — Самая огромная коллекция платных курсов, которые можно скачать бесплатно; 👩‍💻 IT Books — Самая огромная библиотека книг; 💻 Hacking & InfoSec Base — Крутой блог белого хакера; 🛡 CyberGuard — Всё про ИБ; 🤔 ИБ Вакансии — Всё, чтобы найти работу в ИБ; 👩‍💻 linux administration — Всё про Линукс; 👩‍💻 Программистика — Python, python и ещё раз python; 👩‍💻 GameDev Base — Всё про GameDev; 😆 //code — Самые топовые мемы по IT: Подпишись, чтобы не потерять!

⌨️ 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.