Библиотека джависта | Java, Spring, Maven, Hibernate
23.5K subscribers
2.17K photos
44 videos
45 files
3.06K links
Все самое полезное для Java-разработчика в одном канале.

Список наших каналов: https://t.me/proglibrary/9197

Для обратной связи: @proglibrary_feeedback_bot

По рекламе: @proglib_adv

РКН: https://gosuslugi.ru/snet/67a5bbda1b17b35b6c1a55c4
Download Telegram
👀 Внутреннее устройство ConcurrentSkipListMap

ConcurrentSkipListMap — это потокобезопасная реализация NavigableMap из пакета java.util.concurrent.

В отличие от ConcurrentHashMap, эта структура поддерживает упорядоченность элементов и основана на вероятностной структуре данных Skip List (список с пропусками).

📦 Базовая структура

Внутри ConcurrentSkipListMap использует многоуровневый связанный список Skip List (см. фото).

Главная особенность:

— Элементы отсортированы по ключу (естественный порядок или Comparator).

— Несколько уровней индексации для быстрого поиска.

— Lock-free операции чтения через CAS (Compare-And-Swap).

— Минимальные блокировки только при изменении структуры.

🔍 Как устроено хранение

Базовые компоненты:

▪️ Node<K,V> — узел нижнего уровня.
▪️ Index<K,V> — узел индексного уровня.
▪️ HeadIndex<K,V> — голова уровня, хранит высоту.

Принцип работы

— Данные хранятся только на нижнем уровне (в Node).

— Верхние уровни содержат Index — указатели для быстрого прыжка.

— При вставке элемента случайно определяется высота (вероятность ~50% для каждого уровня).

— Максимальная высота ограничена 64 уровнями.

⚡️ Операции поиска, вставки и удаления

🔎 get(K key) — поиск

1. Начинается с верхнего уровня HeadIndex.
2. Движется вправо, пока ключ не станет больше искомого.
3. Спускается на уровень ниже.
4. Повторяет до нижнего уровня → O(log n).
5. Без блокировок, читает volatile-ссылки.

put(K key, V value) — вставка

1. Выполняется поиск позиции (как в get).
2. Если ключ существует, обновляется через CAS → O(log n).
3. Если новый:
— Создаётся новый Node.
— Вставляется в нижний уровень с CAS.
— Случайно генерируется высота (геометрическое распределение).
— Создаются Index-узлы для верхних уровней.
— Связываются с соседями через CAS.
4. При необходимости увеличивается высота всей структуры.

Блокировки: минимальны, только при изменении указателей через CAS-retry loops.

remove(Object key) — удаление

1. Находится узел на нижнем уровне.
2. Помечается как удалённый (marker node) через CAS.
3. Физически отсоединяется от списка.
4. Индексы на верхних уровнях удаляются постепенно (ленивое удаление).
5. Если верхние уровни опустели, высота уменьшается.

⚖️ Важные нюансы

1. Вероятностная балансировка

В отличие от AVL/Red-Black деревьев, балансировка достигается случайной высотой узлов.
Нет гарантии идеального баланса, но ожидаемая сложность O(log n).

2. Null и сравнения

Null ключи запрещены (NullPointerException).
Null значения допустимы (в отличие от ConcurrentHashMap).
Ключи должны быть Comparable или нужен Comparator.

3. Memory Consistency

Гарантии happens-before для put/get одного и того же ключа.
Weakly consistent bulk операции (putAll, clear).

✔️ Полезно для

— Упорядоченных concurrent-карт: логи по времени, рейтинги, приоритетные кэши.

— Range-запросы: subMap(from, to), headMap(to), tailMap(from).

— Навигация: firstEntry(), lastEntry(), floorEntry(key), ceilingEntry(key).

— Concurrent sorted set: keySet() даёт ConcurrentNavigableSet.

— Когда нужен порядок + потокобезопасность без глобальных блокировок.

🔗 Документация: JavaDoc (Java 17)

Ставьте 🔥, если хотите ещё разбор.

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12👍42
🤡 Неожиданный холиварчик

Наткнулся недавно на пост про винду, очень удивился, что после закрытия поддержки десятки народ массово переходит обратно на семёрку 😳

Думал будут прыгать на одиннадцатую или *nix системы.

💬 Какая ОС у вас на личном компе?

🐸 Библиотека джависта

#DevLife
Please open Telegram to view this post
VIEW IN TELEGRAM
😁8👍1🔥1
Как между собой связаны Iterable, Iterator и цикл for-each?

Iterable — это интерфейс с единственным методом iterator(), который возвращает объект Iterator. Любой класс, реализующий Iterable, может использоваться в цикле for-each.

Iterator — это интерфейс для последовательного обхода элементов коллекции. Содержит методы hasNext(), next() и remove().

Цикл for-each — это синтаксический сахар. Компилятор автоматически преобразует его в вызов iterator() и работу с Iterator.

🐸 Библиотека собеса по Java

#core
Please open Telegram to view this post
VIEW IN TELEGRAM
👍133🔥1🤔1
🎁 Конкурс от Proglib Academy!

Кстати, если кто-то ещё не в курсе — у нас тут раздают MacBook Pro 14.
Да-да, не шутка, настоящий, железный, с M3 Pro

Но! Чтобы успеть пройти 2 недели обучения к 15 ноября, курс нужно взять до конца октября — и сейчас на всё скидка 40%.

Чтобы поучаствовать, нужно:

1️⃣ Покупаешь любой курс до конца октября;
2️⃣ Проходишь 2 недели обучения к 15 ноября;
3️⃣ Написать куратору в чат #розыгрыш.

До 15 ноября, потом всё — поезд (и макбук) уйдёт.

👉 Участвовать в розыгрыше
Сохраняйте шпаргалку по командам git

🐸 Библиотека джависта

#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
👍123🔥2
🔍 Просто о сложном: Virtual Threads

В Java 21 появились Virtual Threads — легковесные потоки, которые позволяют писать синхронный код с производительностью асинхронного.

По сути, это JVM-управляемые потоки (а не OS-потоки), которых можно создавать миллионы без значительных затрат памяти. Они решают главную проблему масштабирования традиционных потоков.

🔹 Зачем они нужны

Классические Platform Threads имеют проблемы:

— дорогие в создании (~1MB стека на поток);
— ограничены числом (~тысячи потоков максимум);
— при блокировке (IO, sleep) поток простаивает, занимая ресурсы.

Virtual Threads весят ~1KB, создаются мгновенно, и при блокировке платформенного потока освобождается для других задач. Это позволяет обрабатывать миллионы конкурентных запросов.

🔹 Ключевые моменты

▪️ Thread.ofVirtual() — создание билдера для virtual thread.
▪️ Thread.startVirtualThread() — быстрый старт задачи.
▪️ Executors.newVirtualThreadPerTaskExecutor() — пул для каждой задачи создаёт новый VT.
▪️ Автоматическое отсоединение от платформенного потока при блокировке (IO, sleep, wait, park).
▪️ Присоединение к платформенным потокам из ForkJoinPool.

🔹 Под капотом

Когда виртуальный поток блокируется (например, на IO), он "отцепляется" от платформенного потока. Освободившийся платформенный поток берёт другой готовый виртуальный поток. Когда операция завершается, виртуальный поток "подцепляется" обратно к доступному платформенному потоку.

🔹 Подводные камни

— Pinning (закрепление)

Virtual thread может "застрять" на платформенном потоке при:
• Synchronized блоках
• Нативных методах (JNI)

В таких случаях carrier thread блокируется вместе с virtual thread. Решение: использовать ReentrantLock вместо synchronized.

— ThreadLocal может быть опасен

Миллионы virtual threads с ThreadLocal приведут к огромному потреблению памяти. Используйте ScopedValue (preview feature в Java 21+).

— Не подходит для CPU-bound задач

Virtual threads оптимизированы для IO-bound операций. Для вычислений лучше параллельные стримы или ForkJoinPool.

— Мониторинг
Стандартные инструменты мониторинга потоков могут показывать некорректные данные — они заточены под platform threads.

✔️ Когда использовать

— Высоконагруженные web-серверы с множеством конкурентных запросов.
— Микросервисы с большим количеством внешних вызовов (HTTP, БД).
— Когда нужна простота синхронного кода без сложности реактивного.
— Замена больших thread pools для IO-операций.

Не подходит:

— CPU-intensive вычисления (сортировки, криптография).
— Код с большим количеством synchronized блоков (pinning).
— Легаси-код с активным использованием ThreadLocal.

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥52💯1
Код-ревью Java-проекта прямо в браузере, как в IDE? Похоже на правду! 👀

Джависты, внимание. Платформа SourceCraft выкатила кодонавигацию для Java, которая работает прямо в веб-интерфейсе. Можно открыть любой класс и одним кликом перейти к декларации метода, увидеть все его использования (Find Usages), посмотреть историю изменений по коммитам. 🔥

Это дико удобно при ревью или анализе больших проектов — не нужно клонировать репо и настраивать IDE. Всё работает из браузера. Для команд это реальная экономия времени. По сути, полноценное code-review пространство со встроенными IDE-инструментами.
👍9🔥1
🎃 Хэллоуин в Proglib Academy: скидки, призы и... немного паники

Сегодня 31 октября, и это не просто время тыкв и призраков, это ПОСЛЕДНИЙ ДЕНЬ, когда ты можешь выиграть макбук!

→ Купи любой курс со скидкой 40% 💸
→ Начни обучение, чтобы пройти 2 недели к 15 ноября 🎓
→ Напиши куратору #розыгрыш ✍️

Всё! Теперь ты в игре.

👉 Сейчас или никогда!
😁1
🎭 LangChain4j + Spring Boot

Забудьте про «LLM в лоб». LangChain4j — это промышленный слой интеграции AI с Java-приложениями. Он превращает LLM в часть архитектуры, а не игрушку для экспериментов.

🧩 Вместо того чтобы вызывать модель напрямую, вы описываете цепочку: загрузка данных → векторизация → поиск → рассуждение → ответ. Всё это живёт в Spring-экосистеме, с DI, профилями, бинами и транзакциями.

📝 Промпт:

Generate a production-ready Spring Boot 3 + LangChain4j integration for enterprise RAG and AI agent workflows:

— Configure LangChain4j client with OpenAI or Ollama connector, API-key management, and retry policy.
— Implement document ingestion pipeline: PDF, Markdown, and web sources → chunking → embedding store (Postgres + pgvector).
— Add Retrieval-Augmented Generation chain with context ranking, token budget control, and caching.
— Implement conversational memory (message buffer + summary).
— Create multi-agent setup: retriever → reasoner → summarizer → verifier.
— Secure API credentials using Spring Config / Vault integration.
— Add async streaming responses (Server-Sent Events).
— Integrate observability: OpenTelemetry tracing, metrics with Micrometer + Prometheus.
— Add custom interceptors for logging prompts and responses.
— Provide JUnit tests for chain determinism and data isolation.
— Example endpoint: /ai/ask — retrieves context, runs reasoning chain, returns structured JSON answer.


💡 Расширения:

— добавьте Guardrails для валидации ответов и отказов от токсичного контента;
— настройте дашборд Grafana: latency, token usage, accuracy, fallback-rate.

🐸 Библиотека джависта

#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
👍41🔥1
✔️ Java-тест: Группировка строк по длине

Напишите метод для production-кода 👇

📦 Задание

Реализуйте метод, который группирует строки по их длине:

public Map<Integer, List<String>> groupByLength(List<String> strings) {}


Требования

— Обработать null и пустой список → вернуть пустую Map
— Игнорировать null-элементы в списке
— Порядок строк в группах сохраняется
— Пустые строки группировать с ключом 0

Ставьте → 🔥, если нравится формат. Если нет → 🤔

💬 Решения под спойлер. Сравним, какое будет лучше.

🐸 Библиотека собеса по Java

#practise
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥10👍2🥱21
☕️ Java && Coffee

Делитесь фотографиями выходных.

До Балкан тоже начинает потихоньку добираться осень 🍁

Отправляйте фото в комментарии👇🏻

🐸 Библиотека джависта

#DevLife
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
😍9🔥51👍1
👑 Магия IntelliJ IDEA: Refactor This

Когда рефакторите legacy-код или чистите архитектуру — приходится делать десятки преобразований. А вы знали, что в IDEA есть универсальное меню для всех рефакторингов?

🔹 Что делает

— Открывает контекстное меню со всеми доступными рефакторингами для текущего элемента
— Автоматически фильтрует опции в зависимости от контекста (переменная, метод, класс)
— Позволяет выбрать нужную операцию по цифре или первой букве

🔹 Зачем это нужно

— Не нужно помнить десятки отдельных хоткеев — один хоткей открывает все варианты
— Молниеносный доступ к редким, но мощным рефакторингам (Extract Parameter Object, Introduce Parameter, Pull Members Up)
— Безопасно переименовывает, перемещает и трансформирует код с учётом всех зависимостей
— Особенно полезно для сложных преобразований, которые вручную делать долго и опасно

🔹 Как использовать

— Поставьте курсор на переменную, метод, класс или любой элемент кода
— Нажмите Ctrl+Alt+Shift+T (Windows/Linux) или ⌃+T (macOS)
— Выберите нужный рефакторинг из списка: Extract Method, Rename, Change Signature, Inline и другие
— Настройте параметры → Enter → готово. IDEA сама обновит все использования

🐸 Библиотека джависта

#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
👍115🔥4
🔥 Как подключить MapStruct к проекту

MapStruct — это code-generation библиотека для автоматического маппинга между Java-объектами.

Работает на этапе компиляции, генерирует чистый код без рефлексии — в разы быстрее ModelMapper и Dozer.

1️⃣ Добавляем зависимости

Нужно добавить две зависимости: саму библиотеку mapstruct и процессор mapstruct-processor. Процессор отвечает за генерацию кода во время компиляции.

Критически важно правильно настроить maven-compiler-plugin, именно он запускает annotation processors. В секции annotationProcessorPaths прописываем mapstruct-processor, а если используете Lombok, добавляем его тоже плюс специальный binding для их совместной работы.

2️⃣ Создаём Mapper интерфейс

Создаёте обычный Java-интерфейс с аннотацией @Mapper(componentModel = "spring"). Параметр componentModel указывает, что MapStruct должен сгенерировать Spring-бин, который можно инжектить через конструктор.

В интерфейсе объявляете методы-конвертеры: например UserDto toDto(User user) или List<UserDto> toDtoList(List<User> users). MapStruct сам поймёт, какие поля во что маппить, если названия совпадают.

3️⃣ Настраиваем кастомный маппинг

Когда названия полей отличаются или нужна дополнительная логика, используйте аннотацию @Mapping:

▪️ source/target — для переименования полей: @Mapping(target = "fullName", source = "firstName")
▪️ expression — для Java-выражений: можно написать простую логику прямо в аннотации
▪️ ignore — чтобы игнорировать поле: @Mapping(target = "password", ignore = true)
▪️ dateFormat — для форматирования дат: автоматическая конвертация LocalDateTime в String
▪️ qualifiedByName — для вызова кастомных методов-конвертеров

4️⃣ Работа со вложенными объектами

MapStruct умеет автоматически маппить вложенные структуры. Если у вас в User есть поле Address, а в UserDto есть AddressDto — создайте отдельный AddressMapper и укажите его в параметре uses = {AddressMapper.class}.

MapStruct сам найдёт нужный маппер и применит его для вложенных объектов. Это работает рекурсивно для любой глубины вложенности.

5️⃣ Обновление существующих объектов

Часто нужно не создавать новый объект, а обновить существующий. Для этого используйте аннотацию @MappingTarget на втором параметре метода. MapStruct сгенерирует код, который обновит только нужные поля.

Добавьте @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) — тогда null-значения из DTO не затрут существующие данные в Entity.

6️⃣ Кастомные конвертеры

Для сложной логики добавьте в интерфейс default-методы. Пометьте их @Named("имя") и ссылайтесь через qualifiedByName. Например, для конвертации enum в русский текст или склейки нескольких полей.

Можно также создать отдельный класс с @Component и helper-методами, затем подключить его через параметр uses.

✔️ Что происходит под капотом

MapStruct анализирует ваш интерфейс на этапе компиляции, смотрит на типы полей, их названия и аннотации. Затем генерирует простой Java-код с прямым присвоением значений, никакой магии, рефлексии или proxy.

Сгенерированный код можно открыть и прочитать. Он выглядит так, будто вы написали его вручную. Это упрощает отладку и понимание происходящего.

🐸 Библиотека джависта

#Enterprise
Please open Telegram to view this post
VIEW IN TELEGRAM
👍75🔥2🥱1
👀 Внутреннее устройство LinkedHashSet

LinkedHashSet — это реализация интерфейса Set из пакета java.util, которая сохраняет порядок вставки элементов. Это гибрид между HashSet (быстрый поиск) и списком (предсказуемый порядок итерации).

📦 Базовая структура


LinkedHashSet — это тонкая обёртка над LinkedHashMap. Внутри:

— Все элементы хранятся как ключи в LinkedHashMap.

— Значения — константа PRESENT (заглушка Object).

— Порядок поддерживается через двусвязный список узлов.

Главная особенность:

— O(1) для add, remove, contains (как у HashSet).

— Предсказуемый порядок итерации (порядок вставки).

— Немного больше памяти, чем HashSet (~25% overhead на связи).

🔍 Как устроено хранение

LinkedHashSet полностью делегирует работу LinkedHashMap, а тот устроен так:
Entry<K,V> — узел хранения:
static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before; // предыдущий в порядке вставки
Entry<K,V> after; // следующий в порядке вставки
}
```

### **Двойная структура:**

1. **Хэш-таблица** (массив бакетов):
- Быстрый доступ по хэшу → O(1).
- Разрешение коллизий через цепочки/деревья.

2. **Двусвязный список**:
- Связывает все элементы в порядке добавления.
- Голова списка: `head` (первый добавленный).
- Хвост списка: `tail` (последний добавленный).

**Визуализация:**
```
Хэш-таблица:
Bucket[0]: null
Bucket[1]: Entry("B") ----→ Entry("F")
Bucket[2]: Entry("A")
Bucket[3]: Entry("C")

Двусвязный список (порядок вставки):
head → Entry("A") ⇄ Entry("B") ⇄ Entry("C") ⇄ Entry("F") ← tail


⚡️ Операции добавления, удаления и поиска

add(E element) — добавление

1. Вычисляется хэш элемента: hash(e).
2. Определяется индекс бакета: index = hash & (n-1).
3. Проверяется наличие элемента в бакете:
— если есть → возвращается false (дубликат).
— если нет → создаётся новый Entry.
4. Новый Entry:
— добавляется в бакет хэш-таблицы.
— связывается с tail в двусвязном списке.
— обновляется tail = newEntry.
5. При необходимости таблица расширяется (load factor > 0.75).

Сложность: O(1) в среднем.

🔎 contains(Object o) — проверка наличия

1. Вычисляется хэш объекта.
2. Проверяется соответствующий бакет.
3. Сравнивается через equals().
4. Двусвязный список НЕ используется для поиска.

Сложность: O(1) в среднем.

remove(Object o) — удаление

1. Находится Entry в хэш-таблице по хэшу.
2. Узел удаляется из бакета.
3. Узел отсоединяется от двусвязного списка.
4. Обновляются ссылки head/tail при необходимости.

Сложность: O(1) в среднем.

⚖️ Важные нюансы

1. Наследование от HashSet

Наследует поведение HashSet, но меняет внутреннюю реализацию. Конструкторы создают LinkedHashMap вместо HashMap.

2. Null элементы

Один null может быть добавлен (как в HashSet).

3. Не потокобезопасен

Для многопоточного доступа требуется внешняя синхронизация. Альтернатива: CopyOnWriteArraySet (но без хэш-таблицы).

4. Equals и hashCode

Сравнивает содержимое, игнорируя порядок:

5. Capacity и Load Factor

Начальные значения: Capacity 100, load factor 0.75

Начальная ёмкость должна учитывать ожидаемый размер.
При достижении threshold (capacity × load factor) происходит resize.

Не использовать если

1. Порядок не важен

Используйте HashSet — проще и немного быстрее (меньше overhead).

2. Нужна сортировка

Используйте TreeSet — автоматическая сортировка по Comparator/Comparable.

3. Многопоточный доступ

Используйте ConcurrentHashMap.newKeySet() или CopyOnWriteArraySet. Или оборачивайте: Collections.synchronizedSet().

4. Критична минимизация памяти:

HashSet использует меньше памяти (~20% экономии).

🔗 Документация: JavaDoc (Java 17)

Ставьте 🔥, если хотите разбор TreeSet!

🐸 Библиотека джависта

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9👍3🎉1