Fury Java
219 subscribers
16 photos
3 links
Красим джейсоны и повышаем надои с яростной джавой 🤗
Реальные кейсы из практики, задачи с собесов, теоретические нюансы

🫨 никаких бесполезных мемов
🥱 никаких тупых постов из-под chatGPT

Чат: https://t.me/fury_java_chat
Автор: https://t.me/Ldv236
Download Telegram
"Как работает Хэшмапа?" - этот вопрос уже стал мемом в контексте обсуждения собеседований по джаве. Он встречается в любом списке "топ-N вопросов по Java" и звучит настолько часто, что многие стали считать это признаком низкого качества подготовки интервьюера к проведению тех.собеса.

Однако, означает ли это, что нам не надо разбираться в хэшмапе? Конечно, надо, потому что это база, а еще хорошая возможность набрать легкие баллы там, где другие теряются в деталях!

📊 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🤝21
📔 Книга "Чистый код" начинается с иллюстрации единственной надежной метрики качества кода - количество "чертей" в минуту 👹/m

Сразу небольшое отступление.
знаете, когда написана эта книга? Шок - 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
Многие знают про плагин Key Promoter X, но я считаю его скорее вредным.
Он вроде бы полезен - подсказывает горячие клавиши, но на самом деле отслеживает далеко не все ситуации, когда вы делаете что-то мышкой и глазами, а могли бы использовать шорткаты. Таким образом, создается иллюзия, что он вам поможет, но помощь эта неэффективна.

Намного лучше взять с сайта жетбрейнс официальный 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👍42🥱1
Всем привет! Пост не технический.
Сегодня завершилось моё преподавание дисциплин Java Core и SQL в замечательной группе мотивированных и старательных учеников. Эти полгода мы тщательно разбирали базу, учились применять её в своих проектах, рассматривали реальные кейсы и готовились к тем сюрпризам, которые ждут на собеседованиях и в работе.
Большое спасибо всем за ваше внимание, активное участие и правильные вопросы, было круто, я и сам учился вместе с вами))

Буду рад увидеть здесь ваши отзывы. Что запомнилось? Что было полезно? Что можно было сделать лучше? В общем, пишите всё, что думаете. Остаёмся на связи! 😊
1🔥9🥰2👏2😢1
Как стримы дружат с мапами, часть 1.
Тренируемся на кошках
.

Когда речь заходит о быстром (и удобном!) доступе к данным в 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🔥63🤔3
Как стримы дружат с мапами, часть 2.
Тренируемся на кошках
.


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👍64🔥4
Fury Java pinned «Иногда в пылу разработки мы забиваем забываем об одной важной детали при работе с реляционными БД — индексах на внешних ключах 🔍Казалось бы, мелочь: зачем заморачиваться с ещё одним индексом, если сам внешний ключ уже прописан? Но именно индекс по FK способен…»
Про паттерны и собеседования (и про тот-паттерн-который-нельзя-называть)

На собеседованиях часто спрашивают про паттерны (что-то вроде «Расскажи про паттерны. Какие знаешь?»).
Хочется сразу сделать несколько оговорок:

1️⃣ когда говорят "паттерны", обычно подразумевают паттерны проектирования или Design Patterns - из известной книги "банды четырёх". Однако, паттерны – более широкое понятие. Есть паттерны микросервисной архитектуры, паттерны concurrency (вроде Thread Pool), паттерны интеграции (например, Circuit Breaker)
даже банальный DTO - тоже паттерн

(кстати, описан Фаулером в книге Patterns of Enterprise Application Architecture), т.е. можно сказать, что это архитектурный или же прикладной паттерн, в отличие в паттернов банды четырех, которые сфокусированы на объектно-ориентированном проектировании


2️⃣ у "банды четырёх" 23 паттерна, но не все из них часто используются (возможно, некоторые даже не выходили за пределы страниц книги 😳)

3️⃣ точнее - немногие из них программисту приходится реализовывать руками, потому что они используются под капотом во фреймворках и готовых библиотеках.
Например, Dependency Injection или Proxy в спринге — это уже готовые решения. Про такие надо знать, даже если не писал их ни разу самостоятельно, чтобы понимать, как оно работает внутри, и какие могут быть подводные камни

4️⃣ и последнее: паттерны — не rocket science, и при некотором упорстве многие из них можно изобрести самостоятельно, задавшись целью оптимизировать свой код. Но зачем, если их уже придумали за нас?

В общем-то, можно поделиться с собеседующим подобными размышлениями, и это будет неплохое начало ответа. А после добавить конкретики и — ВАЖНО — личного опыта работы с паттернами.
➡️Ссылаться на личный опыт — в целом выигрышный приём при ответе на любой вопрос. Это покажет, что вы не просто запомнили нужные слова, а действительно понимаете, о чём говорите.
Сделайте это своим паттерном прохождения технических интервью!


Так вот, после этого надо перейти к конкретике…
а конкретика в следующем посте

👻 Но главное – никогда не говорите про паттерн «синглтон»
Просто не произносите это слово вслух, поберегите себя
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥13👍4👏1
Обещанное продолжение про паттерны.
В прошлом посте были рассуждения о паттернах в целом, которыми можно поделиться на собеседовании, чтобы все поняли какой вы пирожочек. А теперь конкретика:
и да – представляем, что разговор идет в контексте ООП-design patterns от банды четырех


Паттерны делятся на три вида по их назначению:
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 не смущает):

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);


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


❗️ Но в реальности, как правило, мы пишем приложения на Спринге, поэтому позже расскажу как реализовать такую схему с помощью спрингового DI (dependency injection), там просто кайф. Тогда картина станет полной
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥133👏2
В последней Intellij Idea 2024.3.4 появилась классная фича -
логическая структура проекта.

Нет, там много классных фич, на эта стоит прям на первом месте под заголовком "Главное".
Касается панели "Structure", которая раньше была так себе, а теперь прям вау.
Не буду утомлять текстом, смотрите скрины на примере простенького проекта одного из моих менти (скрин собственно из "What`s New in IntelliJ IDEA" там же в конце)
6👍13🔥6🤔21👏1
Про Стратегию в Spring-приложении.
В этом посте писал о том, что в реальности Стратегия чаще применяется не совсем так, как это обычно показывается в книгах по ООП-паттернам.
Вдруг понял, что в тексте это сложно преподнести так, как я хочу, поэтому в творческом кураже запилил первое на канале видео:
📺 Youtube

📺 Rutube

📺 ВК-видео


Код на гитхабе - ReportStrategy

Примечание: в видео не всё учтено (не судите строго, хотя нет - судите, можете даже писать гневные комменты, и чем больше - тем лучше🤔), так что в репозиторий внесены некоторые дополнения с комментариями:
➡️ поля в стратегиях объявлены как final
➡️ добавлена валидация списка стратегий во избежание NPE в рантайме
➡️ используется EnumMap
Please open Telegram to view this post
VIEW IN TELEGRAM
7🔥11👍842
Клиент -> запрос -> сервер. Давайте попробуем "сказать людям" 😄 что веб-разработка всё же несколько сложнее, чем на этой картинке, и начнём с малого

По стрелке request данные от клиента к серверу (клиент-серверная архитектура же) могут приходить разными способами.
Здесь составлю короткую памятку по аннотациям спринга для этих способов

1. @PathVariable

➡️ берём часть пути (например, /users/{id})
➡️ удобно, когда нужно получить динамический сегмент URL (обычно ID сущностей)

2. @RequestParam

➡️ берём Query-параметры (GET url?param=value) - они тоже в урле, как и @PathVariable, но не являются частью пути, а добавляются в хвосте через знак вопроса
➡️ если параметров несколько, они разделяются амперсандом &
➡️ такие параметры могут быть обязательными или нет (required=false)
➡️ используется для простых атомарных параметров, например, для фильтрации (указываются условия для выборочного возвращения каких-то запрашиваемых объектов)

3. @RequestBody

➡️ берём JSON/XML из тела запроса (обращайте внимание на header запроса Content-Type - application/json или application/xml)
➡️ для сложных объектов. Spring автоматически преобразует данные в DTO или другой объект, указанный как параметр метода контроллера

4. @ModelAttribute

➡️ берём query-параметры и собираем из них объект (DTO).
Т.е. данные передаются как при @RequestParam, но мы не хотим использовать их по-отдельности. Конечно, в объекте должны быть поля с теми же именами, что и передаваемые параметры
➡️ часто используется для пагинации и сортировки
(тут есть хитрость для формирования документации вашего восхитительного api в сваггере - чтобы поля этого объекта отображались как отдельные параметры, нужно применить рядом @ParameterObject, но работать будет и без неё)
➡️ если вообще не указать аннотацию перед объектом, то формирование этого объекта будет такое же, как с аннотацией @ModelAttribute, т.е. можно сказать, что она используется по умолчанию
➡️ также применяется для приёма данных в виде "multipart/form-data", если нужно передавать параметры и сразу файлы (загляните в постман, там есть такой вариант в разделе Body, указываются пары Key-Value с типом text/file)

5. @RequestPart

➡️ берём отдельные части из multipart/form-data (а не собираем в объект как с @ModelAttribute)
➡️ полезно для загрузки файлов

6. @RequestHeader

➡️ берём заголовки из запроса (например, User-Agent, Authorization)

7. @CookieValue

➡️ берём куки. Зачем? Потому что можем

🤔🤔🤔
И напоследок еще один каверзный вопрос из реальных собеседований -
можно ли передавать тело в get-запросе? 🤔
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍83🔥3👏1👌1
Неочевидное.

Что первое отвечает джавист на вопрос "для чего переопределять в классах методы 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 так же важна, как и для ключей ХэшМапы

(при этом использование ХэшСета с нашими собственными объектами - не такая уж и редкость).

Только теперь непонятно 🤔 почему на схеме-то этого не показано?
- во-первых, там показано наследование и реализация интерфейсов, а Сет и Мап связаны по-другому, скорее композиционно;
- во-вторых, на этой схеме вообще много чего не показано, она очень упрощена!
Конец.


Хммм, а если есть не только HashSet, а еще LinkedHashSet и TreeSet, в них тоже внутри используется HashMap или...
Но об этом в другом посте
Please open Telegram to view this post
VIEW IN TELEGRAM
3🔥13👍622👨‍💻2
Есть три сета, которые нужно объединить:
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