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

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

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

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

РКН: https://gosuslugi.ru/snet/67a5bbda1b17b35b6c1a55c4
Download Telegram
⚙️ Интеграция системы логирования с Logback в Spring Boot

Ищете, как настроить логирование в приложении на Spring Boot? Используйте Logback для эффективного логирования и управления уровнями логов. Используйте AI для ускорения процесса.

📝 Промпт:


Generate a logging system integration for a Spring Boot 3 application using Logback.

— Set up logback-spring.xml for flexible configuration of logging levels and appenders.
— Define rolling file appender to archive old log files based on size or date.
— Configure log format with PatternLayoutEncoder for structured and readable logs.
— Implement different logging levels (INFO, WARN, ERROR) for different components of the application.
— Integrate MDC (Mapped Diagnostic Context) for tracking user sessions or specific requests.
— Set up asynchronous logging with AsyncAppender to improve performance in high-traffic applications.
— Enable console logging for development and file logging for production environments.


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

— Добавьте Set up rolling file appender for log rotation based on size or date для архивирования старых логов.
— Добавьте Implement asynchronous logging with AsyncAppender для улучшения производительности в высоконагруженных приложениях.
— Добавьте Integrate custom appenders for external monitoring systems like Elasticsearch or Splunk для интеграции с внешними системами мониторинга.

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍72🔥1
🔍 Просто о сложном: что такое Garbage Collector (GC)?

Garbage Collector (GC) в Java — это механизм автоматического управления памятью, который отвечает за очистку памяти от объектов, которые больше не используются в программе. Вместо того, чтобы разработчик вручную освобождал память, как в некоторых других языках программирования, Java использует сборщик мусора, который делает это автоматически.

🔵 Как работает GC?

Когда вы создаёте объект в Java, он занимает место в куче (heap) — области памяти, предназначенной для динамического распределения. Однако по мере работы программы некоторые объекты становятся ненужными, и их можно удалить, чтобы освободить память для других задач. Это и есть основная задача GC — найти объекты, которые больше не используются, и освободить память.

🔵 Основные этапы работы GC

1. Маркировка. На первом этапе система анализирует объекты в куче и помечает те, на которые существуют ссылки, то есть которые всё ещё могут быть использованы в программе. Эти объекты называют живыми.

2. Сборка мусора. После маркировки GC удаляет объекты, которые не были помечены как «живые». Эти объекты больше не используются в программе и могут быть безопасно удалены, а занимаемое ими пространство освобождается.

3. Компактизация (Compaction). Иногда после удаления объектов в куче остаются фрагменты пустой памяти. В этом случае GC может перемещать объекты, чтобы устранить фрагментацию и сделать память более сплошной. Это улучшает использование доступных ресурсов.

🔵 Типы Garbage Collector в Java

Java предлагает несколько типов сборщиков мусора, каждый из которых имеет свои особенности и подходит для разных сценариев:

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

Parallel GC: этот сборщик использует несколько потоков для работы, что ускоряет процесс очистки. Он подходит для многозадачных приложений и приложений с большими объемами данных, где важно минимизировать время пауз.

CMS (Concurrent Mark-Sweep): сборщик мусора, который работает параллельно с основной программой, минимизируя паузы. Он использует несколько шагов для маркировки и уборки мусора, чтобы не блокировать выполнение приложения на долгое время.

G1 (Garbage First): один из самых современных сборщиков мусора. Он фокусируется на минимизации времени пауз и дает разработчикам больше контроля над процессом. G1 отлично подходит для больших приложений с высоким уровнем взаимодействия.

🔵 Паузы и их влияние на производительность

Одним из главных аспектов работы GC является Stop-the-World пауза, когда приложение временно приостанавливается, чтобы сборщик мусора очистил память. Хотя паузы в большинстве случаев довольно короткие, они могут заметно повлиять на производительность, особенно в приложениях с высокими требованиями к времени отклика.

🔵 Как улучшить производительность при работе с GC

Оптимизация размера кучи. Размер кучи можно настроить в зависимости от объема данных, с которым работает ваше приложение. Неправильно выбранный размер может привести к слишком частым или слишком редким сборкам мусора.

Использование правильного сборщика. Выбор сборщика мусора зависит от особенностей вашего приложения. Например, для приложений с требованием низкой задержки лучше использовать G1 или CMS.

Профилирование. Используйте инструменты профилирования, чтобы отслеживать, как работает GC в вашем приложении. Это поможет выявить проблемы и оптимизировать использование памяти.

🔵 Когда стоит задуматься о Garbage Collector

— Когда ваше приложение работает с большим количеством объектов, и необходимо следить за производительностью.
— Если заметны задержки или паузы, вызванные работой GC, и нужно оптимизировать работу с памятью.
— В сложных многозадачных или распределённых приложениях, где важно, чтобы GC не блокировал выполнение других задач.

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍111🔥1👏1
📈 Big-O ≠ производительность

Часто выбор коллекции ограничивается только таблицей сложностей и на этом всё.

Но реальный кейс сложнее: средняя сложность ≠ реальная скорость в продакшне. JVM, кэш процессора, GC и паттерны доступа могут радикально поменять картину.

🔑 Главная мысль

Выбирайте коллекцию под сценарий использования, а не “по самой быстрой ячейке в таблице”.

1️⃣ ArrayList — быстр в чтение, но не во вставке

ArrayList хранит элементы в массиве → локальность памяти + CPU кэш → итерации летят.
Вставка в середину за O(n), но при небольших списках разница с LinkedList исчезающе мала.

🔧 Паттерн использования:

— 90% чтение, редкие вставки → идеально.
— Если заранее известно примерное кол-во элементов → задайте initialCapacity, иначе ArrayList будет несколько раз пересоздавать массив (copy O(n) на каждом росте).

📌 Факт:

В бенчмарках JMH даже при вставке в середину ArrayList часто быстрее LinkedList просто потому, что LinkedList платит за “pointer chasing” (скачки по памяти, cache-miss).

2️⃣ LinkedList — звучит круто, но редко нужен

Да, вставка/удаление в начало или конец за O(1).
Но get(i) = O(n), и каждый шаг = новый объект, новая ссылка → нагрузка на GC.

🔧 Паттерн использования:

— Когда нужна двусторонняя очередь с частыми удалениями/добавлениями в начало и конец.
— Во всех остальных случаях лучше ArrayDeque, он без лишних объектов и быстрее почти всегда.

📌 Факт:

LinkedList ест больше памяти: на каждый элемент два указателя + объект-узел.

3️⃣ HashMap / HashSet — быстрые, пока не наступил resize

HashMap даёт O(1) доступ при хорошем hashCode().

Но:
— Если хэши “плохие” → коллизии → O(log n)
— При достижении load factor 0.75 → resize → перераспределение всех бакетов (дорогая операция).

🔧 Паттерн использования:

— Когда нужен быстрый поиск по ключу без сохранения порядка или когда важно хранить уникальные элементы или строить словари/кэши по ключу.
— Если знаете примерное кол-во элементов → сразу задайте кол-во элементов в конструкторе new HashMap<>(N).

📌 Факт:

Начиная с Java 8 при коллизии, когда LinkedList становится длинным (по умолчанию ≥ 8 элементов) → список превращается в красно-чёрное дерево.

4️⃣ TreeMap / TreeSet — порядок стоит денег

Дают O(log n) доступ и всегда хранят ключи отсортированными.
Но если сортировка нужна редко, дешевле собрать HashMap и вызвать sorted() на стриме.

🔧 Паттерн использования:

— Когда важно поддерживать сортировку на каждой операции (напр. Top-N задач в приоритетной очереди).
— Не храните mutable-ключи, т.к. можно “потерять” элемент при изменении поля, участвующего в compareTo.

📌 Факт:

TreeMap хранит узлы с балансировкой (красно-чёрное дерево) → накладные расходы на память + сравнения ключей.

5️⃣ LinkedHashMap — скрытый герой для кэшей

LinkedHashMap поддерживает порядок вставки или порядок доступа (accessOrder=true).
Можно сделать LRU-кэш, переопределив removeEldestEntry.

🔧 Паттерн использования:

— Когда важен порядок, но сортировка не нужна.
— Когда нужно легко реализовать ограниченный кэш.

📌 Факт:

Каждый get() в режиме accessOrder вызывает перестановку в двусвязном списке → небольшие накладные расходы.

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍229🔥4
💎 Как работает String.intern() под капотом

Многие знают, что строки в Java «живут в пуле». Но что реально происходит, когда мы вызываем intern()?

1️⃣ Что такое String Pool

Java хранит все строковые литералы в специальной области памяти — String Intern Pool. Зачем? Чтобы одинаковые строки не занимали память несколько раз.
String a = "hello";
String b = "hello";
System.out.println(a == b); // true


Обе ссылки указывают на одну и ту же строку из пула.

2️⃣ Как работает intern()

Если вызвать s.intern():

— JVM проверит, есть ли такая строка в пуле.
— Если есть, вернёт ссылку на неё.
— Если нет, добавит текущую строку в пул и вернёт ссылку.

Пример:
String x = new String("world");
String y = x.intern();
String z = "world";

System.out.println(x == z); // false
System.out.println(y == z); // true


3️⃣ Под капотом JVM

🔹 До Java 7 String Pool находился в PermGen → можно было легко словить OOM.
🔹 Начиная с Java 7 (HotSpot) пул перенесли в heap, что сильно упростило жизнь.
🔹 Реализация — ConcurrentHashMap внутри JVM, так что intern() потокобезопасен.

4️⃣ Где это полезно

🔹 Оптимизация памяти при большом количестве одинаковых строк (например, при парсинге XML/JSON).
🔹 Сравнение строк через == (только если они гарантированно interned).
🔹 При работе с ключами в больших мапах, чтобы уменьшить дубли.

5️⃣ Подводные камни

🔹 intern() не бесплатен — поиск в пуле и вставка стоят ресурсов.
🔹 Чрезмерное использование может привести к росту heap-а и GC-паузам.
🔹 Нельзя бездумно использовать вместо обычных строк → легко получить деградацию.

🔗 Документация: String.intern()

💬 Использовали когда-нибудь intern() в продакшене?

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍73🤔2🔥1
📌 Как пользоваться тегами в канале

Чтобы вам было проще ориентироваться в постах, мы разделили весь контент по 4 основным направлениям:

🔹 #CoreJava — фундаментальные знания: JVM, JDK, ООП, многопоточность, паттерны и базовые концепции. Всё, что помогает понимать Java глубже, а не просто «писать код, чтобы работало».

🔹 #Enterprise — прикладные инструменты и практика: Spring, Hibernate, Kafka, Docker, микросервисы. Всё, что встречается в работе разработчика каждый день.

🔹 #DevLife — сообщество и карьера: мемы, холивары, задачи с собесов, советы по развитию и личные рубрики. Всё, что создаёт атмосферу и объединяет нас как комьюнити.

🔹 #News — дайджесты, свежие анонсы, релизы и новости. А также реклама и инфоповоды, которые стоит знать.

👉 Используйте теги, чтобы быстро находить посты по интересующей теме.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9🔥3👏1
🔍 Просто о сложном: ORM в Java

ORM (Object-Relational Mapping) — это мост между объектами Java и реляционными базами данных. Вместо того, чтобы писать SQL-запросы руками, вы работаете с привычными объектами и методами.

🔹 Зачем нужен ORM

В Java идет работа с классами, полями, методами. В базе данных всё хранится в виде таблиц, строк и столбцов. ORM выступает «переводчиком»:

— Таблица ↔️ Класс
— Строка ↔️ Объект
— Столбец ↔️ Поле

ORM берёт на себя «грязную работу»:

1. При создании объекта в коде (new User("Alex")) и вызове save(), ORM формирует SQL-запрос INSERT INTO users (name) VALUES ('Alex') и отправляет его в базу.

2. Когда вы хотите достать данные (userRepository.findById(1)), ORM делает SELECT * FROM users WHERE id=1, создаёт объект User и наполняет его полями из результата запроса.

3. Если вы меняете поле (user.setName("Ivan")) и сохраняете, ORM сгенерирует UPDATE users SET name='Ivan' WHERE id=1.

4. Если объект больше не нужен и вы вызываете delete(), ORM сформирует DELETE FROM users WHERE id=1.

То есть вы оперируете объектами и методами, а ORM переводит ваши действия в SQL и обратно.

🔹 Плюсы ORM


— Меньше шаблонного кода. Не нужно постоянно писать INSERT, SELECT, UPDATE.

— Более читаемо. Идет работа с методами вроде userRepository.findByEmail(), а не с SQL запросами.

— Кросс-СУБД. ORM умеет подстраиваться под разные базы (PostgreSQL, MySQL, Oracle).

— Интеграция. Легко комбинируется со Spring и другими фреймворками.

🔹 Минусы ORM

— Иллюзия простоты. Кажется, что можно забыть про SQL, но «под капотом» всё равно генерируются запросы.

— Проблема N+1. Частая ошибка, когда ORM делает сотни мелких запросов вместо одного «жирного».

— Сложные кейсы. Для тяжёлой аналитики или оптимизации всё равно пишут чистый SQL или хранят процедуры.

— Производительность. ORM добавляет прослойку, которая может стать узким местом при больших нагрузках.

🔹 Типичные ловушки ORM

— Ленивая загрузка (Lazy Loading). Может неожиданно тянуть данные из БД в середине транзакции.

— Кэширование. ORM кэширует объекты, но если работать невнимательно, то легко получить «старые» данные.

— Транзакции. Многие забывают, что ORM не волшебная палочка: без грамотной работы с транзакциями данные могут остаться в полупрозрачном состоянии.

🔹 Основные инструменты в Java

— JPA (Java Persistence API). Стандарт, описывающий, как именно должны работать ORM-инструменты.

— Hibernate. Самый популярный провайдер JPA, фактический стандарт в экосистеме Java.

— EclipseLink, OpenJPA. Альтернативные реализации, реже используемые.

— Spring Data JPA. Надстройка над JPA, позволяющая писать репозитории и автогенерировать запросы из названий методов.

🔹 ORM или не ORM

✔️ ORM полезен, если:

— Приложение типовое (CRUD, REST API, веб-сервисы)
— Важна скорость разработки и поддерживаемость
— Команда не хочет тратить часы на ручные SQL-запросы

ORM мешает, если:

— Высоконагруженная система с миллионами записей
— Важен каждый миллисекундный отклик
— Есть сложная аналитика или отчёты (там SQL быстрее и прозрачнее)

📌 Итог

ORM — это «помощник», а не замена знаний SQL. Он снимает рутину и ускоряет разработку, но требует грамотного использования. Понимать, что происходит «под капотом», — ключ к тому, чтобы не наступать на грабли.

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍96🔥2
🎁 Задача на дизайн: сервис подарков

На выходных можно подумать о высоком и попрактиковать дизайн систем. Полезно для будущих собесов, а кому-то просто для тренировки. В реальной задаче функциональные и нефункциональные требования ещё нужно было бы уточнить, но в формате поста сразу добавили их.

🔹 Условие

Перед днём рождения коллеги выбирается ответственный, ему скидывают деньги, он что-то покупает, а потом надо решать, что делать со сдачей. Сервис должен:

— Позволять зарегистрированным пользователям скидываться в общую «копилку»
— Хранить средства до момента покупки
— Дать возможность выбрать товары из каталога маркетплейса
— После закрытия копилки — потратить средства на выбранное и вернуть остаток кэшбеком

🔹 Функциональные требования

— Создание и управление «копилкой»
— Пополнение копилки разными пользователями
— Привязка к каталогу и корзине маркетплейса
— Сценарий покупки и списания денег
— Возврат сдачи кэшбеком
— Уведомления участников о статусе

🔹 Нефункциональные требования

— До 200k DAU, всего ~1 млн пользователей
— Запуск MVP в течение квартала
— Масштабируемость под рост аудитории
— Интеграция с существующими сервисами: биллинг, пользователи, каталог, кэшбек, логистика
— SLA: быстрый отклик API (≤200 мс), отказоустойчивость, логирование

💡 Подсказки

— Если нужно уточнить какие-то вопросы: можете писать в комментарии, расширим вводные
— Подумайте о жизненном цикле копилки: создание → пополнение → покупка → возврат остатка → архив
— Разбейте на сущности: User, GiftPool, Contribution, Order, Refund
— Продумайте API: создание копилки, пополнение, список участников, закрытие, возврат
— Учтите рост нагрузки: кеширование, асинхронные процессы (например, списания)
— Не забудьте про безопасность: кто имеет право закрыть копилку и инициировать возврат

💬 Делитесь вашими результатами в комментариях. Можно развернуть жаркие дебаты о плюсах и минусах разных решений.

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍21
🔍 JPA vs Hibernate vs Spring Data JPA — что есть что?

Используете ORM, но путаетесь в терминах: JPA, Hibernate, Spring Data JPA? Давайте разложим по полочкам.

🔹 JPA (Java Persistence API)

— Это спецификация, а не инструмент.

— Определяет, как описывать сущности (@Entity, @Id, @OneToMany и т.д.).

— Определяет стандартные операции: EntityManager.persist(), find(), remove().

— Не содержит «железа» — только контракт.

👉 JPA = «договор о том, как работать с ORM в Java».

🔹 Hibernate

— Это реализация JPA, самый популярный ORM-фреймворк.

— Реально выполняет работу: маппинг объектов ↔️ таблиц, генерация SQL.

— Добавляет много фич «поверх» JPA (например, кэширование второго уровня, собственные аннотации).

— Можно использовать напрямую, но чаще его подключают как провайдер JPA.

👉 Hibernate = «двигатель», который выполняет то, что описано в JPA.

🔹 Spring Data JPA

— Это надстройка над JPA (и Hibernate под капотом).

— Упрощает жизнь: вместо EntityManager вы пишете интерфейсы UserRepository, а Spring сам генерирует реализацию.

— Позволяет писать запросы прямо в именах методов (findByEmailAndStatus).

— Поддерживает @Query для кастомных запросов.

— Даёт дополнительные удобства: пэйджинг, сортировки, спецификации.

👉 Spring Data JPA = «фасад», который избавляет от рутины и скрывает низкоуровневые детали.

🔹 Когда что использовать


— Если нужен чистый стандарт → JPA + реализация (например, Hibernate).

— Если хотите гибкость и полный контроль → можно работать напрямую с Hibernate.

— Если важна скорость разработки и минимум кода → Spring Data JPA.

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍112🔥2
👀 Внутреннее устройства HashMap

HashMap — один из базовых и в то же время самых хитро устроенных контейнеров в JDK. На поверхности простая структура «ключ–значение», но под капотом она сочетает массивы, списки и даже деревья, чтобы оставаться быстрой в разных сценариях нагрузки.

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

HashMap хранит данные в массиве бакетов (Node<K,V>[] table). Каждый бакет — это «корзина» для элементов, чей hashCode после хеширования и применения & (n-1) (где n — длина массива) указывает на конкретный индекс.

🔑 Хэш и распределение

1. Вызов hashCode() у ключа.
2. Дополнительное хеширование (spread), чтобы снизить коллизии из-за плохой реализации hashCode.
3. Индекс бакета = hash & (table.length - 1).

🌊 Коллизии

Если несколько ключей попали в один бакет:

— до Java 8 это всегда был связанный список (linked list),

— начиная с Java 8: при росте числа элементов в бакете больше 8 и достаточном размере таблицы он превращается в сбалансированное красно-чёрное дерево. Это резко ускоряет поиск в «плохих» случаях (с O(n) до O(log n)).

⚡️ Ресайзинг

Когда количество элементов превышает capacity * loadFactor (по умолчанию 0.75), создаётся новый массив в 2 раза больше, все элементы перехешируются и раскладываются по новым бакетам. Это дорогостоящая операция, но благодаря амортизации остаётся приемлемой.

📊 Производительность


— Поиск/вставка/удаление в среднем: O(1).

— В худшем случае (плохой hashCode + коллизии): O(log n) благодаря деревьям.

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


— Ключи неупорядочены. Для упорядоченности есть LinkedHashMap.

— HashMap не потокобезопасен. Для многопоточной среды нужен ConcurrentHashMap или синхронизация.

— Хорошо реализованный hashCode и equals критичны, иначе получите «забитые» бакеты и деградацию.

🧮 loadFactor и capacity


— Capacity — размер массива бакетов. По умолчанию 16.

— LoadFactor — коэффициент заполнения. По умолчанию 0.75.

Почему именно 0.75? Это компромисс: выше → меньше памяти, но больше коллизий; ниже → быстрее доступ, но больше памяти уходит впустую. Capacity всегда степень двойки, чтобы можно было вычислять индекс через hash & (n-1) вместо затратного %.

🔄 Итераторы и fail-fast

Если во время обхода карта меняется (кроме iterator.remove()), бросается ConcurrentModificationException. Под капотом это работает через счётчик модификаций (modCount), который проверяется в каждом next().

🌳 Деревья в деталях

Коллизии превращаются в красно-чёрное дерево, если размер списка в бакете > 8 и общее количество бакетов ≥ 64. Обратно в список (untreeify) при падении количества элементов < 6. Это сделано, чтобы не тратить память и CPU на лишнюю балансировку при малых размерах.

🔗 Документация: OpenJDK — HashMap source

Ставьте 🔥, если хотите такой же пост по другим коллекциям.

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥24👍71👏1
🔍 Просто о сложном: AtomicInteger

В многопоточном коде даже простая операция count++ не является атомарной. Она распадается на три шага:

1. Прочитать значение переменной.
2. Увеличить его.
3. Записать обратно.

Если два потока выполнят это одновременно, получится гонка данных (race condition). Итоговое значение будет меньше ожидаемого.

🔹 Как решает проблему AtomicInteger

AtomicInteger — это класс из пакета java.util.concurrent.atomic, который предоставляет атомарные (неделимые) операции над целыми числами.

Под капотом он использует механизм CAS (Compare-And-Swap), который поддерживается на уровне процессора.

Принцип работы:
— Читаем текущее значение.
— Проверяем, не изменилось ли оно за это время.
— Если совпадает, записываем новое.
— Если нет, повторяем попытку (spin loop).

🔹 Пример использования

public class Counter {
private final AtomicInteger count = new AtomicInteger(0);

public void increment() {
count.incrementAndGet(); // атомарное ++
}

public int get() {
return count.get();
}
}

Здесь incrementAndGet() гарантирует, что два потока не «перетрут» друг друга, а каждый инкремент будет учтён.

🔹 Основные методы

▪️ get() — получить текущее значение.
▪️ set(int newValue) — установить новое значение.
▪️ incrementAndGet() / getAndIncrement() — инкремент.
▪️ decrementAndGet() — декремент.
▪️ addAndGet(int delta) — прибавить значение.
▪️ compareAndSet(int expect, int update) — вручную применить CAS.

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

— Спин-блокировки

CAS может крутиться в цикле, пока не удастся обновить значение. При высокой конкуренции это нагружает процессор.

— ABA-проблема

Значение могло измениться на «A → B → A». Для простых счётчиков это не критично, но в сложных структурах данных используют AtomicStampedReference.

— Ограниченность

AtomicInteger работает только с int. Для более сложных случаев есть AtomicLong, AtomicReference, LongAdder (оптимизирован для высокой конкуренции).

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

✔️ Подходит:

— Для простых счётчиков, метрик.
— В неблокирующих алгоритмах (lock-free).
— В высоконагруженных сценариях, где synchronized слишком дорог.

Не подходит:

— Для сложных бизнес-операций над несколькими переменными (лучше использовать мьютексы или транзакции).
— При очень высокой конкуренции, может быть лучше взять LongAdder.

🔹 Итог

AtomicInteger — это лёгкий способ избавиться от гонок при работе с числами в многопоточности.

Это не магия, а всего лишь тонкая обёртка над CAS, встроенным в процессоры. Понимание этого механизма помогает писать по-настоящему безопасный и быстрый многопоточный код.

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍127🔥2
Сохраняйте шпаргалку по лямбдам и стрим апи

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
13👍7🔥4
👀 Внутреннее устройство LinkedList

LinkedList — это классическая реализация двусвязного списка. На поверхности он выглядит как обычная коллекция, реализующая интерфейсы List, Deque и Queue. Но под капотом это структура узлов (Node), которые связаны друг с другом через ссылки на предыдущий и следующий элемент.

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


Каждый элемент списка хранится в отдельном объекте Node<E>, который содержит:

▪️ E item — сам элемент
▪️ Node<E> next — ссылка на следующий узел
▪️ Node<E> prev — ссылка на предыдущий узел

У LinkedList есть два поля:

▪️ first — голова списка
▪️ last — хвост списка

Это позволяет быстро добавлять элементы в начало и конец.

⚡️ Добавление и удаление

— Добавление в начало (addFirst) или конец (addLast) → O(1): меняем ссылки у пары узлов.
— Удаление головы или хвоста также → O(1).
— Вставка или удаление в середине требует сначала дойти до нужного узла → O(n).

🌊 Поиск элемента


— По индексу: список не хранит массив, значит придётся идти по ссылкам.
— Оптимизация: если индекс ближе к голове, обход идёт с first, если к хвосту — с last.
Сложность в среднем — O(n/2), то есть линейная.

📊 Производительность

— Доступ по индексу → O(n).
— Добавление/удаление в начало или конец → O(1).
— Вставка/удаление в середину → O(n).
— Итерация по списку → O(n), но эффективно, так как используется последовательный проход по ссылкам.

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

— В отличие от ArrayList, в LinkedList нет операций с массивами и «ресайзинга».
— Но расходует больше памяти: каждый узел хранит не только элемент, но и две ссылки (prev/next).
— Итераторы fail-fast: изменение списка во время обхода бросает ConcurrentModificationException.

🔄 Итераторы и Deque

LinkedList реализует Deque, что делает его удобным для очередей и стеков. Offer, poll, peek работают за O(1). Push/pop превращают список в стек.

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

На практике ArrayList почти всегда быстрее по времени и эффективнее по памяти.
LinkedList может быть полезен только в редких случаях, когда нужны очень частые вставки/удаления в середину коллекции (без итерации по коллекции) и не важен доступ по индексу. В остальных случаях выбирайте ArrayList.

🔗 Документация: OpenJDK — LinkedList source | Официальная JavaDoc (Java 17)

Ставьте 🔥, если хотите такой же пост по другим коллекциям, например CopyOnWriteArrayList.

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥20👍32😁2🤔1
🧩 final vs finally vs finalize()

Казалось бы, три похожих ключевых слова → final, finally, finalize(). А смысл принципиально разный. Давайте разберёмся.

🔹 final

Модификатор, который делает сущность неизменяемой:

▪️ final переменная → нельзя переприсвоить.
▪️ final метод → нельзя переопределить.
▪️ final класс → нельзя наследовать.

Используется для обеспечения immutability и контрактов в коде.

🔹 finally

Блок в try-catch, который выполняется всегда (даже если выброшено исключение).

Гарантирует освобождение ресурсов:
try {
FileReader reader = new FileReader("data.txt");
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("Закрываем ресурсы");
}


🔹 finalize()


Метод класса Object, вызываемый GC перед удалением объекта. Используется редко, считается устаревшим (deprecated с Java 9, удалён в Java 18).

Минус: непредсказуемое время вызова.

Современная альтернатива: try-with-resources или явная очистка.

💡 Вывод

final → контроль изменяемости.
finally → контроль завершения.
finalize() → контроль очистки (но не используйте).

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍81🔥1👏1
🔍 Просто о сложном: Spring Boot

Spring Boot — это не «новый фреймворк», а надстройка над Spring, которая убирает рутину и ускоряет разработку.

В обычном Spring нужно было вручную конфигурировать всё: от DataSource до DispatcherServlet. В Boot это делается автоматически через автоконфигурацию.

🔹 Как работает автоконфигурация

Spring Boot сканирует зависимости и classpath, а затем подключает нужные бины:

— Если у вас есть spring-boot-starter-data-jpa, Boot автоматически создаст EntityManagerFactory, DataSource, транзакционный менеджер.

— Если добавлен spring-boot-starter-web, он поднимет встроенный Tomcat/Jetty и зарегистрирует контроллеры. И так далее.

Магия кроется в аннотации @EnableAutoConfiguration (включается через @SpringBootApplication). Она загружает META-INF/spring.factories → список классов-конфигов → каждый проверяет условия @ConditionalOnClass, @ConditionalOnMissingBean и решает: активироваться или нет.

💡 Почему это работает так гладко

Потому что в Boot сотни готовых конфигураций «на все случаи жизни».

Фактически, это огромная библиотека «если увидишь X — настрой Y».

👀 Подводный слой магии

— Черный ящик

Легко забыть, что именно сконфигурировал Boot. Иногда приходится «копать» в автоконфигурацию, чтобы понять, какой бин реально используется.

— Избыточные зависимости


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

— Конфликт настроек


Собственная конфигурация может пересечься с автоконфигурацией.

⚡️ Хорошая практика

— Не доверяйте «чёрному ящику»: при старте приложения смотрите Spring Boot Actuator и логи автоконфигурации.

— Знайте про --debug при старте: он показывает, какие автоконфигурации включены или отключены.

— В продакшене лучше контролировать, какие именно стартеры вы тянете. Иногда spring-boot-starter-web приносит в проект в три раза больше, чем реально нужно.

🎯 Итог

Spring Boot — это ускоритель, но не магия. Его сила в автоконфигурациях, а слабость в том, что легко потерять контроль.

Понимание того, как работает @EnableAutoConfiguration и условия @Conditional, отличает разработчика, который «просто пишет на Boot», от того, кто реально управляет приложением.

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

#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍132🔥1😁1