Все с пелёнок знают о существовании String Pool 🏊♀️
Полезная вещь для экономии памяти. Но единственный ли это пул в java?
В Integer, том самом классе-обертке для самого используемого примитива, есть внутренний класс IntegerCache - пул целых чисел в промежутке [-128; 127], так как в большинстве случаев используются числа как раз в этих пределах. Он объявлен как private static. В этом внутреннем классе кэшированные объекты находятся в массиве cache[].
Кэширование выполняется при первом использовании класса-обертки. После этого вместо создания нового экземпляра (кроме явного использования конструктора, конечно), используются кэшированные объекты, JVM берет их из пула✍🏻
⬇️⬇️⬇️
И каверзный вопрос
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5❤4🤔2
💡 Почему приложение теряет соединение с базой данных? Разбираем на примере реальной ошибки
🔍 Ситуация:
Приложение работает, но через какое-то время начинаются странности: база данных внезапно "отказывает", в логах ошибки
JDBCConnectionException - Unable to acquire JDBC Connection
и если присмотреться - рядом еще
SocketException - Too many open files .
О каких files речь и почему это мешает подключению к БД?
Вы проверяете PostgreSQL — соединений немного (ну вдруг "too many" как-то относится к соединениям), всё в порядке. В чём же проблема?
🕵️ Ответ скрыт на уровне операционной системы:
Ошибка Too many open files говорит о том, что приложение исчерпало лимит файловых дескрипторов. Linux (на сервере с вашим приложением) выделяет каждому процессу ограниченное количество дескрипторов, которые используются не только для работы с файлами, но и для сетевых соединений (сокетов)!
Если в приложении какие-то ресурсы (например, сокеты или файлы) открываются, но не закрываются, дескрипторы "утекают". В итоге, когда приложение пытается установить новое соединение с базой данных, система просто не может выделить дескриптор для нового сокета.
⚙️ Причина оказывается не в базе, а в куче "забытых" файлов или других ресурсов.
🔧 Как избежать?
Проблема решается дисциплиной работы с ресурсами. Можно использовать автоматические механизмы языка, такие как try-with-resources, или следить за ручным закрытием.
🛠 Вывод:
Ошибка соединения с базой данных и лимит открытых файлов на первый взгляд не связаны, но на практике это замкнутая цепь. Утечки файлов → превышение лимита дескрипторов → невозможность создать сокет → сбой подключения к БД.
В следующих постах расскажем как ручками проверить количество активных, ожидающих и зависших подключений к БД.
🔍 Ситуация:
Приложение работает, но через какое-то время начинаются странности: база данных внезапно "отказывает", в логах ошибки
JDBCConnectionException - Unable to acquire JDBC Connection
SocketException - Too many open files
О каких files речь и почему это мешает подключению к БД?
Вы проверяете PostgreSQL — соединений немного (ну вдруг "too many" как-то относится к соединениям), всё в порядке. В чём же проблема?
🕵️ Ответ скрыт на уровне операционной системы:
Ошибка Too many open files говорит о том, что приложение исчерпало лимит файловых дескрипторов. Linux (на сервере с вашим приложением) выделяет каждому процессу ограниченное количество дескрипторов, которые используются не только для работы с файлами, но и для сетевых соединений (сокетов)!
Если в приложении какие-то ресурсы (например, сокеты или файлы) открываются, но не закрываются, дескрипторы "утекают". В итоге, когда приложение пытается установить новое соединение с базой данных, система просто не может выделить дескриптор для нового сокета.
⚙️ Причина оказывается не в базе, а в куче "забытых" файлов или других ресурсов.
🔧 Как избежать?
Проблема решается дисциплиной работы с ресурсами. Можно использовать автоматические механизмы языка, такие как try-with-resources, или следить за ручным закрытием.
🛠 Вывод:
Ошибка соединения с базой данных и лимит открытых файлов на первый взгляд не связаны, но на практике это замкнутая цепь. Утечки файлов → превышение лимита дескрипторов → невозможность создать сокет → сбой подключения к БД.
В следующих постах расскажем как ручками проверить количество активных, ожидающих и зависших подключений к БД.
❤6🆒3✍2
💡 Совет: как ускорить отладку
В процессе дебага нужно проверить небольшое изменение в методе, но перезапускать всё приложение ради одной строки кода — слишком долго? Используйте механизм HotSwap для быстрого внесения изменений:
1️⃣ Запускаете Debug Mode.
2️⃣ Делаете изменения в коде.
3️⃣ Заходите в меню Build - пункт Recompile 'ChangedClass.java' (или просто Ctrl+Shift+F9), в окошке жмёте Reload -> видите сообщение о том, что один класс reloaded.
4️⃣ Как ни в чем не бывало продолжаете дебажить код, но уже с изменениями.
⚠️ Ограничения:
Работает только для изменений в методах (без добавления новых полей/методов/классов).
Более сложные изменения могут потребовать использования инструментов, таких как JRebel.
🚀 Итог: вместо долгого перезапуска вы сразу видите результат изменений в работе приложения. Попробуйте — это действительно экономит время!
P.S. Речь про Idea, конечно же
В процессе дебага нужно проверить небольшое изменение в методе, но перезапускать всё приложение ради одной строки кода — слишком долго? Используйте механизм HotSwap для быстрого внесения изменений:
1️⃣ Запускаете Debug Mode.
2️⃣ Делаете изменения в коде.
3️⃣ Заходите в меню Build - пункт Recompile 'ChangedClass.java' (или просто Ctrl+Shift+F9), в окошке жмёте Reload -> видите сообщение о том, что один класс reloaded.
4️⃣ Как ни в чем не бывало продолжаете дебажить код, но уже с изменениями.
⚠️ Ограничения:
Работает только для изменений в методах (без добавления новых полей/методов/классов).
Более сложные изменения могут потребовать использования инструментов, таких как JRebel.
🚀 Итог: вместо долгого перезапуска вы сразу видите результат изменений в работе приложения. Попробуйте — это действительно экономит время!
P.S. Речь про Idea, конечно же
1🔥6❤5👍4✍2
✨Иногда в пылу разработки мы забиваем забываем об одной важной детали при работе с реляционными БД — индексах на внешних ключах
🔍Казалось бы, мелочь: зачем заморачиваться с ещё одним индексом, если сам внешний ключ уже прописан? Но именно индекс по FK способен ускорить join-запросы (а также каскадное удаление), особенно при работе с большими объёмами данных. Еще и общая нагрузка на систему снижается. В результате вы получаете более отзывчивое приложение и довольных пользователей
✅Если у вас в базе есть внешние ключи (а они есть), убедитесь, что к ним привязаны соответствующие индексы — и ваша СУБД скажет вам спасибо
upd
в комментах вскрылось, что по этому поводу говорит оф.документация по постгресу ⬇️
🔍Казалось бы, мелочь: зачем заморачиваться с ещё одним индексом, если сам внешний ключ уже прописан? Но именно индекс по FK способен ускорить join-запросы (а также каскадное удаление), особенно при работе с большими объёмами данных. Еще и общая нагрузка на систему снижается. В результате вы получаете более отзывчивое приложение и довольных пользователей
✅Если у вас в базе есть внешние ключи (а они есть), убедитесь, что к ним привязаны соответствующие индексы — и ваша СУБД скажет вам спасибо
upd
в комментах вскрылось, что по этому поводу говорит оф.документация по постгресу ⬇️
1👍6🔥6💯3
SERIAL vs GENERATED AS IDENTITY в PostgreSQL - что выбрать
В большинстве случаев id наших таблиц - это число с автоинкрементом. И под это определение идеально подходит тип SERIAL (или BIGSERIAL) - при его использовании для колонки задается тип INT (или BIGINT, аналог лонга в джаве) + последовательность с такими параметрами как начальное значение, текущее, шаг инкремента и т.д.
Но это вариант считается устаревшим.
Начиная с версии PostgreSQL 10 (т.е. довольно давно) появился более современный и гибкий способ — GENERATED [ALWAYS | BY DEFAULT] AS IDENTITY. В чём же разница?
🪗
- Под капотом создаёт отдельную SEQUENCE и использует nextval() для автоинкремента
- Не соответствует стандарту SQL и имеет ограничения в управлении последовательностью
- Всё ещё встречается в старых проектах
- ПОЗВОЛЯЕТ вставлять значения идентификатора принудительно!
🚀
- Полностью соответствует SQL:2003
- Интегрированная логика без необходимости управлять отдельной SEQUENCE
- Легко изменять параметры автоинкремента с помощью ALTER TABLE
- ЗАПРЕЩАЕТ вставлять значения идентификатора принудительно!
Чуть подробнее про п.4:
При использовании SERIAL/BIGSERIAL можно в команде UPDATE указать поле id и соответствующее значение в VALUES, и оно будет добавлено в новой строке. Но что если инкремент сиквенса потом дойдёт до этого значения (допустим, сейчас сиквенс на 100, мы вставили 200)? Он же его обойдёт? Обойдёт же?
А ничего подобного. Сиквенс на это не посмотрит, выдаст очередное значение, и операция завершится ошибкой (что-то типа "значение id = n нарушает ограничение первичного ключа").
Конечно, в стандартном сценарии работы с таблицей, тем более с помощью JPA, это не критично, но всё же.
При использовании GENERATED ... AS IDENTITY тоже можно вставлять свои значения в id, но только если ALWAYS заменить на BY DEFAULT (но тогда и нюанс с нарушением ограничения не исчезает).
Если же написать ALWAYS, то принудительная вставка своих id будет невозможна.
Кстати, пишите в комментах свои предположения - для чего может быть нужно вставлять свои id и как всё-таки избежать конфликта (чтобы сиквенс не напоролся на вставленное значение🤔🤔🤔)?
И немного про управление последовательностью (сиквенсом):
Её можно сбросить на единицу или начать с другого значения несколькими способами
напрямую изменить сиквенс
или, если используется GENERATED...IDENTITY - сделать это через alter таблицы
или, например, с подзапросом для продолжения с максимального значения на данный момент
Итог:
всё тлен, делайте какие угодно id, хоть прямо в джаве генерируйте, кому какое дело
В большинстве случаев id наших таблиц - это число с автоинкрементом. И под это определение идеально подходит тип SERIAL (или BIGSERIAL) - при его использовании для колонки задается тип INT (или BIGINT, аналог лонга в джаве) + последовательность с такими параметрами как начальное значение, текущее, шаг инкремента и т.д.
Но это вариант считается устаревшим.
Начиная с версии PostgreSQL 10 (т.е. довольно давно) появился более современный и гибкий способ — GENERATED [ALWAYS | BY DEFAULT] AS IDENTITY. В чём же разница?
🪗
CREATE TABLE some_entity (
id SERIAL PRIMARY KEY,
-- other columns
);
- Под капотом создаёт отдельную SEQUENCE и использует nextval() для автоинкремента
- Не соответствует стандарту SQL и имеет ограничения в управлении последовательностью
- Всё ещё встречается в старых проектах
- ПОЗВОЛЯЕТ вставлять значения идентификатора принудительно!
🚀
CREATE TABLE some_entity (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
-- other columns
);
- Полностью соответствует SQL:2003
- Интегрированная логика без необходимости управлять отдельной SEQUENCE
- Легко изменять параметры автоинкремента с помощью ALTER TABLE
- ЗАПРЕЩАЕТ вставлять значения идентификатора принудительно!
Чуть подробнее про п.4:
При использовании SERIAL/BIGSERIAL можно в команде UPDATE указать поле id и соответствующее значение в VALUES, и оно будет добавлено в новой строке. Но что если инкремент сиквенса потом дойдёт до этого значения (допустим, сейчас сиквенс на 100, мы вставили 200)? Он же его обойдёт? Обойдёт же?
А ничего подобного. Сиквенс на это не посмотрит, выдаст очередное значение, и операция завершится ошибкой (что-то типа "значение id = n нарушает ограничение первичного ключа").
Конечно, в стандартном сценарии работы с таблицей, тем более с помощью JPA, это не критично, но всё же.
При использовании GENERATED ... AS IDENTITY тоже можно вставлять свои значения в id, но только если ALWAYS заменить на BY DEFAULT (но тогда и нюанс с нарушением ограничения не исчезает).
Если же написать ALWAYS, то принудительная вставка своих id будет невозможна.
Кстати, пишите в комментах свои предположения - для чего может быть нужно вставлять свои id и как всё-таки избежать конфликта (чтобы сиквенс не напоролся на вставленное значение🤔🤔🤔)?
И немного про управление последовательностью (сиквенсом):
Её можно сбросить на единицу или начать с другого значения несколькими способами
напрямую изменить сиквенс
ALTER SEQUENCE some_entity_id_seq RESTART WITH 1;
или, если используется GENERATED...IDENTITY - сделать это через alter таблицы
ALTER TABLE some_entity ALTER COLUMN id RESTART WITH 1;
или, например, с подзапросом для продолжения с максимального значения на данный момент
SELECT setval('some_entity_id_seq', (SELECT MAX(id) FROM some_entity));Итог:
всё тлен, делайте какие угодно id, хоть прямо в джаве генерируйте, кому какое дело
1🔥11🤝3❤2☃1
"Как работает Хэшмапа? " - этот вопрос уже стал мемом в контексте обсуждения собеседований по джаве. Он встречается в любом списке "топ-N вопросов по Java" и звучит настолько часто, что многие стали считать это признаком низкого качества подготовки интервьюера к проведению тех.собеса.
Однако, означает ли это, что нам не надо разбираться в хэшмапе? Конечно, надо, потому что это база, а еще хорошая возможность набрать легкие баллы там, где другие теряются в деталях!
📊 equals(), hashCode() и… коллизии
Часто вопросы крутятся вокруг работы equals() и hashCode(). И тут можно попасть в ловушку терминологии, особенно когда речь заходит о коллизиях. Вот пример распространённой ошибки:
❌ "Ключи попадают в один бакет при коллизии хэшкода"
На самом деле это не совсем так. Правильнее будет сказать:
✅ "Ключи попадают в один бакет при коллизии номера бакета"
В чём разница?
Коллизия хэшкода — два разных объекта возвращают одинаковый результат метода hashCode() - это случается довольно редко.
Коллизия бакета — два объекта (неважно, одинаковый у них хэшкод или нет) попадают в один и тот же бакет в HashMap.
И вот важный момент: для коллизии бакета НЕ НУЖНА коллизия хэшкода
🚀 Как определяется номер бакета?
При добавлении элемента (пары ключ-значение) в HashMap определяется хэшкод ключа, а далее по сути находится остаток от его деления на количество бакетов с помощью таких операций:
где n — это размер массива бакетов (всегда степень двойки - 16, 32, 64 etc).
И так как количество бакетов невелико, для разных ключей может быть вычислен один и тот же результат. И вот это уже и есть коллизия бакета
🔑 Наглядный пример:
если бакетов 16, то ключи
- "1" и "17" попадут вместе в один бакет (хотя очевидно, что хэшкод у них абсолютно разный), а следом за ними пойдут еще "33", "49", "65" и т.д. с шагом в 16 единиц;
- "2", "18", "34", "50"... - вместе в следующий бакет
Итоги:
- Коллизия хэшкода != коллизия бакета
- Для распределения по бакетам важен не сам хэшкод, а результат функции распределения
- Понимание этой разницы — не просто "мемный вопрос", а демонстрация того, насколько вы погрузились в тему
В следующих постах расскажу и про другие неочевидные вещи, связанные с коллекциями
Однако, означает ли это, что нам не надо разбираться в хэшмапе? Конечно, надо, потому что это база, а еще хорошая возможность набрать легкие баллы там, где другие теряются в деталях!
📊 equals(), hashCode() и… коллизии
Часто вопросы крутятся вокруг работы equals() и hashCode(). И тут можно попасть в ловушку терминологии, особенно когда речь заходит о коллизиях. Вот пример распространённой ошибки:
❌ "Ключи попадают в один бакет при коллизии хэшкода"
На самом деле это не совсем так. Правильнее будет сказать:
✅ "Ключи попадают в один бакет при коллизии номера бакета"
В чём разница?
Коллизия хэшкода — два разных объекта возвращают одинаковый результат метода hashCode() - это случается довольно редко.
Коллизия бакета — два объекта (неважно, одинаковый у них хэшкод или нет) попадают в один и тот же бакет в HashMap.
И вот важный момент: для коллизии бакета НЕ НУЖНА коллизия хэшкода
🚀 Как определяется номер бакета?
При добавлении элемента (пары ключ-значение) в HashMap определяется хэшкод ключа, а далее по сути находится остаток от его деления на количество бакетов с помощью таких операций:
key.hashCode() ^ (h >>> 16)
(n - 1) & hash
где n — это размер массива бакетов (всегда степень двойки - 16, 32, 64 etc).
И так как количество бакетов невелико, для разных ключей может быть вычислен один и тот же результат. И вот это уже и есть коллизия бакета
🔑 Наглядный пример:
если бакетов 16, то ключи
- "1" и "17" попадут вместе в один бакет (хотя очевидно, что хэшкод у них абсолютно разный), а следом за ними пойдут еще "33", "49", "65" и т.д. с шагом в 16 единиц;
- "2", "18", "34", "50"... - вместе в следующий бакет
Итоги:
- Коллизия хэшкода != коллизия бакета
- Для распределения по бакетам важен не сам хэшкод, а результат функции распределения
- Понимание этой разницы — не просто "мемный вопрос", а демонстрация того, насколько вы погрузились в тему
В следующих постах расскажу и про другие неочевидные вещи, связанные с коллекциями
1👍12🔥9🤝2❤1
📔 Книга "Чистый код" начинается с иллюстрации единственной надежной метрики качества кода - количество "чертей" в минуту 👹/m
Сразу небольшое отступление.
знаете, когда написана эта книга? Шок - 2008 год! При этом прямо в первой главе Боб Мартин пишет:
Дальше он, конечно же, опровергает это мнение. Но это 2008 год - за 14 лет до хайпа chatGPT etc. Вообще подобные разговоры велись чуть ли не всю историю программирования... но это уже
совсем другая история.
🤌 Вернемся к качеству кода.
Далее в главе 3 "Функции" он пишет, что аргументы-флаги (то есть булевы значения) "уродливы", а передача логического значения в метод - ужасная привычка.
Почему?
Потому что сигнатура метода становится менее понятной, а сам метод выполняет две разных операции (в зависимости от флага) вместо одной.
В этом случае он рекомендует разбивать такой метод на два метода (без boolean-параметров), давая каждому методу соответствующее понятное название.
Возможно(совершенно точно) , кто-то может поспорить с этим, но пока идём дальше.
📘Что говорит Джош Блох в своём "Эффективном программировании"?
У него тоже есть совет разбивать методы, но, правда, в другой ситуации - когда у исходного метода слишком много параметров - больше четырёх
(это только один из трех способов исправления ситуации с большим количеством параметров, а кроме него - использование вспомогательного класса с набором параметров, и использование билдера).
А что насчет boolean? Блох тоже отговаривает от таких сигнатур методов, предлагая взамен...
🫨 ЭНАМЫ с двумя элементами!
Пример:
Какие плюсы:
1️⃣ сразу понятнее - и название энама, и сами значения несут явный смысл
2️⃣ в любой момент можно добавить в энам новые значения без лишних телодвижений (приводится пример - TemperatureScale.KELVIN).
Если метод написан хорошо, то его, возможно, вообще не придется трогать. Принцип open-closed в действии?
Пример с шкалами температур, мне кажется, не совсем удачный, но смысл ясен.
3️⃣ вероятно, количество чертей станет ближе к левой двери с картинки, чем к правой
👀Итог:
выбрать какой-то из приведенных способов (по Мартину или по Блоху), или же написать метод, принимающий 5 boolean-аргументов - как всегда, зависит от конкретной ситуации и от положения Марса в пятом доме. Но лучше знать об этих вариантах, чтобы было из чего выбирать
Сразу небольшое отступление.
знаете, когда написана эта книга? Шок - 2008 год! При этом прямо в первой главе Боб Мартин пишет:
[...] Нам даже доводилось слышать мнение, что [...] скоро весь код будет генерироваться, а не писаться вручную. Что программисты станут попросту не нужны, потому что бизнесмены будут генерировать программы по спецификациям.
Дальше он, конечно же, опровергает это мнение. Но это 2008 год - за 14 лет до хайпа chatGPT etc. Вообще подобные разговоры велись чуть ли не всю историю программирования... но это уже
совсем другая история.
🤌 Вернемся к качеству кода.
Далее в главе 3 "Функции" он пишет, что аргументы-флаги (то есть булевы значения) "уродливы", а передача логического значения в метод - ужасная привычка.
Почему?
Потому что сигнатура метода становится менее понятной, а сам метод выполняет две разных операции (в зависимости от флага) вместо одной.
В этом случае он рекомендует разбивать такой метод на два метода (без boolean-параметров), давая каждому методу соответствующее понятное название.
Возможно
📘Что говорит Джош Блох в своём "Эффективном программировании"?
У него тоже есть совет разбивать методы, но, правда, в другой ситуации - когда у исходного метода слишком много параметров - больше четырёх
(это только один из трех способов исправления ситуации с большим количеством параметров, а кроме него - использование вспомогательного класса с набором параметров, и использование билдера).
А что насчет boolean? Блох тоже отговаривает от таких сигнатур методов, предлагая взамен...
🫨 ЭНАМЫ с двумя элементами!
Пример:
public enum TemperatureScale { FAHRENHEIT, CELSIUS }Какие плюсы:
1️⃣ сразу понятнее - и название энама, и сами значения несут явный смысл
doSomething(TemperatureScale.CELSIUS);
// vs
doSomething(true);
2️⃣ в любой момент можно добавить в энам новые значения без лишних телодвижений (приводится пример - TemperatureScale.KELVIN).
Если метод написан хорошо, то его, возможно, вообще не придется трогать. Принцип open-closed в действии?
Пример с шкалами температур, мне кажется, не совсем удачный, но смысл ясен.
3️⃣ вероятно, количество чертей станет ближе к левой двери с картинки, чем к правой
👀Итог:
выбрать какой-то из приведенных способов (по Мартину или по Блоху), или же написать метод, принимающий 5 boolean-аргументов - как всегда, зависит от конкретной ситуации и от положения Марса в пятом доме. Но лучше знать об этих вариантах, чтобы было из чего выбирать
1🔥13👏2💯1
Кстати, какие книги считаете маст рид?
Anonymous Poll
23%
Чистый код, Боб Мартин
21%
Java. Эффективное программирование, Джош Блох
16%
Философия Java, Брюс Эккель
21%
Java. Полное руководство/Руководство для начинающих, Герберт Шилдт
5%
Java. Библиотека профессионала, Кей Хорстманн
9%
Книга с поездами
21%
Книга с кабанчиком
5%
Алгоритмы на Java, Роберт Седжвик, Кевин Уэйн
12%
Spring in action
16%
Аксиома Эскобара по всем пунктам
Многие знают про плагин Key Promoter X, но я считаю его скорее вредным.
Он вроде бы полезен - подсказывает горячие клавиши, но на самом деле отслеживает далеко не все ситуации, когда вы делаете что-то мышкой и глазами, а могли бы использовать шорткаты. Таким образом, создается иллюзия, что он вам поможет, но помощь эта неэффективна.
Намного лучше взять с сайта жетбрейнс официальный keymap (на картинке).
Там много всего, сходу может выглядеть страшно, поэтому предлагаю пробежаться по реально полезным штукам для повседневной работы:
Работа с вкладками
↔️ Ctrl + Tab – переключение между вкладками (там еще разные пункты есть кроме открытых вкладок, посмотрите сами)
🔂 Alt + Left/Right - переключение по соседним вкладкам влево/вправо
⏹️ Ctrl + F4 – закрытие вкладки (очень удобно когда в ходе работы наоткрывалось много, а после нужно все закрыть)
Навигация
⬅️➡️ Ctrl + Alt + Left/Right – переход назад/вперед по истории переходов (это прям самая имба - возвращаетесь назад в том порядке, каким попали в текущее место в коде - например, при переходе по вызовам методов или объявлению переменных)
🔀 Ctrl + Shift + Backspace – переход к последнему изменению (тоже очень полезно)
🔢 Ctrl + E – переключение между последними использованными файлами (похоже на переключение между вкладками, но включает и уже закрытые)
Работа с закладками
⏸️ F11 – поставить обычную закладку на строке /убрать любую закладку
#️⃣ Shift + F11 – открыть список всех закладок.
⏺️ Ctrl + F11 – поставить пронумерованную закладку (легче ориентироваться, чем с обычными)
↗️ Ctrl + <номер> – переход к пронумерованной закладке (например, Ctrl + 1)
Использование таких приемов действительно делает работу проще и быстрее
Он вроде бы полезен - подсказывает горячие клавиши, но на самом деле отслеживает далеко не все ситуации, когда вы делаете что-то мышкой и глазами, а могли бы использовать шорткаты. Таким образом, создается иллюзия, что он вам поможет, но помощь эта неэффективна.
Намного лучше взять с сайта жетбрейнс официальный keymap (на картинке).
Там много всего, сходу может выглядеть страшно, поэтому предлагаю пробежаться по реально полезным штукам для повседневной работы:
Работа с вкладками
↔️ Ctrl + Tab – переключение между вкладками (там еще разные пункты есть кроме открытых вкладок, посмотрите сами)
🔂 Alt + Left/Right - переключение по соседним вкладкам влево/вправо
⏹️ Ctrl + F4 – закрытие вкладки (очень удобно когда в ходе работы наоткрывалось много, а после нужно все закрыть)
Навигация
⬅️➡️ Ctrl + Alt + Left/Right – переход назад/вперед по истории переходов (это прям самая имба - возвращаетесь назад в том порядке, каким попали в текущее место в коде - например, при переходе по вызовам методов или объявлению переменных)
🔀 Ctrl + Shift + Backspace – переход к последнему изменению (тоже очень полезно)
🔢 Ctrl + E – переключение между последними использованными файлами (похоже на переключение между вкладками, но включает и уже закрытые)
Кстати, в Идее у многих шорткатов есть еще и действие на второе нажатие! Попробуйте нажать Ctrl + E несколько раз - увидите разницу (показывает последние использованные файлы/последние измененные файлы)
Работа с закладками
⏸️ F11 – поставить обычную закладку на строке /убрать любую закладку
#️⃣ Shift + F11 – открыть список всех закладок.
⏺️ Ctrl + F11 – поставить пронумерованную закладку (легче ориентироваться, чем с обычными)
↗️ Ctrl + <номер> – переход к пронумерованной закладке (например, Ctrl + 1)
Использование таких приемов действительно делает работу проще и быстрее
1🔥15👍4❤2🥱1
Всем привет! Пост не технический.
Сегодня завершилось моё преподавание дисциплин Java Core и SQL в замечательной группе мотивированных и старательных учеников. Эти полгода мы тщательно разбирали базу, учились применять её в своих проектах, рассматривали реальные кейсы и готовились к тем сюрпризам, которые ждут на собеседованиях и в работе.
Большое спасибо всем за ваше внимание, активное участие и правильные вопросы, было круто, я и сам учился вместе с вами))
Буду рад увидеть здесь ваши отзывы. Что запомнилось? Что было полезно? Что можно было сделать лучше? В общем, пишите всё, что думаете. Остаёмся на связи! 😊
Сегодня завершилось моё преподавание дисциплин Java Core и SQL в замечательной группе мотивированных и старательных учеников. Эти полгода мы тщательно разбирали базу, учились применять её в своих проектах, рассматривали реальные кейсы и готовились к тем сюрпризам, которые ждут на собеседованиях и в работе.
Большое спасибо всем за ваше внимание, активное участие и правильные вопросы, было круто, я и сам учился вместе с вами))
Буду рад увидеть здесь ваши отзывы. Что запомнилось? Что было полезно? Что можно было сделать лучше? В общем, пишите всё, что думаете. Остаёмся на связи! 😊
1🔥9🥰2👏2😢1
Как стримы дружат с мапами, часть 1.
Тренируемся на кошках.
Когда речь заходит о быстром (и удобном!) доступе к данным в Java, сразу приходит на ум HashMap. Она широко используется в самых разных сценариях — от реализации паттерна «Стратегия» (где стратегии могут храниться в мапе по неким ключам) до кеширования данных и многого другого.
На практике бывает нужно преобразовать одну коллекцию в другую - например, перебросить все объекты из списка в мапу (как при той же Стратегии с иcпользованием спрингового dependency injection - об этом тоже расскажу, там есть свои фишки).
Но с коллекциями мы часто работаем с помощью Stream api, и возникает вопрос - как можно собрать мапу стримами? А сделать это можно аж тремя способами (методами из Collectors), не говоря уж о перегрузках этих методов:
1️⃣ Самый прямолинейный способ - это использовать метод toMap(), сделав id объектов - ключами, а значениями - сами объекты (для этого в метод toMap передается две функции, т.е. реализации функционального интерфейса Function):
Кстати, вместо
можно применять статический метод из этого же Function, он делает ровно то же самое:
2️⃣ Можно сгруппировать объекты по какому-то из полей, например по "year"
Из объявления мапы видно, что кошки с одинаковым годом окажутся в одном списке, т.е. value мапы - List<Kitty>.
Но что если жуть как хочется собрать их в сет, а не в лист? Решение есть - добавить второй параметр - еще один коллектор (в объявлении, соответственно, тоже ставим Set<Kitty>)
На самом деле можно вторым параметром передать и Collectors.toList(), но это как раз поведение по умолчанию, ничего не изменится.
А что если при этом еще и собрать надо не в HashMap (как по умолчанию), а в LikedHashMap или TreeMap? Еще параметр! (между существующими)
Продолжение следует...
Тренируемся на кошках.
Когда речь заходит о быстром (и удобном!) доступе к данным в Java, сразу приходит на ум HashMap. Она широко используется в самых разных сценариях — от реализации паттерна «Стратегия» (где стратегии могут храниться в мапе по неким ключам) до кеширования данных и многого другого.
На практике бывает нужно преобразовать одну коллекцию в другую - например, перебросить все объекты из списка в мапу (как при той же Стратегии с иcпользованием спрингового dependency injection - об этом тоже расскажу, там есть свои фишки).
Но с коллекциями мы часто работаем с помощью Stream api, и возникает вопрос - как можно собрать мапу стримами? А сделать это можно аж тремя способами (методами из Collectors), не говоря уж о перегрузках этих методов:
Collectors.toMap()
Collectors.groupingBy()
Collectors.partitioningBy()
1️⃣ Самый прямолинейный способ - это использовать метод toMap(), сделав id объектов - ключами, а значениями - сами объекты (для этого в метод toMap передается две функции, т.е. реализации функционального интерфейса Function):
Map<Integer, Kitty> kittyById = kitties.stream()
.collect(Collectors.toMap(
Kitty::getId, // функция для ключа
kitty -> kitty // функция для значения
));
Кстати, вместо
kitty -> kitty
можно применять статический метод из этого же Function, он делает ровно то же самое:
Map<Integer, Kitty> kittyById = kitties.stream()
.collect(Collectors.toMap(
Kitty::getId, // функция для ключа
Function.identity() // функция для значения
));
2️⃣ Можно сгруппировать объекты по какому-то из полей, например по "year"
Map<Integer, List<Kitty>> groupedByYear = kitties.stream()
.collect(Collectors.groupingBy(
Kitty::getYear // поле для группировки
));
Из объявления мапы видно, что кошки с одинаковым годом окажутся в одном списке, т.е. value мапы - List<Kitty>.
Но что если жуть как хочется собрать их в сет, а не в лист? Решение есть - добавить второй параметр - еще один коллектор (в объявлении, соответственно, тоже ставим Set<Kitty>)
Map<Integer, Set<Kitty>> groupedByYear = kitties.stream()
.collect(Collectors.groupingBy(
Kitty::getYear,
Collectors.toSet() // коллектор в коллекторе
));
На самом деле можно вторым параметром передать и Collectors.toList(), но это как раз поведение по умолчанию, ничего не изменится.
А что если при этом еще и собрать надо не в HashMap (как по умолчанию), а в LikedHashMap или TreeMap? Еще параметр! (между существующими)
Map<Integer, Set<Kitty>> groupedByYear = kitties.stream()
.collect(Collectors.groupingBy(
Kitty::getYear,
LinkedHashMap::new, // конкретная мапа
Collectors.toSet()
));
Продолжение следует...
👍10🔥6❤3🤔3
Как стримы дружат с мапами, часть 2.
Тренируемся на кошках.
3️⃣ Разбить объекты на две партиции по некоторому условию. Ключами являются в этом случае значение true и false, а объекты также собираются в коллекции (в одну - удовлетворяющие условию, в другую - остальные)
После этого по ключу true получаем список кошек, у которых поле "year" больше или равно 2020, по ключу false - от 2019 и меньше.
4️⃣ Так как самым популярным является первый вариант, то можно рассмотреть для него еще некоторые дополнительные возможности.
🎱 Например, что делать если при использовании метода toMap() ключи могут совпадать?
С помощью третьего параметра можно сказать стриму оставить первое найденное с этим ключом value, а можно - последнее.
И даже! - сказать ему вообще убрать из мапы такие объекты, у которых ключи оказались повторяющимися:
Естественно, oldValue и newValue - просто условные названия, их можно заменить на что угодно.
И еще - если не задать один из этих вариантов поведения, то при обнаружении одинаковых ключей выпадет исключение IllegalStateException: Duplicate key
🗺 Также в методе toMap() можно получить на выходе любой вид мапы:
Вывод:
в стримах есть много не очень часто используемых, но очень интересных возможностей, мы увидели это на примере формирования мапы на основе списка объектов
P.S. Ни одна kitty не пострадала
Тренируемся на кошках.
3️⃣ Разбить объекты на две партиции по некоторому условию. Ключами являются в этом случае значение true и false, а объекты также собираются в коллекции (в одну - удовлетворяющие условию, в другую - остальные)
Map<Boolean, List<Kitty>> partitionedByYear = kitties.stream()
.collect(Collectors.partitioningBy(
kitty -> kitty.getYear() >= 2020 // любое условие
));
После этого по ключу true получаем список кошек, у которых поле "year" больше или равно 2020, по ключу false - от 2019 и меньше.
4️⃣ Так как самым популярным является первый вариант, то можно рассмотреть для него еще некоторые дополнительные возможности.
🎱 Например, что делать если при использовании метода toMap() ключи могут совпадать?
С помощью третьего параметра можно сказать стриму оставить первое найденное с этим ключом value, а можно - последнее.
И даже! - сказать ему вообще убрать из мапы такие объекты, у которых ключи оказались повторяющимися:
Map<String, Kitty> kittyById = kitties.stream()
.collect(Collectors.toMap(
Kitty::getName, // например, у кошек одинаковые имена
Function.identity(),
(oldValue, newValue) -> oldValue // оставляем первую такую кошку
// или
(oldValue, newValue) -> newValue // оставляем последнюю (новую)
// или
(oldValue, newValue) -> null // избавляемся от неуникальных
));
Естественно, oldValue и newValue - просто условные названия, их можно заменить на что угодно.
И еще - если не задать один из этих вариантов поведения, то при обнаружении одинаковых ключей выпадет исключение IllegalStateException: Duplicate key
🗺 Также в методе toMap() можно получить на выходе любой вид мапы:
Map<String, Kitty> kittyById = kitties.stream()
.collect(Collectors.toMap(
Kitty::getName, // ключи - имена
Function.identity(),
(old, new) -> old,
TreeMap::new // сразу сортируем по именам
));
Вывод:
в стримах есть много не очень часто используемых, но очень интересных возможностей, мы увидели это на примере формирования мапы на основе списка объектов
P.S. Ни одна kitty не пострадала
1👍6❤4🔥4
Про паттерны и собеседования (и про тот-паттерн-который-нельзя-называть)
На собеседованиях часто спрашивают про паттерны (что-то вроде «Расскажи про паттерны. Какие знаешь?»).
Хочется сразу сделать несколько оговорок:
1️⃣ когда говорят "паттерны", обычно подразумевают паттерны проектирования или Design Patterns - из известной книги "банды четырёх". Однако, паттерны – более широкое понятие. Есть паттерны микросервисной архитектуры, паттерны concurrency (вроде Thread Pool), паттерны интеграции (например, Circuit Breaker)
2️⃣ у "банды четырёх" 23 паттерна, но не все из них часто используются (возможно, некоторые даже не выходили за пределы страниц книги 😳 )
3️⃣ точнее - немногие из них программисту приходится реализовывать руками, потому что они используются под капотом во фреймворках и готовых библиотеках.
Например, Dependency Injection или Proxy в спринге — это уже готовые решения. Про такие надо знать, даже если не писал их ни разу самостоятельно, чтобы понимать, как оно работает внутри, и какие могут быть подводные камни
4️⃣ и последнее: паттерны — не rocket science, и при некотором упорстве многие из них можно изобрести самостоятельно, задавшись целью оптимизировать свой код. Но зачем, если их уже придумали за нас?
В общем-то, можно поделиться с собеседующим подобными размышлениями, и это будет неплохое начало ответа. А после добавить конкретики и — ВАЖНО — личного опыта работы с паттернами.
➡️ Ссылаться на личный опыт — в целом выигрышный приём при ответе на любой вопрос. Это покажет, что вы не просто запомнили нужные слова, а действительно понимаете, о чём говорите.
Так вот, после этого надо перейти к конкретике…
а конкретика в следующем посте
👻 Но главное – никогда не говорите про паттерн«синглтон»
Просто не произносите это слово вслух, поберегите себя
На собеседованиях часто спрашивают про паттерны (что-то вроде «Расскажи про паттерны. Какие знаешь?»).
Хочется сразу сделать несколько оговорок:
даже банальный DTO - тоже паттерн
(кстати, описан Фаулером в книге Patterns of Enterprise Application Architecture), т.е. можно сказать, что это архитектурный или же прикладной паттерн, в отличие в паттернов банды четырех, которые сфокусированы на объектно-ориентированном проектировании
Например, Dependency Injection или Proxy в спринге — это уже готовые решения. Про такие надо знать, даже если не писал их ни разу самостоятельно, чтобы понимать, как оно работает внутри, и какие могут быть подводные камни
В общем-то, можно поделиться с собеседующим подобными размышлениями, и это будет неплохое начало ответа. А после добавить конкретики и — ВАЖНО — личного опыта работы с паттернами.
Сделайте это своим паттерном прохождения технических интервью!
Так вот, после этого надо перейти к конкретике…
а конкретика в следующем посте
👻 Но главное – никогда не говорите про паттерн
Просто не произносите это слово вслух, поберегите себя
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥13👍4👏1
Обещанное продолжение про паттерны.
В прошлом посте были рассуждения о паттернах в целом, которыми можно поделиться на собеседовании, чтобы все поняли какой вы пирожочек. А теперь конкретика:
Паттерны делятся на три вида по их назначению:
1️⃣Порождающие (Creational) — решают, как создавать объекты. Например, Factory или Builder
2️⃣Структурные (Structural) — помогают выстраивать структуру системы. Adapter, Decorator - про то, как объекты и классы "складываются" вместе
3️⃣Поведенческие (Behavioral) — отвечают за взаимодействие объектов и разделение обязанностей. Strategy, Observer, Command — про поведение и логику
В Спринге паттерны встречаются повсюду:
➡️ Factory (порождающий) — BeanFactory или ApplicationContext.
➡️ Proxy (структурный) — используется для аспектов, транзакций
➡️ Chain of Responsibility (поведенческий) — фильтры в Spring Security
➡️ Builder (порождающий) — например, в RestTemplateBuilder
🤔 Что касается реализации паттернов руками (добавляете личный опыт к сухой теории) - недавно делал(а) Стратегию - это поведенческий паттерн для формирования семейства алгоритмов. Ситуация такая: есть объект, и некоторая операция над ним может осуществляться по-разному в зависимости от какого-то его параметра.
Можно было сделать через switch-case или if-else, но метод бы раздулся, да и новые варианты, которые точно будут в будущем добавляться, внедрять неудобно (обоснование, почему применение паттерна было целесообразно)
Как именно реализовал стратегию:
1️⃣ Написал интерфейс с одним методом,
например process, означающим ту самую вариативную операцию (метод принимает самого юзера, с которым надо работать, тут для себя вспомните про передачу объектов в джаве – в методе можно изменять существующий объект, пусть void не смущает):
2️⃣ Написал его реализации (имплементации)
(количество реализаций равно количеству вариантов бизнес-логики операции, в этом случае 2 - для обычных и для премиум юзеров):
3️⃣ Как это использовать?
В UserProcessor (или там где у нас выполняется операция над юзером) внедряем интерфейс UserStrategy как поле класса. А раз у этого интерфейса несколько реализаций, то можно передать любую из них (= инициализировать поле strategy объектом любого из этих классов, привет полиморфизм).
После для strategy вызываем тот самый метод process (внутри метода execute), и отрабатывает нужный нам вариант логики (т.е. переопределенный метод из конкретной реализации)
4️⃣ Ну и использовал в логике приложения
Есть юзер Иван, а у него поле со значением "premium". Иван мог бы быть и обычным юзером, но раз он премиум, то передаем в конструктор UserProcessor-а обработчик PremiumUserStrategy, далее передаем в метод execute Ивана
🥳 Получился нормальный ответ про паттерны.
❗️ Но в реальности, как правило, мы пишем приложения на Спринге, поэтому позже расскажу как реализовать такую схему с помощью спрингового DI (dependency injection), там просто кайф. Тогда картина станет полной
В прошлом посте были рассуждения о паттернах в целом, которыми можно поделиться на собеседовании, чтобы все поняли какой вы пирожочек. А теперь конкретика:
и да – представляем, что разговор идет в контексте ООП-design patterns от банды четырех
Паттерны делятся на три вида по их назначению:
1️⃣Порождающие (Creational) — решают, как создавать объекты. Например, Factory или Builder
2️⃣Структурные (Structural) — помогают выстраивать структуру системы. Adapter, Decorator - про то, как объекты и классы "складываются" вместе
3️⃣Поведенческие (Behavioral) — отвечают за взаимодействие объектов и разделение обязанностей. Strategy, Observer, Command — про поведение и логику
В Спринге паттерны встречаются повсюду:
Например, обработка юзера отличается в зависимости от его типа (обычный или премиум) – тут приведите пример из своей предметной области
Можно было сделать через switch-case или if-else, но метод бы раздулся, да и новые варианты, которые точно будут в будущем добавляться, внедрять неудобно (обоснование, почему применение паттерна было целесообразно)
Как именно реализовал стратегию:
1️⃣ Написал интерфейс с одним методом,
например process, означающим ту самую вариативную операцию (метод принимает самого юзера, с которым надо работать, тут для себя вспомните про передачу объектов в джаве – в методе можно изменять существующий объект, пусть void не смущает):
public interface UserStrategy {
void process(User user);
}2️⃣ Написал его реализации (имплементации)
(количество реализаций равно количеству вариантов бизнес-логики операции, в этом случае 2 - для обычных и для премиум юзеров):
public class RegularUserStrategy implements UserStrategy {
@Override
public void process(User user) {
// Логика обработки обычного юзера
}
}
public class PremiumUserStrategy implements UserStrategy {
@Override
public void process(User user) {
// Логика обработки премиум юзера с плюшками
}
}3️⃣ Как это использовать?
В UserProcessor (или там где у нас выполняется операция над юзером) внедряем интерфейс UserStrategy как поле класса. А раз у этого интерфейса несколько реализаций, то можно передать любую из них (= инициализировать поле strategy объектом любого из этих классов, привет полиморфизм).
После для strategy вызываем тот самый метод process (внутри метода execute), и отрабатывает нужный нам вариант логики (т.е. переопределенный метод из конкретной реализации)
public class UserProcessor {
private final UserStrategy strategy;
public UserProcessor(UserStrategy strategy) {
this.strategy = strategy;
}
public void execute(User user) {
strategy.process(user);
}
}4️⃣ Ну и использовал в логике приложения
Есть юзер Иван, а у него поле со значением "premium". Иван мог бы быть и обычным юзером, но раз он премиум, то передаем в конструктор UserProcessor-а обработчик PremiumUserStrategy, далее передаем в метод execute Ивана
User user = new User(“Иван”, “premium”);
UserProcessor processor = new UserProcessor(new PremiumUserStrategy());
processor.execute(user);
Напомню, что пример вам нужно выбрать из вашего домена и приложения, а чтобы рассказывать об этом уверенно, надо действительно сделать это своими руками.
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥13❤3👏2
В последней Intellij Idea 2024.3.4 появилась классная фича -
Нет, там много классных фич, на эта стоит прям на первом месте под заголовком "Главное".
Касается панели "Structure", которая раньше была так себе, а теперь прям вау.
Не буду утомлять текстом, смотрите скрины на примере простенького проекта одного из моих менти (скрин собственно из "What`s New in IntelliJ IDEA" там же в конце)
логическая структура проекта.
Нет, там много классных фич, на эта стоит прям на первом месте под заголовком "Главное".
Касается панели "Structure", которая раньше была так себе, а теперь прям вау.
Не буду утомлять текстом, смотрите скрины на примере простенького проекта одного из моих менти (скрин собственно из "What`s New in IntelliJ IDEA" там же в конце)
6👍13🔥6🤔2❤1👏1