🎯 Optional: используй правильно
Optional появился в Java 8, но многие до сих пор используют его неэффективно. Короткий чеклист 👇
❌ Антипаттерны
✔️ Полезные комбинации
❌ Когда НЕ использовать
— Не используй Optional в полях класса
— Не передавай Optional как параметр метода
— Не создавай Optional для коллекций (верни пустую коллекцию)
— Не используй Optional для примитивов (есть OptionalInt, OptionalLong, OptionalDouble)
✔️ Когда использовать
— Возвращаемое значение метода, которое может отсутствовать
— Stream API operations
— Замена null в return
Ставь → 🔥, если полезно и хочешь короткие разборы других фич?
🔹 Курс «Алгоритмы и структуры данных»
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib
🐸 Библиотека джависта
#CoreJava
Optional появился в Java 8, но многие до сих пор используют его неэффективно. Короткий чеклист 👇
// Плохо #1: проверка через isPresent()
if (optional.isPresent()) {
doSomething(optional.get());
}
// ✔️ Правильно:
optional.ifPresent(this::doSomething);
// Плохо #2: get() без проверки
User user = userOptional.get(); // NoSuchElementException
// ✔️ Правильно:
User user = userOptional.orElseThrow(() ->
new UserNotFoundException("User not found"));
// Плохо #3: orElse с вычислением
return optional.orElse(repository.findDefault()); // ВСЕГДА вызовется!
// ✔️ Правильно:
return optional.orElseGet(() -> repository.findDefault());
// map + orElse
String name = userOptional
.map(User::getName)
.orElse("Anonymous");
// flatMap для вложенных Optional
Optional<String> email = userOptional
.flatMap(User::getEmail); // User::getEmail возвращает Optional<String>
// filter + ifPresent
userOptional
.filter(u -> u.getAge() > 18)
.ifPresent(this::sendAdultContent);
// or (Java 9+) - fallback к другому Optional
return cache.get(id)
.or(() -> database.find(id))
.orElseThrow();
— Не используй Optional в полях класса
— Не передавай Optional как параметр метода
— Не создавай Optional для коллекций (верни пустую коллекцию)
— Не используй Optional для примитивов (есть OptionalInt, OptionalLong, OptionalDouble)
— Возвращаемое значение метода, которое может отсутствовать
— Stream API operations
— Замена null в return
Ставь → 🔥, если полезно и хочешь короткие разборы других фич?
🔹 Курс «Алгоритмы и структуры данных»
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥43❤3👍3🤔2
👀 Внутреннее устройство Map.computeIfAbsent()
computeIfAbsent() — это не просто «get или put». Это атомарная операция с ленивым вычислением, которая решает классическую проблему check-then-act в многопоточном коде.
📦 Что такое computeIfAbsent()
— Поведение
1. Если ключ существует и value != null → вернуть value
2. Если ключа нет или value == null → вызвать mappingFunction
3. Результат функции put в map
4. Вернуть computed value
— Классический use case:
🔍 Упрощённый код из JDK:
📊 Performance
Benchmark: 1M операций
✅ Делайте
— Используйте для lazy initialization
— Используйте ConcurrentHashMap для thread-safety
— Держите mappingFunction быстрым и простым
❌ Не делайте
— Не вызывайте computeIfAbsent рекурсивно на том же ключе
— Не модифицируйте map внутри mappingFunction
— Не возвращайте null если хотите кэшировать отсутствие
— Не используйте для побочных эффектов (только для вычисления value)
🔗 Документация
Ставьте 🔥, если интересны другие Map методы!
✨ Бонусы для подписчиков:
— Скидка 40% на все курсы Академии
— Розыгрыш Apple MacBook
— Бесплатный тест на знание математики
🐸 Библиотека джависта
#CoreJava
computeIfAbsent() — это не просто «get или put». Это атомарная операция с ленивым вычислением, которая решает классическую проблему check-then-act в многопоточном коде.
📦 Что такое computeIfAbsent()
— Поведение
1. Если ключ существует и value != null → вернуть value
2. Если ключа нет или value == null → вызвать mappingFunction
3. Результат функции put в map
4. Вернуть computed value
— Классический use case:
// ❌ Старый способ — race condition!
if (!map.containsKey(key)) {
map.put(key, expensiveOperation());
}
// ✅ Новый способ — атомарно
map.computeIfAbsent(key, k -> expensiveOperation());
🔍 Упрощённый код из JDK:
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
if (mappingFunction == null)
throw new NullPointerException();
int hash = hash(key);
Node<K,V>[] tab = table;
Node<K,V> first = tab[index];
// Поиск существующего entry
if (first != null) {
Node<K,V> e = first;
do {
if (e.hash == hash &&
Objects.equals(key, e.key)) {
V v = e.value;
if (v != null) {
return v; // Найден, не вызываем функцию!
}
}
} while ((e = e.next) != null);
}
// Ключа нет — вызов mappingFunction
V newValue = mappingFunction.apply(key);
if (newValue != null) {
putVal(hash, key, newValue, true, true);
}
return newValue;
}
📊 Performance
Benchmark: 1M операций
// Старый способ: containsKey + put
if (!map.containsKey(key)) {
map.put(key, new ArrayList<>());
}
// Time: ~45ms, 2 hash lookups
// computeIfAbsent
map.computeIfAbsent(key, k -> new ArrayList<>());
// Time: ~30ms, 1 hash lookup
computeIfAbsent() на 33% быстрее!
✅ Делайте
— Используйте для lazy initialization
— Используйте ConcurrentHashMap для thread-safety
— Держите mappingFunction быстрым и простым
❌ Не делайте
— Не вызывайте computeIfAbsent рекурсивно на том же ключе
— Не модифицируйте map внутри mappingFunction
— Не возвращайте null если хотите кэшировать отсутствие
— Не используйте для побочных эффектов (только для вычисления value)
Ставьте 🔥, если интересны другие Map методы!
✨ Бонусы для подписчиков:
— Скидка 40% на все курсы Академии
— Розыгрыш Apple MacBook
— Бесплатный тест на знание математики
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10❤4👍4👏1
Какая рубрика нравится больше? Если забыли, о чём рубрика, можно освежить в памяти тут.
🔥 → #CoreJava
👍🏼 → #Enterprise
👾 → #DevLife
🤔 → #News
❤️ → Всё нравится :))
#DevLife
Please open Telegram to view this post
VIEW IN TELEGRAM
❤20👍12🔥6👾4
HashSet — это реализация интерфейса Set на основе HashMap. Хранит уникальные элементы без дубликатов с быстрым O(1) поиском.
📦 Базовая структура
HashSet — это тонкая обёртка над HashMap:
public class HashSet<E> {
private transient HashMap<E, Object> map;
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
}▪️ Главные особенности
→ Элементы хранятся как ключи HashMap.
→ Значения — константа PRESENT (заглушка).
→ O(1) для add, remove, contains (как у HashMap).
→ Не гарантирует порядок элементов.
→ Не допускает дубликаты (ключи Map уникальны).
🔹 Внутренняя структура идентична HashMap:
HashMap<E, PRESENT>:
table[]:
[0]: null
[1]: Entry(key="B", value=PRESENT) → Entry(key="M", value=PRESENT)
[2]: Entry(key="A", value=PRESENT)
[3]: Entry(key="C", value=PRESENT)
[4]: null
...
Set элементы = ключи HashMap
Значения = PRESENT (игнорируются)
🔹 Все операции делегируются HashMap
➕ add(E element) — добавление
1. Вызывается map.put(element, PRESENT).
2. HashMap вычисляет hash(element).
3. Определяется бакет по индексу.
4. Проверяется наличие ключа через equals():
— Если есть: возвращается false (дубликат не добавлен).
— Если нет: создаётся Entry, возвращается true.
Сложность: O(1) в среднем.
🔎 contains(Object o) — проверка наличия
1. Вызывается map.containsKey(o).
2. HashMap ищет ключ по hash и equals().
3. Возвращается true/false.
Сложность: O(1) в среднем.
➖ remove(Object o) — удаление
1. Вызывается map.remove(o).
2. HashMap находит и удаляет ключ.
3. Возвращается true если элемент был, иначе false.
Сложность: O(1) в среднем.
⚖️ Важные нюансы
HashSet НЕ наследует HashMap, а содержит его как поле. Все методы делегируют вызовы.
HashSet допускает один null (так как HashMap допускает null key).
Элементы ДОЛЖНЫ корректно реализовывать hashCode() и equals().
Для concurrent доступа:
— Collections.synchronizedSet(new HashSet<>());
— ConcurrentHashMap.newKeySet();
— CopyOnWriteArraySet (для read-heavy нагрузки).
Как у HashMap, можно указать при создании. Это помогает избегать resize операций.
→ Быстрой проверки наличия
→ Автоматического удаления дубликатов
→ Операций над множествами. Объединение, пересечение, разность за O(n).
→ Максимальной производительности без затрат на сортировку.
Ставьте 🔥, если хотите ещё разбор.
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥19👍2👏1
🚀 Просто о сложном: паттерны проектирования микросервисов
Проектирование микросервисов — это не просто «разбить монолит на части». Нужны паттерны, которые помогают сервисам надёжно общаться, масштабироваться независимо и корректно восстанавливаться при сбоях.
Разбираем ключевые паттерны микросервисной архитектуры, которые должен знать каждый инженер:
1️⃣ API Gateway
→ Единая точка входа для всех клиентов
→ Скрывает сложность внутренней архитектуры
→ Берёт на себя аутентификацию, rate limiting, маршрутизацию
→ N запросов от клиента — 1 запрос через gateway
2️⃣ Saga Pattern
→ Для распределённых транзакций (когда несколько сервисов должны завершиться успешно или откатиться)
→ Два подхода: оркестрация или хореография
→ Гарантирует консистентность данных без глобальных блокировок
3️⃣ Circuit Breaker
→ Защищает систему от медленных или падающих downstream-сервисов
→ «Размыкает цепь» при превышении порога ошибок
→ Предотвращает каскадные падения
→ Автоматически восстанавливается после стабилизации
4️⃣ Event-Driven Architecture
→ Сервисы общаются через события вместо прямых вызовов
→ Слабая связанность компонентов
→ Отличная масштабируемость
→ Идеально для real-time обновлений
5️⃣ Strangler Fig
→ Постепенная миграция монолита
→ Выносим модули один за другим
→ Маршрутизируем трафик через gateway
→ Миграция без даунтайма
6️⃣ Database per Service
→ Каждый сервис владеет своими данными
→ Слабая связанность
→ Независимые деплои
→ Избегаем bottleneck «одной большой общей БД»
💬 Какие паттерны используете часто? Делитесь опытом в комментах 👇
🔹 Курс «Алгоритмы и структуры данных»
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib
🐸 Библиотека джависта
#CoreJava
Проектирование микросервисов — это не просто «разбить монолит на части». Нужны паттерны, которые помогают сервисам надёжно общаться, масштабироваться независимо и корректно восстанавливаться при сбоях.
Разбираем ключевые паттерны микросервисной архитектуры, которые должен знать каждый инженер:
→ Единая точка входа для всех клиентов
→ Скрывает сложность внутренней архитектуры
→ Берёт на себя аутентификацию, rate limiting, маршрутизацию
→ N запросов от клиента — 1 запрос через gateway
→ Для распределённых транзакций (когда несколько сервисов должны завершиться успешно или откатиться)
→ Два подхода: оркестрация или хореография
→ Гарантирует консистентность данных без глобальных блокировок
→ Защищает систему от медленных или падающих downstream-сервисов
→ «Размыкает цепь» при превышении порога ошибок
→ Предотвращает каскадные падения
→ Автоматически восстанавливается после стабилизации
→ Сервисы общаются через события вместо прямых вызовов
→ Слабая связанность компонентов
→ Отличная масштабируемость
→ Идеально для real-time обновлений
→ Постепенная миграция монолита
→ Выносим модули один за другим
→ Маршрутизируем трафик через gateway
→ Миграция без даунтайма
→ Каждый сервис владеет своими данными
→ Слабая связанность
→ Независимые деплои
→ Избегаем bottleneck «одной большой общей БД»
🔹 Курс «Алгоритмы и структуры данных»
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤5🔥2
LinkedList — это реализация интерфейса List и Deque на основе двусвязного списка. Отличный выбор для частых вставок/удалений, но медленный доступ по индексу.
📦 Базовая структура
LinkedList состоит из узлов (Node), связанных ссылками:
public class LinkedList<E> {
transient int size = 0;
transient Node<E> first; // голова списка
transient Node<E> last; // хвост списка
private static class Node<E> {
E item;
Node<E> next; // следующий узел
Node<E> prev; // предыдущий узел
}
}Главные особенности:
— O(1) для add/remove с концов (first/last).
— O(n) для доступа по индексу (нужен обход).
— O(1) для вставки/удаления при наличии ссылки на узел.
— Больше памяти чем ArrayList (~24 байта overhead на элемент).
Внутренности Node
→ item: элемент
→ prev: ссылка на предыдущий узел
→ next: ссылка на следующий узел
Преимущества двусвязного списка
— Обход в обе стороны (вперёд и назад).
— Быстрое удаление узла при наличии ссылки на него.
— Динамический размер без перевыделения памяти.
➕ add(E element) — добавление в конец
1. Создаётся новый Node:
newNode = new Node<>(last, element, null).2. Если список пустой
(last == null), то first = last = newNode.3. Иначе:
last.next = newNode И last = newNode.4. Увеличивается size.
Сложность: O(1).
➕ add(int index, E element) — вставка по индексу
1. Проверяется range:
index > size → IndexOutOfBoundsException.2. Если
index == size, вызывается addLast().3. Иначе находится узел по индексу через
node(index).4. Создаётся новый узел и вставляется перед найденным.
5. Обновляются ссылки prev/next соседних узлов.
Сложность: O(n) — нужен поиск узла по индексу.
🔎 get(int index) — получение по индексу
1. Проверка границ:
index >= size → IndexOutOfBoundsException.2. Вызывается
node(index) для поиска узла.3.
node(index) использует оптимизацию:— Если index < size/2: обход от first вперёд.
— Иначе: обход от last назад.
4. Возвращается item найденного узла.
Сложность: O(n) — в худшем случае обход половины списка.
➖ remove(int index) — удаление по индексу
1. Находится узел по индексу через
node(index).2. Узел отсоединяется от списка:
— node.prev.next = node.next (если prev != null).
— node.next.prev = node.prev (если next != null).
3. Обновляются first/last если нужно.
4. Обнуляются ссылки в узле для GC:
node.item = null; node.next = node.prev = null.Сложность: O(n) — поиск узла O(n), удаление O(1).
➕ addFirst(E e) / addLast(E e)
Специальные методы для работы как Deque:
list.addFirst("A"); // O(1) — добавление в начало
list.addLast("Z"); // O(1) — добавление в конецСложность: O(1) — прямое изменение first/last.
➖ removeFirst() / removeLast()
list.removeFirst(); // O(1) — удаление первого
list.removeLast(); // O(1) — удаление последнего
Сложность: O(1) — прямой доступ к first/last.
⚖️ Важные нюансы
LinkedList может работать как двусторонняя очередь. Идеально для реализации стеков и очередей.
Каждый узел требует ~24 байта overhead (на 64-bit JVM):
— 12 байт заголовок объекта
— 8 байт ссылка item
— 8 байт ссылка next
— 8 байт ссылка prev
— Padding до 8 байт
Для списка из 1000 элементов overhead ~24 КБ против ~4 КБ у ArrayList.
LinkedList НЕ реализует RandomAccess marker interface. Это сигнал для алгоритмов использовать итераторы вместо get(i).
// ❌ Медленно — O(n²)
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
}
// ✅ Быстро — O(n)
for (String s : list) {
// iterator используется автоматически
}
ListIterator позволяет двунаправленный обход и модификацию.
LinkedList допускает null значения.
→ Частые вставки/удаления с концов.
→ Модификация при итерации.
→ Неизвестный размер с частым ростом (Нет overhead на расширение массива как у ArrayList).
Ставьте 🔥, если хотите ещё разбор.
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍4❤1
🔥 Устал каждый раз городить велосипед для Telegram-ботов на Spring Boot?
Новый готовый Spring Boot Starter решает именно эту боль: минимальная конфигурация, понятный pipeline обработки апдейтов, маршрутизация, обработка ошибок и простая интеграция в Spring-экосистему — всё из коробки.
Архитектура разделяет приём апдейтов (Ingress), Delivery, Interceptor, Dispatcher и Router/Handler, а также даёт готовые хуки расширения и обработки нестандартных сценариев.
🔗 Подробнее в статье
🔹 Курс «Алгоритмы и структуры данных»
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib
🐸 Библиотека джависта
#CoreJava
Новый готовый Spring Boot Starter решает именно эту боль: минимальная конфигурация, понятный pipeline обработки апдейтов, маршрутизация, обработка ошибок и простая интеграция в Spring-экосистему — всё из коробки.
Архитектура разделяет приём апдейтов (Ingress), Delivery, Interceptor, Dispatcher и Router/Handler, а также даёт готовые хуки расширения и обработки нестандартных сценариев.
🔹 Курс «Алгоритмы и структуры данных»
🔹 Получить консультацию менеджера
🔹 Сайт Академии 🔹 Сайт Proglib
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥3❤1
List.of() появился в Java 9 и произвёл революцию в создании коллекций. Но под капотом это не просто обёртка над ArrayList — это целая иерархия оптимизированных классов.
📦 Сигнатура и варианты
Java предоставляет 12 перегруженных методов:
// Специализированные версии для 0-10 элементов
static <E> List<E> of()
static <E> List<E> of(E e1)
static <E> List<E> of(E e1, E e2)
// ... до 10 параметров
static <E> List<E> of(E e1, E e2, ..., E e10)
// Varargs для 11+ элементов
static <E> List<E> of(E... elements)
Зачем столько перегрузок?
— Избежать overhead создания varargs массива
— Каждая перегрузка возвращает оптимизированный класс
— Специализация для частых случаев (1-2 элемента)
→ Разные классы для разного размера:
// 0 элементов — singleton
static <E> List<E> of() {
return ImmutableCollections.EMPTY_LIST; // Переиспользуется!
}
// 1 элемент — List12 (List of 1 or 2)
static <E> List<E> of(E e1) {
return new ImmutableCollections.List12<>(e1);
}
// 2 элемента — тот же List12
static <E> List<E> of(E e1, E e2) {
return new ImmutableCollections.List12<>(e1, e2);
}
// 3+ элементов — ListN
static <E> List<E> of(E e1, E e2, E e3) {
return ImmutableCollections.listFromTrustedArray(e1, e2, e3);
}
→ Структура List12 (для 1-2 элементов):
static final class List12<E> extends AbstractImmutableList<E> {
private final E e0; // Всегда присутствует
private final E e1; // null если один элемент
List12(E e0) {
this.e0 = Objects.requireNonNull(e0);
this.e1 = null;
}
List12(E e0, E e1) {
this.e0 = Objects.requireNonNull(e0);
this.e1 = Objects.requireNonNull(e1);
}
public int size() {
return e1 != null ? 2 : 1;
}
public E get(int index) {
if (index == 0) return e0;
else if (index == 1 && e1 != null) return e1;
throw new IndexOutOfBoundsException();
}
}→ Структура ListN (для 3+ элементов):
static final class ListN<E> extends AbstractImmutableList<E> {
private final E[] elements; // Хранит все элементы
ListN(E... elements) {
// Копирование + null-check каждого элемента
this.elements = elements.clone();
for (E e : this.elements) {
Objects.requireNonNull(e);
}
}
public int size() {
return elements.length;
}
public E get(int index) {
return elements[index];
}
}1. Null элементы запрещены.
2. Varargs создаёт массив.
3. Не Serializable.
4. SubList тоже immutable.
📊 Performance бенчмарк
→ Создание списка из 3 элементов:
List.of("A", "B", "C"): ~5 ns
Arrays.asList("A", "B", "C"): ~8 ns
new ArrayList<>(Arrays.asList(...)): ~25 ns
List.of() в 5 раз быстрее ArrayList!→ Memory footprint:
List.of(1, 2, 3): 48 байт
Arrays.asList(1, 2, 3): ~72 байт (backing array + wrapper)
new ArrayList<>(3 items): ~112 байт (default capacity 10)
List.of() в 2.3 раза компактнее!
🔗 Документация
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16❤4🔥1
👀 Внутреннее устройство PriorityQueue
PriorityQueue — это реализация интерфейса Queue на основе двоичной кучи. Элементы всегда упорядочены по приоритету, минимальный элемент всегда в голове.
📦 Базовая структура
PriorityQueue использует min-heap реализацию:
Главные особенности:
→ O(log n) для add и poll (вставка/удаление с перестройкой).
→ O(1) для peek (доступ к минимуму).
→ O(n) для remove(Object) и contains.
→ Не гарантирует полную сортировку, только min в голове.
→ Не потокобезопасна (для concurrent используйте PriorityBlockingQueue).
🔍 Как устроено хранение
Binary Heap — complete binary tree в массиве
Свойство min-heap:
Каждый родитель ≤ своих детей. Это НЕ полная сортировка — только гарантия минимума в корне.
⚡️ Операции добавления и удаления
➕ add(E element) / offer(E element) — добавление
1. Элемент добавляется в конец массива: queue[size++] = element.
2. Вызывается siftUp(size-1) для восстановления heap property:
— Элемент "всплывает" вверх, пока не станет больше родителя.
— Сравнения через Comparator или Comparable.
Алгоритм siftUp:
Сложность: O(log n) — высота дерева.
➖ poll() — удаление минимального элемента
1. Сохраняется минимум: E result = queue[0].
2. Последний элемент перемещается в корень: queue[0] = queue[--size].
3. Вызывается siftDown(0) для восстановления heap:
— Элемент "тонет" вниз, меняясь с меньшим ребёнком.
Алгоритм siftDown:
Сложность: O(log n).
🔎 peek() — доступ к минимуму
Сложность: O(1) — прямой доступ к корню.
🔍 contains(Object o) — проверка наличия
1. Линейный поиск по массиву queue[].
2. Сравнение через equals().
Сложность: O(n) — требуется обход массива.
➖ remove(Object o) — удаление элемента
1. Линейный поиск элемента: O(n).
2. Удаление найденного:
— Перемещение последнего элемента на место удаляемого.
— siftDown() или siftUp() для восстановления heap.
Сложность: O(n) поиск + O(log n) восстановление = O(n).
⚖️ Важные нюансы
1️⃣ Comparator vs Comparable
Элементы должны быть сравниваемыми. Без Comparator/Comparable → ClassCastException.
2️⃣ НЕ полностью отсортирована
Heap гарантирует только минимум в корне.
3️⃣ Iterator не гарантирует порядок
Для sorted iteration используйте poll() в цикле:
4️⃣ Null элементы НЕ допускаются
✅ Подходит
— Нужен доступ к минимуму/максимуму
— Динамическая сортировка
— Алгоритмы с приоритетами
— Top K проблема
🔗 Документация: JavaDoc (Java 17)
Ставьте 🔥, если хотите ещё разбор.
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#CoreJava
PriorityQueue — это реализация интерфейса Queue на основе двоичной кучи. Элементы всегда упорядочены по приоритету, минимальный элемент всегда в голове.
📦 Базовая структура
PriorityQueue использует min-heap реализацию:
public class PriorityQueue<E> {
transient Object[] queue; // массив-куча
private int size = 0;
private final Comparator<? super E> comparator;
private static final int DEFAULT_INITIAL_CAPACITY = 11;
}Главные особенности:
→ O(log n) для add и poll (вставка/удаление с перестройкой).
→ O(1) для peek (доступ к минимуму).
→ O(n) для remove(Object) и contains.
→ Не гарантирует полную сортировку, только min в голове.
→ Не потокобезопасна (для concurrent используйте PriorityBlockingQueue).
Binary Heap — complete binary tree в массиве
PriorityQueue с элементами: [2, 5, 8, 9, 12, 10, 15]
Куча (min-heap):
2
/ \
5 8
/ \ / \
9 12 10 15
Массив queue[]:
[2, 5, 8, 9, 12, 10, 15]
0 1 2 3 4 5 6
Индексация:
- Родитель узла i: (i - 1) / 2
- Левый ребёнок узла i: 2 * i + 1
- Правый ребёнок узла i: 2 * i + 2
Свойство min-heap:
Каждый родитель ≤ своих детей. Это НЕ полная сортировка — только гарантия минимума в корне.
➕ add(E element) / offer(E element) — добавление
1. Элемент добавляется в конец массива: queue[size++] = element.
2. Вызывается siftUp(size-1) для восстановления heap property:
— Элемент "всплывает" вверх, пока не станет больше родителя.
— Сравнения через Comparator или Comparable.
Алгоритм siftUp:
void siftUp(int k) {
E x = queue[k];
while (k > 0) {
int parent = (k - 1) >>> 1; // деление на 2
E e = queue[parent];
if (compare(x, e) >= 0) break;
queue[k] = e; // родитель опускается
k = parent; // поднимаемся выше
}
queue[k] = x; // вставляем элемент
}Сложность: O(log n) — высота дерева.
➖ poll() — удаление минимального элемента
1. Сохраняется минимум: E result = queue[0].
2. Последний элемент перемещается в корень: queue[0] = queue[--size].
3. Вызывается siftDown(0) для восстановления heap:
— Элемент "тонет" вниз, меняясь с меньшим ребёнком.
Алгоритм siftDown:
void siftDown(int k) {
E x = queue[k];
int half = size >>> 1; // size / 2
while (k < half) {
int child = (k << 1) + 1; // левый ребёнок
E c = queue[child];
int right = child + 1;
if (right < size && compare(c, queue[right]) > 0) {
child = right; // правый меньше
c = queue[child];
}
if (compare(x, c) <= 0) break;
queue[k] = c; // ребёнок поднимается
k = child; // опускаемся ниже
}
queue[k] = x;
}Сложность: O(log n).
🔎 peek() — доступ к минимуму
E peek() {
return (size == 0) ? null : (E) queue[0];
}Сложность: O(1) — прямой доступ к корню.
🔍 contains(Object o) — проверка наличия
1. Линейный поиск по массиву queue[].
2. Сравнение через equals().
Сложность: O(n) — требуется обход массива.
➖ remove(Object o) — удаление элемента
1. Линейный поиск элемента: O(n).
2. Удаление найденного:
— Перемещение последнего элемента на место удаляемого.
— siftDown() или siftUp() для восстановления heap.
Сложность: O(n) поиск + O(log n) восстановление = O(n).
⚖️ Важные нюансы
Элементы должны быть сравниваемыми. Без Comparator/Comparable → ClassCastException.
Heap гарантирует только минимум в корне.
Для sorted iteration используйте poll() в цикле:
✅ Подходит
— Нужен доступ к минимуму/максимуму
— Динамическая сортировка
— Алгоритмы с приоритетами
— Top K проблема
Ставьте 🔥, если хотите ещё разбор.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11👍3❤1
Часто выбор коллекции ограничивается только таблицей сложностей и на этом всё.
Но реальный кейс сложнее: средняя сложность ≠ реальная скорость в продакшне. JVM, кэш процессора, GC и паттерны доступа могут радикально поменять картину.
🔑 Главная мысль
Выбирайте коллекцию под сценарий использования, а не “по самой быстрой ячейке в таблице”.
ArrayList хранит элементы в массиве → локальность памяти + CPU кэш → итерации летят.
Вставка в середину за O(n), но при небольших списках разница с LinkedList исчезающе мала.
🔧 Паттерн использования:
— 90% чтение, редкие вставки → идеально.
— Если заранее известно примерное кол-во элементов → задайте initialCapacity, иначе ArrayList будет несколько раз пересоздавать массив (copy O(n) на каждом росте).
В бенчмарках JMH даже при вставке в середину ArrayList часто быстрее LinkedList просто потому, что LinkedList платит за “pointer chasing” (скачки по памяти, cache-miss).
Да, вставка/удаление в начало или конец за O(1).
Но get(i) = O(n), и каждый шаг = новый объект, новая ссылка → нагрузка на GC.
🔧 Паттерн использования:
— Когда нужна двусторонняя очередь с частыми удалениями/добавлениями в начало и конец.
— Во всех остальных случаях лучше ArrayDeque, он без лишних объектов и быстрее почти всегда.
LinkedList ест больше памяти: на каждый элемент два указателя + объект-узел.
HashMap даёт O(1) доступ при хорошем hashCode().
Но:
— Если хэши “плохие” → коллизии → O(log n)
— При достижении load factor 0.75 → resize → перераспределение всех бакетов (дорогая операция).
🔧 Паттерн использования:
— Когда нужен быстрый поиск по ключу без сохранения порядка или когда важно хранить уникальные элементы или строить словари/кэши по ключу.
— Если знаете примерное кол-во элементов → сразу задайте кол-во элементов в конструкторе new HashMap<>(N).
Начиная с Java 8 при коллизии, когда LinkedList становится длинным (по умолчанию ≥ 8 элементов) → список превращается в красно-чёрное дерево.
Дают O(log n) доступ и всегда хранят ключи отсортированными.
Но если сортировка нужна редко, дешевле собрать HashMap и вызвать sorted() на стриме.
🔧 Паттерн использования:
— Когда важно поддерживать сортировку на каждой операции (напр. Top-N задач в приоритетной очереди).
— Не храните mutable-ключи, т.к. можно “потерять” элемент при изменении поля, участвующего в compareTo.
TreeMap хранит узлы с балансировкой (красно-чёрное дерево) → накладные расходы на память + сравнения ключей.
LinkedHashMap поддерживает порядок вставки или порядок доступа (accessOrder=true).
Можно сделать LRU-кэш, переопределив removeEldestEntry.
🔧 Паттерн использования:
— Когда важен порядок, но сортировка не нужна.
— Когда нужно легко реализовать ограниченный кэш.
Каждый get() в режиме accessOrder вызывает перестановку в двусвязном списке → небольшие накладные расходы.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava #лучшее2025
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16❤3🔥2👏1
Механизм сборки мусора в JVM — это не просто “магия, которая чистит память”, а сложная система, работающая по поколениям, фазам и стратегиям.
Понимание его внутренней архитектуры важно, если вы хотите управлять производительностью, избегать утечек и эффективно настраивать параметры JVM.
🔹 Архитектура: как устроена куча (Heap)
Куча памяти делится на поколения:
Heap
├── Young Generation
│ ├── Eden Space
│ └── Survivor Spaces (S0, S1)
└── Old Generation (Tenured)
Eden — вновь созданные объекты.
Survivor — те, кто “выжил” после первой сборки.
Old Gen — объекты, пережившие несколько сборок, считаются “долгоживущими”.
Дополнительно есть Metaspace (с Java 8), где хранятся данные о классах.
🔹 Алгоритм работы GC: по фазам
1. Mark
GC начинает с “корневых” ссылок (стек, глобальные переменные) и помечает все достижимые объекты.
2. Sweep
Удаляются все немаркированные объекты — они считаются “мертвыми”.
3. Compact (в некоторых GC)
Уплотнение памяти: “живые” объекты перемещаются ближе друг к другу, чтобы избежать фрагментации.
🔹 Типы сборок
1. Minor GC
Запускается при заполнении Eden. Очищаются только молодые поколения. Быстро, но может происходить часто.
2. Major GC / Full GC
Включает Old Gen и Metaspace. Дорогая операция, может “заморозить” все потоки (stop-the-world pause).
🔹 Типы сборщиков и их принципы
— Serial GC: однопоточная сборка. Просто и медленно.
— Parallel GC: многопоточная сборка всех поколений. Высокая пропускная способность.
— G1 GC: делит кучу на регионы, параллельно собирает “Region Set”. Поддерживает предсказуемые паузы.
— ZGC: целиком конкурентный сборщик. Работает с огромными кучами (до терабайта), паузы <10 мс.
— Shenandoah: минимальные паузы за счёт почти полной конкуренции с пользовательскими потоками.
🔹 Как GC определяет, что объект мёртв?
GC не использует reference count. Он строит граф достижимости:
1. Начинает с “корней” (GC roots)
2. Если оттуда нельзя добраться до объекта — он считается мусором
3. Это позволяет избежать утечек при циклических ссылках
🔹 Советы по оптимизации
— Избегайте долгоживущих ссылок (static, ThreadLocal) без необходимости
— Используйте WeakReference, если хотите избежать удержания объекта GC
— Кэшируйте объекты осознанно — утечка через Map может быть незаметной
— Задавайте лимиты памяти (-Xms512m -Xmx1024m)
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava #лучшее2025
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥4❤2👏2
💼⌛️ ТОП-5 причин, почему программист не может долго найти работу
Почему некоторые разработчики остаются "между работами" месяцы?
Не всегда дело в нехватке вакансий или «рынок просел». Часто дело в подходе к поиску проекта. Вроде бы есть опыт, стек, даже pet-проекты, но офферов всё нет.
Часто корень проблемы — неумение продать себя правильно. Отказ выполнять тестовые задания, считая их ненужными или обидными. Кроме того, нежелание рассматривать стажировки как стартовую площадку для получения опыта и расширения профессиональных связей также может замедлить процесс трудоустройства. И это далеко не все возможные причины.
🔗 Подробнее в статье
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#CoreJava #лучшее2025
Почему некоторые разработчики остаются "между работами" месяцы?
Не всегда дело в нехватке вакансий или «рынок просел». Часто дело в подходе к поиску проекта. Вроде бы есть опыт, стек, даже pet-проекты, но офферов всё нет.
Часто корень проблемы — неумение продать себя правильно. Отказ выполнять тестовые задания, считая их ненужными или обидными. Кроме того, нежелание рассматривать стажировки как стартовую площадку для получения опыта и расширения профессиональных связей также может замедлить процесс трудоустройства. И это далеко не все возможные причины.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava #лучшее2025
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5😁3🔥2👏1
📈 Как «ленивая разработка» захватывает IT-рынок
Пока мы выстраиваем архитектуру, пишем тесты и спорим о лучших практиках, рынок всё активнее обживают те, кто вообще не пишет код. Low-code и no-code решения не просто живы — они становятся нормой для бизнеса.
Порог входа минимальный, скорость разработки — бешеная, а заказчику всё равно, написано ли это на Java или накликано в визуальном редакторе. Вопрос: как долго останется актуальной классическая разработка?
🔗 Подробнее в статье
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#CoreJava #лучшее2025
Пока мы выстраиваем архитектуру, пишем тесты и спорим о лучших практиках, рынок всё активнее обживают те, кто вообще не пишет код. Low-code и no-code решения не просто живы — они становятся нормой для бизнеса.
Порог входа минимальный, скорость разработки — бешеная, а заказчику всё равно, написано ли это на Java или накликано в визуальном редакторе. Вопрос: как долго останется актуальной классическая разработка?
🔗 Подробнее в статье
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava #лучшее2025
Please open Telegram to view this post
VIEW IN TELEGRAM
😁11🥱3👍1🔥1🤔1
⚡️ Параллельные стримы: ускорение или нет?
Java предоставляет мощный инструмент для обработки данных — параллельные стримы. Они позволяют автоматически распределять вычисления по нескольким потокам, но их эффективность зависит от множества факторов.
Добавление parallelStream() бездумно — это не "оптимизация", а лотерея с шансом на баги и падение.
❌ Когда не использовать
— При небольшом наборе данных (<10 000 элементов) затраты на управление потоками могут превышать прирост скорости.
— Операции sorted(), distinct() или limit() требуют полного знания данных, что снижает эффективность параллельного выполнения.
— Вложенные parallelStream() в CompletableFuture или ExecutorService могут привести к конкуренции за ресурсы и неожиданному падению производительности.
✔️ Когда использовать
— Обработка больших объёмов данных (100 000+ элементов).
— Операции независимы и ресурсоёмки, например, сложные вычисления, парсинг файлов, загрузка данных из сети.
🔍 Важная особенность
parallelStream() использует ForkJoinPool.commonPool(). Если есть другие задачи, использующие этот же пул, они могут начать конкурировать за потоки, замедляя всё приложение.
💬 Делитесь в комментах интересными кейсами
══════ Навигация ══════
Вакансии • Задачи • Собесы
🐸 Библиотека джависта
#CoreJava #лучшее2025
Java предоставляет мощный инструмент для обработки данных — параллельные стримы. Они позволяют автоматически распределять вычисления по нескольким потокам, но их эффективность зависит от множества факторов.
Добавление parallelStream() бездумно — это не "оптимизация", а лотерея с шансом на баги и падение.
— При небольшом наборе данных (<10 000 элементов) затраты на управление потоками могут превышать прирост скорости.
— Операции sorted(), distinct() или limit() требуют полного знания данных, что снижает эффективность параллельного выполнения.
— Вложенные parallelStream() в CompletableFuture или ExecutorService могут привести к конкуренции за ресурсы и неожиданному падению производительности.
— Обработка больших объёмов данных (100 000+ элементов).
— Операции независимы и ресурсоёмки, например, сложные вычисления, парсинг файлов, загрузка данных из сети.
parallelStream() использует ForkJoinPool.commonPool(). Если есть другие задачи, использующие этот же пул, они могут начать конкурировать за потоки, замедляя всё приложение.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava #лучшее2025
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥2❤1👏1
Kafka транзакции не панацея, а узкоспециализированный инструмент.
🔹 Что гарантируют Kafka транзакции:
— Атомарную публикацию в несколько топиков (все сообщения видны или ни одно).
— Связывание read → process → write в одну операцию для консьюмеров-продюсеров.
🔹 Что НЕ гарантируют:
— Откат изменений во внешних системах (БД, API, email).
— Защиту от дубликатов, если downstream-сервисы не настроены правильно.
🔹 Два реальных сценария использования транзакций:
Отправка события обработки заказа и событие в audit-топик с временной меткой. Если сервис упадёт между отправками, то при повторе время не совпадёт — история будет некорректной.
Транзакция гарантирует, что оба сообщения либо станут видимы консьюмерам одновременно, либо ни одно из них не будет доставлено.
Если сервис упадёт после отправки сообщений, но до коммита оффсета, downstream-сервисы увидят дубликаты.
Транзакции связывают отправку и коммит в одну атомарную операцию.
Транзакции добавляют накладные расходы и подходят только для специфических кейсов.
══════ Навигация ══════
Вакансии • Задачи • Собесы
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤3🔥1👏1