Java | Фишки и трюки
前往频道在 Telegram
Java: примеры кода, интересные фишки и полезные трюки Купить рекламу: https://telega.in/c/java_tips_and_tricks ✍️По всем вопросам: @Pascal4eg Менеджер по рекламе: @shmyzna
显示更多6 953
订阅者
-324 小时
-27 天
+630 天
帖子存档
6 953
⌨️ Какой хеш-код имеет
null?
null не имеет хеш-кода, потому что null не является объектом. Однако, при использовании null в структурах данных, таких как HashMap или HashSet, применяется специальная логика:
➡️ Что происходит с null в HashMap и HashSet?
✔️ В HashMap ключ null всегда попадает в первый бакет (bucket 0).
✔️ В HashSet (который использует HashMap внутри) null также хранится в этом же бакете.
✔️ Проверки выполняются в Objects.hashCode(), который для null всегда возвращает 0.
➡️ Пример:
System.out.println(Objects.hashCode(null)); // 0
📌 Вывод: null не имеет собственного хеш-кода, но в хеш-структурах ему назначается 0.6 953
👩💻 Программирование — В С Ё
В 2026 году на кодинге уже не вывезешь, перспектива года - Информационная Безопасность.
Ловите полезные каналы, которые помогут ворваться в новое направление.
👍 ZeroDay — Уроки, эксплуатация уязвимостей с нуля
👍 Белый Хакер — Свежие новости из мира ИБ
😎 Бункер Хакера — Статьи, книги, шпаргалки и хакинг
👨💻 Серверная Админа — Настройка и уроки по компьютерным сетям
📂 Вступай и изучай новое направление!
6 953
☕️ Интерфейсы в Java
В этом видео автор подробно разбирает, что такое интерфейсы в Java и чем они похожи на абстрактные классы. Вы узнаете об их ключевых отличиях, научитесь применять интерфейсы на практике и поймёте, в каких задачах они становятся незаменимым инструментом для создания гибкой и понятной архитектуры кода.👉 Ссылка на первоисточник 🗣Запомни: интерфейсы — это контракты, которые определяют что должен делать класс, а не как. Это основа для написания слабосвязанного и тестируемого кода. 🤩 Java Фишки и трюки || #Видео
6 953
👩💻 Хватит учить только синтаксис, начинай делать реальные проекты!
Python Ready — авторский канал, где Python перестаёт быть только теорией и становится рабочим инструментом. Мини-проекты, боты, советы, разборы задач, гайды и шпаргалки для каждого программиста.
🔥 Советую подписаться: @python_ready
6 953
📅
LocalDate и DateTimeFormatter: вечная путаница с YYYY и yyyy
Каждый Новый год эта ошибка просыпается и незаметно портит данные в логах, отчётах и экспортах. Разница между YYYY и yyyy - всего одна буква, но она может сдвинуть дату на целый год вперёд или назад в первую неделю января.
Вот как не попасть в эту ловушку и почему это происходит 👇
📁 Шаг 1: Наглядный пример ошибки (31 декабря 2024 года)
DateTimeFormatter wrongFormatter = DateTimeFormatter.ofPattern("dd.MM.YYYY");
DateTimeFormatter correctFormatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
LocalDate date = LocalDate.of(2024, 12, 31);
System.out.println("Неправильно (YYYY): " + date.format(wrongFormatter));
// 31.12.2025 ← вот он, сдвиг на год!
System.out.println("Правильно (yyyy): " + date.format(correctFormatter));
// 31.12.2024
➡️ Да, 31 декабря 2024 года форматируется как... 2025 год. Это не баг, а фича, о которой мало кто знает.
📁 Шаг 2: В чём разница? (Год vs Год недели)
- `yyyy` — это обычный календарный год (year-of-era). То, что мы все понимаем под годом.
- `YYYY` — это год недели (week-based-year) по стандарту ISO 8601.
➡️ YYYY используется вместе с номером недели (ww) и днём недели (E). Он нужен для отображения года, к которому относится неделя, а не календарная дата.
📁 Шаг 3: Почему происходит сдвиг? (Магия первой недели года)
По ISO 8601:
1. Неделя начинается с понедельника.
2. Первая неделя года — это неделя, содержащая первый четверг года.
3. Год недели (YYYY) — это год, к которому относится эта неделя.
// Пример: 1 января 2025 года — это среда
LocalDate jan1 = LocalDate.of(2025, 1, 1);
System.out.println("Год недели: " + jan1.format(DateTimeFormatter.ofPattern("YYYY")));
// 2025 — потому что неделя (30 дек - 5 янв) содержит первый четверг 2025 года (2 января)
// А 30 декабря 2024 года — это понедельник, начало недели 1 (2025 года!)
LocalDate dec30 = LocalDate.of(2024, 12, 30);
System.out.println("Год недели: " + dec30.format(DateTimeFormatter.ofPattern("YYYY")));
// 2025 — потому что эта неделя (30 дек - 5 янв) содержит первый четверг 2025 года
➡️ Критические периоды: последние 3-4 дня декабря и первые 3-4 дня января. Именно здесь YYYY и yyyy чаще всего расходятся.
📁 Шаг 4: Где используется `YYYY` (правильно)
Только вместе с номером недели!
// Для отображения номера недели и года недели (отчёты, планирование)
DateTimeFormatter weekFormatter = DateTimeFormatter.ofPattern("'Неделя' ww, YYYY");
LocalDate date = LocalDate.of(2024, 12, 30);
System.out.println(date.format(weekFormatter));
// "Неделя 01, 2025" ← это правильно для систем, работающих по неделям
➡️ Используй YYYY только если ты точно понимаешь, что такое год недели, и используешь его с ww (номер недели).
📁 Шаг 5: Где НЕЛЬЗЯ использовать `YYYY` (опасные случаи)
1. Логирование — дата в логах внезапно станет 2025 годом 30 декабря.
2. Экспорт в CSV/Excel для отчётности.
3. Формирование имён файлов с датами.
4. Любые даты, которые потом будут парситься обратно.
// ❌ Опасный парсинг (может сломаться)
String wrong = "31.12.2024";
LocalDate parsed = LocalDate.parse(wrong, DateTimeFormatter.ofPattern("dd.MM.YYYY"));
// Может выкинуть исключение или спарсить не ту дату
📁 Шаг 6: Как избежать ошибки навсегда
1. Используй готовые форматеры вместо ручных паттернов:
DateTimeFormatter.ISO_LOCAL_DATE; // yyyy-MM-dd
DateTimeFormatter.BASIC_ISO_DATE; // yyyyMMdd
2. Пиши тесты на граничные даты:
@Test
void dateFormatting_aroundNewYear() {
LocalDate dec30 = LocalDate.of(2024, 12, 30);
LocalDate jan1 = LocalDate.of(2025, 1, 1);
assertThat(dec30.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")))
.isEqualTo("30/12/2024");
assertThat(jan1.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")))
.isEqualTo("01/01/2025");
}
3. Включи в code review проверку всех DateTimeFormatter на наличие YYYY.
🗣️ Запомни: YYYY - это не опечатка, а отдельная концепция. Используй yyyy для всего, что связано с календарными датами. А YYYY оставь для отчётов по неделям.6 953
Docker и Kubernetes: основы разработки под облачную инфраструктуру
Курс для тех, кто хочет держать свой стэк и знания актуальными и глубоко разбираться, как устроены Docker, Kubernetes, и современная облачная инфраструктура в целом.
🌐 Чему вы научитесь:
🤩 Создавать облачную инфраструктуру «с нуля» управление и конфигурация серверов с Terraform, Ansible, cloud‑init
🤩 Уверенно работать с Docker: Dockerfile, слои, кэш, многоступенчатые сборки, реестры, безопасность, air‑gapped
🤩 Проектировать многоконтейнерные приложения: паттерны Sidecar, Ambassador, Adapter, проверки (liveness/readiness), DaemonSet и поды
🤩 Настраивать сеть и балансировку в Kubernetes
ClusterIP, Services, Ingress, MetalLB, TLS/SNI, сервис‑меши (Istio)
🤩 Организовывать хранение данных: PersistentVolumes / PVC, StorageClasses, резервное копирование. Упаковка в Helm и поддержка через Operator
🥸 Кто мы: R&D-центр Devhands. Автор курса — Николай Ихалайнен, эксперт по СУБД и бекенду (ex-Percona), со-основатель MyDB, энтузиаст открытого ПО.
🗓 Старт курса: 25 февраля, 6 недель обучения.
Изучить программу и записаться можно здесь.
Ждем вас!
Реклама. ИП Рыбак А.А. ИНН 771407709607 Erid: 2Vtzqvh26Qk
6 953
🧪 Тестируем приватные методы: хак или норма? (Спойлер: это запах)
Тебе точно знакома ситуация: логика спрятана в приватном методе, а протестировать её «в лоб» нельзя. Первая мысль - рефлексия! Но это как использовать лом для починки часов. Сломаешь больше, чем починишь.
Вот почему тестирование приватных методов - это сигнал о проблеме в дизайне класса, и как это исправить правильно 👇
🟢 Шаг 1: Типичный хак через рефлексию (как НЕ делать)
import java.lang.reflect.Method;
public class Calculator {
private int superSecretFormula(int a, int b) {
return (a + b) * (a - b);
}
}
// В тесте:
Method method = Calculator.class.getDeclaredMethod("superSecretFormula", int.class, int.class);
method.setAccessible(true); // Взламываем приватность
int result = (int) method.invoke(new Calculator(), 5, 3);
➡️ Проблемы:
🔴 Код теста становится хрупким: переименовал метод — тесты сломались.
🔴 Нарушается инкапсуляция. Тест теперь знает о внутренней кухне класса.
🔴 Это просто некрасиво. Рефлексия — это крик отчаяния.
🟢 Шаг 2: Почему это «запах» кода?
Если метод настолько сложный, что его нужно тестировать отдельно — он, скорее всего, делает что-то самостоятельное. Возможно, он:
1. Выполняет несколько задач (нарушает SRP). 2. Содержит логику, которая может пригодиться где-то ещё. 3. Слишком умный для своего класса.➡️ Частный метод — это деталь реализации. Тестировать нужно публичное поведение, а не приватные детали. 🟢 Шаг 3: Решение №1 — Вынеси логику в отдельный класс ❌Был:
public class ReportService {
public String generateReport() {
// ... подготовка данных
String cleaned = cleanData(rawData); // приватный метод
// ... формирование отчёта
}
private String cleanData(String data) {
// сложная логика очистки
}
}
✔️Стал:
// Новая публичная единица, которую легко тестировать
public class DataCleaner {
public String clean(String data) {
// та же логика, но теперь она публичная
}
}
// В ReportService:
public class ReportService {
private final DataCleaner cleaner;
public String generateReport() {
String cleaned = cleaner.clean(rawData);
// ...
}
}
➡️ Теперь DataCleaner можно и нужно тестировать напрямую. Это чистая архитектура.
🟢 Шаг 4: Решение №2 — Сделай метод package-private (если очень надо)
// Было: private void helper() { ... }
// Стало (в том же пакете):
class Service {
void helper() { ... } // без модификатора = виден в пакете
}
// В тестах (которые лежат в том же test/java/... пакете):
@Test
void testHelper() {
Service service = new Service();
service.helper(); // Доступно!
}
➡️ Компромиссный вариант. Логика всё ещё скрыта от внешнего мира, но доступна для тестов. Используй осторожно.
🟢 Шаг 5: Решение №3 — Тестируй через публичный метод
Просто протестируй публичный метод, который использует этот приватный. Если покрытие кажется недостаточным — возможно, это повод задуматься: а нужен ли этот тест? Может, приватный метод настолько прост, что не требует отдельного теста?
@Test
void generateReport_usesCleanDataLogic() {
ReportService service = new ReportService();
String report = service.generateReport();
assertThat(report).contains("очищенные данные");
}
➡️ Ты тестируешь результат, а не реализацию. Это сильнее и надёжнее.
🗣️ Запомни: Рефлексия для тестов — как гаечный ключ в микросхеме. Если очень хочется протестировать приватное, значит, твой класс тайно просит, чтобы его разделили. Хороший дизайн рождает лёгкое тестирование.6 953
📱Большинство Android-приложений ломаются не из-за UI, а из-за архитектуры. Неправильное разделение слоёв, хаотичная работа с API и логикой — и проект перестаёт масштабироваться уже на старте.
На открытом вебинаре OTUS вы пройдёте полный путь создания Android-приложения: от работы с API сервера до отображения данных на экране. Разберём сетевое взаимодействие, получение данных из интернета и разложим приложение по слоям в рамках чистой архитектуры и MVVM.
Этот урок — практическая точка входа в современную Android-разработку. Вы увидите, как связать API, бизнес-логику и UI в целостное приложение, которое легко поддерживать и развивать.
📆Встречаемся 24 февраля в 20:00 МСК в преддверии старта курса «Android Developer». Регистрация открыта: https://otus.pw/oQe2/
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru
6 953
🪄 Магия
@SneakyThrows в Lombok: как она обманывает компилятор
Когда видишь @SneakyThrows, кажется, что нашёл волшебную палочку: checked-исключения больше не нужно объявлять в throws и ловить в try-catch. Но эта магия — не настоящее волшебство, а тонкая иллюзия, которая может сломать твой код в самый неподходящий момент.
Давай заглянем под капот и узнаем, как эта аннотация обманывает компилятор, и почему с ней нужно быть осторожным 👇
🟢 Шаг 1: Как работают checked-исключения (обычный путь)
// Без Lombok: нужно либо пробросить, либо поймать
public void readFile() throws IOException { // Объявляем throws
Files.readAllBytes(Paths.get("file.txt"));
}
// Или так:
public void readFile() {
try {
Files.readAllBytes(Paths.get("file.txt"));
} catch (IOException e) { // Ловим исключение
throw new RuntimeException(e);
}
}
➡️ Компилятор Java требует обработать checked-исключения (IOException, SQLException). Это проверка на этапе компиляции.
🟢 Шаг 2: Волшебство `@SneakyThrows` (как оно выглядит)
import lombok.SneakyThrows;
public class FileReader {
@SneakyThrows
public byte[] readFile() {
return Files.readAllBytes(Paths.get("file.txt"));
}
}
➡️ Никакого throws, никакого try-catch! Код компилируется, и ты можешь вызывать метод, как будто исключений не существует. Но они никуда не делись.
🟢 Шаг 3: Как Lombok это делает (разоблачение фокуса)
Компилятор Lombok преобразует код примерно вот во что:
public byte[] readFile() {
try {
return Files.readAllBytes(Paths.get("file.txt"));
} catch (IOException e) {
throw Lombok.sneakyThrow(e); // Вот этот метод — ключ!
}
}
// А метод sneakyThrow делает вот что:
public static RuntimeException sneakyThrow(Throwable t) {
throw Lombok.<RuntimeException>sneakyThrow0(t);
}
@SuppressWarnings("unchecked")
private static <T extends Throwable> T sneakyThrow0(Throwable t) throws T {
throw (T) t; // Кастование к unchecked-исключению!
}
➡️ Фокус в двойном касте! Lombok «притворяется», что кидает unchecked-исключение (RuntimeException), но на самом деле прокидывает оригинальное checked-исключение. JVM разрешает это, потому что стирание типов (type erasure) в generics скрывает реальный тип исключения.
🟢 Шаг 4: Чем это опасно? Проблема №1 — отсутствие декларации
@SneakyThrows
public void connect() {
Socket socket = new Socket("host", 9999); // Может кинуть IOException
// ... работа с сокетом
}
// Где-то в другом месте:
public void init() {
connect(); // Код не знает, что здесь возможен IOException!
// И поэтому не готов его обработать
}
➡️ Контракт метода нарушен. Вызывающий код не знает, что метод может выбросить checked-исключение. Это сбивает с толку и ломает принцип явного объявления исключений.
🟢 Шаг 5: Проблема №2 — несовместимость с некоторыми конструкциями
// Попробуем использовать в лямбде (Consumer)
list.forEach(element -> {
@SneakyThrows
Thread.sleep(100); // Так не сработает!
});
// Нужно отдельно выносить:
list.forEach(this::sneakySleep);
@SneakyThrows
private void sneakySleep(Object element) {
Thread.sleep(100);
}
➡️ @SneakyThrows не работает прямо на лямбдах. Это ограничивает его применение.
🟢 Шаг 6: Когда это можно использовать (редкие случаи)
1. В тестах, где checked-исключения маловероятны и только мешают.
2. В реализациях интерфейсов, которые не объявляют исключений, но твоя реализация может их кидать (например, Runnable).
3. Для избежания излишнего обертывания в RuntimeException, когда ты точно уверен в поведении метода.
// Пример с Runnable
public class Worker implements Runnable {
@SneakyThrows
@Override
public void run() {
Files.readAllBytes(Paths.get("config.json")); // IOException будет проброшен как unchecked
}
}
➡️ Но даже здесь нужно понимать риски — исключение может «уплыть» и убить поток без возможности адекватной обработки.
🗣️ Запомни: @SneakyThrows - это не решение, а побег из тюрьмы checked-исключений через подкоп.6 953
🔴 Завтра тестовое собеседование с Java-разработчиком
11 февраля(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.
Как это будет:
📂 Сергей Чамкин, старший разработчик из Uzum, ex-WildBerries, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Cергей будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Сергею
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама.
О рекламодателе.
6 953
⌨️ Какие существуют модификаторы доступа?
В Java модификаторы доступа определяют, из каких частей программы можно получить доступ к классу, методу или переменной. Всего их четыре:
public – предоставляет полный доступ к классу или его членам из любого другого класса в любом пакете. Применяется для методов, полей и классов, которые должны быть доступны всем.
protected – доступ к члену класса разрешен в пределах того же пакета и для подклассов, даже если они находятся в других пакетах. Используется для полей и методов, которые могут быть полезны в наследовании.
default (пакетный доступ) – если модификатор не указан явно, доступ к члену класса разрешен только внутри того же пакета. Этот доступ используется для внутренних структур, не предназначенных для использования в других пакетах.
private – доступен только внутри того же класса. Поля и методы, помеченные как private, невидимы для других классов, включая подклассы.
Эти модификаторы помогают контролировать видимость и инкапсуляцию данных в программе.
#java #public #protected #default #private
6 953
🎭
Optional.ofNullable() - как не стрелять себе в ногу NullPointerException
Кажется, что Optional должен спасать от null. Но если перепутать Optional.of() с Optional.ofNullable() - получишь NPE там, где меньше всего ожидал. Это как заряжать пистолет, думая, что он игрушечный.
Вот разбор, как не попасть в эту ловушку и когда что использовать 👇
📂 Шаг 1: В чём разница? (Одно слово - null)
// Если value == null, то:
Optional.of(value); // 🚨 Выбросит NPE
Optional.ofNullable(value); // ✅ Вернёт Optional.empty()
➡️ Optional.of создаёт контейнер только для не-null значений. Optional.ofNullable - для любых, сам проверит и сделает empty() при null.
📂 Шаг 2: Где стреляет `Optional.of()` (реальный пример)
Представь, что метод может вернуть null, а ты оборачиваешь результат в Optional для красоты:
String findNameById(Long id) {
// ... какой-то поиск, который может вернуть null
}
// Где-то в коде:
Optional<String> name = Optional.of(findNameById(42)); // БАМ! NPE
➡️ Кажется, что ты делаешь «безопасный» контейнер, но of() выстрелит раньше, чем ты успеешь его использовать. Типичная ошибка при рефакторинге старого кода.
📂 Шаг 3: Правильный паттерн для оборачивания неизвестных значений
// Входящее значение может быть null -> только ofNullable
Optional<String> safeName = Optional.ofNullable(findNameById(42));
// Теперь можно безопасно работать
String result = safeName.orElse("По умолчанию");
➡️ ofNullable - это шлюз, который пропускает и null, и значение, превращая первое в empty().
📂 Шаг 4: А когда тогда нужен `Optional.of()`?
Только когда ты на 100% уверен, что значение не null. Например:
// Константы, финальные строки
Optional<String> env = Optional.of("PROD");
// Результаты методов, которые никогда не возвращают null по контракту
Optional<String> uuid = Optional.of(UUID.randomUUID().toString());
➡️ Используй of() как assertion - «это точно не null». Если сомневаешься - ofNullable.
📂 Шаг 5: Тест на внимательность
public Optional<String> extract(boolean flag) {
String value = flag ? "Java" : null;
return Optional.of(value); // Что будет при flag = false?
}
➡️ Правильно - NPE при вызове метода, а не пустой Optional. Эту ошибку компилятор не подскажет.
🗣 Запомни: Optional.of() - это крик «я уверен!». Если не готов ловить NPE, как пулю, всегда используй ofNullable.6 953
🔥 БЕСПЛАТНЫЙ КУРС ПО ВРЕМЕННЫМ РЯДАМ И AI 🔥
Ищете практический и углубленный курс, чтобы освоить временные ряды? Мы создали курс из 5 объемных занятий. Это именно то, что нужно, чтобы прокачаться в одной из самых востребованных аналитических областей абсолютно бесплатно!
📌 Темы занятий:
1. Основы анализа временных рядов
2. Прогнозирование на основе временных рядов с помощью AI
3. Выявление аномалий в данных с помощью нейросетей
4. Применение временных рядов в рекомендационных системах
5. Тенденции и будущее анализа временных рядов с AI
Почему временные ряды? Потому что это одна из центральных тем, они отличаются тем, что:
🧬 1. Очень нужны компаниям - прям прямая необходимость
🧬 2. Очень непредсказуемые - в отличие от CV, где всё понятно, тут итоговая точность нейронки вообще непредсказуемая
🤖 Присоединяйтесь к нашему бесплатному курсу и разберитесь в этой увлекательной теме с нами!
6 953
☕️ Построение ПК приложений. Java Swing JFrame
В этом видео автор переходит от консольных проектов к созданию графических интерфейсов. Вы научитесь разрабатывать полноценное ПК-приложение с использованием библиотеки Java Swing, добавляя в него кнопки, надписи, текстовые поля и другие графические компоненты на основе JFrame.👉 Ссылка на первоисточник 🗣️Запомни: умение создавать GUI-приложения — это ключевой шаг от учебных задач к разработке реальных программ, которые видят и используют люди. 🤩 Java Фишки и трюки || #Видео
6 953
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 для склейки с разделителями. Используй вместо ручных проверок «не первый ли элемент?».
🗣 Запомни: Выбирай инструмент не по привычке, а по делу.6 953
🕰️ 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);6 953
🤖Уведомления в Android — это важный инструмент взаимодействия с пользователем, который напрямую влияет на вовлечённость, retention и пользовательский опыт. Ошибки в работе с уведомлениями — частая проблема у начинающих Android-разработчиков.
📆На открытом вебинаре OTUS разберём все виды уведомлений в Android: от базовых до более продвинутых сценариев. Покажем, какие типы уведомлений существуют, чем они отличаются и в каких случаях используются. Все примеры — с разбором кода и практической реализацией.
За один вебинар вы получите системное понимание уведомлений в Android и научитесь внедрять их в свои проекты осознанно, а не методом проб и ошибок. Это базовый навык, без которого невозможно двигаться дальше в Android-разработке.
💥Встречаемся 27 января в 20:00 МСК в преддверии старта курса «Android Developer». Регистрация открыта: https://otus.pw/8KD0/
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
6 953
⌨️ 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 953
🔴 Завтра тестовое собеседование с Java-разработчиком
21 января(уже завтра!) в 19:00 по мск приходи онлайн на открытое собеседование, чтобы посмотреть на настоящее интервью на Middle Java-разработчика.
Как это будет:
📂 Сергей Чамкин, старший разработчик из Uzum, ex-WildBerries, будет задавать реальные вопросы и задачи разработчику-добровольцу
📂 Cергей будет комментировать каждый ответ респондента, чтобы дать понять чего от вас ожидает собеседующий на интервью
📂 В конце можно будет задать любой вопрос Сергею
Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.
Переходи в нашего бота, чтобы получить ссылку на эфир → @shortcut_sh_bot
Реклама.
О рекламодателе.
6 953
👻 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.
现已上线!2025 年 Telegram 研究 — 年度关键洞察 
