"Как работает Хэшмапа? " - этот вопрос уже стал мемом в контексте обсуждения собеседований по джаве. Он встречается в любом списке "топ-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
Про Стратегию в Spring-приложении.
В этом посте писал о том, что в реальности Стратегия чаще применяется не совсем так, как это обычно показывается в книгах по ООП-паттернам.
Вдруг понял, что в тексте это сложно преподнести так, как я хочу, поэтому в творческом кураже запилил первое на канале видео:
Код на гитхабе - ReportStrategy
Примечание: в видео не всё учтено (не судите строго, хотя нет - судите, можете даже писать гневные комменты, и чем больше - тем лучше 🤔 ), так что в репозиторий внесены некоторые дополнения с комментариями:
➡️ поля в стратегиях объявлены как final
➡️ добавлена валидация списка стратегий во избежание NPE в рантайме
➡️ используется EnumMap
В этом посте писал о том, что в реальности Стратегия чаще применяется не совсем так, как это обычно показывается в книгах по ООП-паттернам.
Вдруг понял, что в тексте это сложно преподнести так, как я хочу, поэтому в творческом кураже запилил первое на канале видео:
📺 Youtube
📺 Rutube
📺 ВК-видео
Код на гитхабе - ReportStrategy
Примечание: в видео не всё учтено (
Please open Telegram to view this post
VIEW IN TELEGRAM
7🔥11👍8⚡4❤2
Клиент -> запрос -> сервер. Давайте попробуем "сказать людям" 😄 что веб-разработка всё же несколько сложнее, чем на этой картинке, и начнём с малого
По стрелке request данные от клиента к серверу (клиент-серверная архитектура же) могут приходить разными способами.
Здесь составлю короткую памятку по аннотациям спринга для этих способов
➡️ берём часть пути (например, /users/{id})
➡️ удобно, когда нужно получить динамический сегмент URL (обычно ID сущностей)
➡️ берём Query-параметры (GET url?param=value) - они тоже в урле, как и @PathVariable, но не являются частью пути, а добавляются в хвосте через знак вопроса
➡️ если параметров несколько, они разделяются амперсандом &
➡️ такие параметры могут быть обязательными или нет (required=false)
➡️ используется для простых атомарных параметров, например, для фильтрации (указываются условия для выборочного возвращения каких-то запрашиваемых объектов)
➡️ берём JSON/XML из тела запроса (обращайте внимание на header запроса Content-Type - application/json или application/xml)
➡️ для сложных объектов. Spring автоматически преобразует данные в DTO или другой объект, указанный как параметр метода контроллера
➡️ берём query-параметры и собираем из них объект (DTO).
Т.е. данные передаются как при @RequestParam, но мы не хотим использовать их по-отдельности. Конечно, в объекте должны быть поля с теми же именами, что и передаваемые параметры
➡️ часто используется для пагинации и сортировки
(тут есть хитрость для формирования документации вашего восхитительного api в сваггере - чтобы поля этого объекта отображались как отдельные параметры, нужно применить рядом @ParameterObject, но работать будет и без неё)
➡️ если вообще не указать аннотацию перед объектом, то формирование этого объекта будет такое же, как с аннотацией @ModelAttribute, т.е. можно сказать, что она используется по умолчанию
➡️ также применяется для приёма данных в виде "multipart/form-data", если нужно передавать параметры и сразу файлы (загляните в постман, там есть такой вариант в разделе Body, указываются пары Key-Value с типом text/file)
➡️ берём отдельные части из multipart/form-data (а не собираем в объект как с @ModelAttribute)
➡️ полезно для загрузки файлов
➡️ берём заголовки из запроса (например, User-Agent, Authorization)
➡️ берём куки. Зачем? Потому что можем
🤔🤔🤔
И напоследок еще один каверзный вопрос из реальных собеседований -
можно ли передавать тело в get-запросе?🤔
По стрелке request данные от клиента к серверу (клиент-серверная архитектура же) могут приходить разными способами.
Здесь составлю короткую памятку по аннотациям спринга для этих способов
1. @PathVariable
2. @RequestParam
3. @RequestBody
4. @ModelAttribute
Т.е. данные передаются как при @RequestParam, но мы не хотим использовать их по-отдельности. Конечно, в объекте должны быть поля с теми же именами, что и передаваемые параметры
(тут есть хитрость для формирования документации вашего восхитительного api в сваггере - чтобы поля этого объекта отображались как отдельные параметры, нужно применить рядом @ParameterObject, но работать будет и без неё)
5. @RequestPart
6. @RequestHeader
7. @CookieValue
🤔🤔🤔
И напоследок еще один каверзный вопрос из реальных собеседований -
можно ли передавать тело в get-запросе?
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍8❤3🔥3👏1👌1
Неочевидное.
Что первое отвечает джавист на вопрос "для чего переопределять в классах методы equals и hashCode?".
🤔 Если отвечаете как-то иначе - прошу в комменты.
Но дальше может возникнуть вопрос - а разве мы так часто используем мапы, в которых ключи - наши объекты? Обычно же в ключах используем строки или числа, не?😨
И вот тут надо немного углубиться в Java Collection Framework (JCF).
На обычной схеме иерархии коллекций мы видим две отдельные ветки - по Map и по Set, и между ними не обозначено никаких связей . Но давайте зайдем в класс HashSet и посмотрим на банальную штуку - конструктор
Это что же получается - "нет никакого Сета, это всё компьютерная графика"? Получается, да. Сет - это замаскированная ХэшМапа. Которая сохранена в переменную с говорящим названием map:
А метод добавления элемента в Сет выглядит как добавление в мапу простым put:
Но есть нюанс: элемент e вставляется вместо ключа (первый параметр в put), а вместо значения - какой-то PRESENT.
А PRESENT - это просто константа-заглушка, dummy. Просто один самый простой объект Object, который используется во всех values внутренней ХэшМапы:
Итак, из двух обозначенных утверждений
1️⃣ - иквалс и хэшкод нужны для ключей ХэшМапы
2️⃣ - ХэшСет внутри является ХэшМапой и сохраняет элементы как ключи этой внутренней ХэшМапы
мы можем сделать третье утверждение:
(при этом использование ХэшСета с нашими собственными объектами - не такая уж и редкость).
Только теперь непонятно🤔 почему на схеме-то этого не показано?
- во-первых, там показано наследование и реализация интерфейсов, а Сет и Мап связаны по-другому, скорее композиционно;
- во-вторых, на этой схеме вообще много чего не показано, она очень упрощена!
Конец.
Хммм, а если есть не только HashSet, а еще LinkedHashSet и TreeSet, в них тоже внутри используется HashMap или...
Но об этом в другом посте
Что первое отвечает джавист на вопрос "для чего переопределять в классах методы equals и hashCode?".
➡️ Чтобы корректно сравнивать объекты, но для этого достаточно equals, а hashCode - чтобы использовать экземпляры этих классов как ключи в хэшмапе (на то она и "хэш-"). Также между методами equals и hashCode предусмотрен контракт, чтобы это всё правильно работало.
Но дальше может возникнуть вопрос - а разве мы так часто используем мапы, в которых ключи - наши объекты? Обычно же в ключах используем строки или числа, не?
И вот тут надо немного углубиться в Java Collection Framework (JCF).
На обычной схеме иерархии коллекций мы видим две отдельные ветки - по Map и по Set, и между ними не обозначено никаких связей . Но давайте зайдем в класс HashSet и посмотрим на банальную штуку - конструктор
public HashSet() {
map = new HashMap<>();
}
Это что же получается - "нет никакого Сета, это всё компьютерная графика"? Получается, да. Сет - это замаскированная ХэшМапа. Которая сохранена в переменную с говорящим названием map:
transient HashMap<E,Object> map;
А метод добавления элемента в Сет выглядит как добавление в мапу простым put:
public void add(E e) {
map.put(e, PRESENT);
}
Но есть нюанс: элемент e вставляется вместо ключа (первый параметр в put), а вместо значения - какой-то PRESENT.
А PRESENT - это просто константа-заглушка, dummy. Просто один самый простой объект Object, который используется во всех values внутренней ХэшМапы:
// Dummy value to associate with an Object in the backing Map
static final Object PRESENT = new Object();
Итак, из двух обозначенных утверждений
1️⃣ - иквалс и хэшкод нужны для ключей ХэшМапы
2️⃣ - ХэшСет внутри является ХэшМапой и сохраняет элементы как ключи этой внутренней ХэшМапы
мы можем сделать третье утверждение:
🤌 для элементов Сета корректность переопределения методов equals и hashCode так же важна, как и для ключей ХэшМапы
(при этом использование ХэшСета с нашими собственными объектами - не такая уж и редкость).
Только теперь непонятно
- во-первых, там показано наследование и реализация интерфейсов, а Сет и Мап связаны по-другому, скорее композиционно;
- во-вторых, на этой схеме вообще много чего не показано, она очень упрощена!
Конец.
Но об этом в другом посте
Please open Telegram to view this post
VIEW IN TELEGRAM
3🔥13👍6❤2✍2👨💻2
Есть три сета, которые нужно объединить:
Как бы вы это сделали? (тык в опрос ниже + горячо приветствуется обсуждение)
1)
2)
3) другим способом (или одним из указанных, но доработанным)
Set<String> set1 = Set.of("A1", "A2", ..., "A1000");
Set<String> set2 = Set.of("B1", "B2", ..., "B1000");
Set<String> set3 = Set.of("C1", "C2", ..., "C1000");Как бы вы это сделали? (тык в опрос ниже + горячо приветствуется обсуждение)
1)
Set<String> result = new HashSet<>();
result.addAll(set1);
result.addAll(set2);
result.addAll(set3);
2)
Set<String> result = Stream.of(set1, set2, set3)
.flatMap(Set::stream)
.collect(Collectors.toSet());
3) другим способом (или одним из указанных, но доработанным)
pu pu pu
2🤔6👍3🤓2💅1