Как стримы дружат с мапами, часть 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
Каким способом объединили бы сеты?
Anonymous Poll
40%
№ 1 (addAll каждого сета в новый сет)
49%
№2 (преобразовать в стримы и слить в один стрим)
11%
№ 3 (этот пункт без пояснений в комментах не принимается🤷♂️ 🤷♂️ 🤷♂️ )