ym104432846
Вставьте ссылку на видео из Youtube, Rutube, VK видео
Задайте вопрос по видео
Что вас интересует?
00:00:00
Коллекции Java:
  • 1. Не рассмотрены темы очередей, ассоциативных массивов и Mackie
  • 2. Планируется обсудить хэш-код, компараторы и константную модификацию исключений
  • 3. Запланировано начало изучения функционального программирования
00:00:48
Очереди и их особенности:
  • 1. Обсуждаются стандартные разновидности коллекций (списки, множества, очереди)
  • 2. Возможность реализации интерфейса коллекции напрямую допускается
  • 3. При самостоятельной реализации коллекции дополнительные свойства и гарантии контракта отсутствуют
00:01:33
Интерфейсы коллекций и особенности реализации:
  • Коллекция очередей имеет дополнительные методы, позволяющие добавлять и извлекать элементы из разных концов (начало и конец)
  • Очереди с приоритетом нарушают порядок стандартного добавления и получения элементов, предоставляя возможность изменять приоритеты элементов
  • Для работы с очередями рекомендуется использовать специализированные классы, такие как Ray Дек, Priority Queue и двунаправленные очереди, поскольку стандартные коллекции могут иметь ограничения и подводные камни
00:20:09
Ассоциативные массивы и их типы:
  • Рассматриваются различные интерфейсы и классы Java Collections Framework (abstractMap, NavigableMap), включая особенности работы с ними
  • Обсуждаются способы эффективного поиска значений в отсортированных структурах данных и возможности изменения значений в паре ключ-значение
  • Упоминаются методы работы с объектами-копиями и методами-виушками (viewsets, viewvalues, viewentries), которые отражают изменения в исходной структуре данных
00:31:37
Использование итераторов и обход коллекций:
  • Рассматриваются различные подходы к обходу карты (хэштаблиц): использование `entry set`, обход ключей напрямую (`map.get(key)`), применение методов `forEach` и лямбда-выражений
  • Обсуждаются преимущества и недостатки каждого подхода: быстрота выполнения, необходимость вычисления хеш-кодов, возможность прерывания цикла и создание временных объектов
  • Предлагается использовать методы `forEach` и лямбды для оптимизации производительности и упрощения кода, особенно когда ключи не требуются
00:36:44
Работа с ассоциативными массивами и их модификации:
  • 1. Предложено решение использовать `map.entrySet()` для обхода карты, чтобы избежать повторного вычисления хеш-кодов и повысить эффективность работы
  • 2. Обсуждалось применение метода `set` через `entry.set`, позволяющий быстро изменять элементы карты без пересчета хеша
  • 3. Упоминалось, что использование стандартных методов интерфейса карты (`get`, `put`) менее эффективно, чем подход с использованием `entry.set`
00:38:47
Оптимизация работы с ассоциативными массивами:
  • Предложено использовать простую лямбда-функцию для обработки пар ключ-значение, где лямбда принимает значение и возвращает новое значение
  • Для удаления ненужных значений предлагается воспользоваться методом `map.removeIf`, передавая предикат, определяющий условия удаления
  • Рассматривается возможность отказа от использования лямбд и возвращения к старым способам работы с коллекциями (например, Java 1.5), если требуется удалить конкретные значения
00:43:36
Структура данных Multimap:
  • 1. Обсуждаются возможности работы с многозначными ключами (мультимэп), включая ситуации с повторяющимися значениями и поддержкой порядка элементов
  • 2. Рассматриваются сторонние библиотеки Java, такие как Guava и Apache Commons Lang, содержащие реализацию мультимэпа
  • 3. Упоминается возможность самостоятельного создания мультимэпа на основе стандартных возможностей Java 8
00:44:51
Реализация собственных ассоциативных массивов:
  • Разработана методика добавления новых значений в коллекцию (релист), используя подход «ключ-значение»
  • Предложено использовать метод `computeIfAbsent` в Java 8 и выше для упрощения алгоритма работы с коллекциями
  • Рассмотрено использование изменяемых объектов в качестве значений коллекции и возможные проблемы модификации ключей в мэпах
00:49:56
Суммирующие структуры данных:
  • Рассматривается использование структуры данных «сумма (bag)», представляющей собой множество, допускающее повторы элементов, и не учитывающее порядок элементов
  • Предлагается решение задачи подсчета статистики встречаемости объектов с использованием специального алгоритма, основанного на отображении объектов на их количество
  • Упоминаются возможные типы данных для хранения количества объектов (int, long), а также особенности работы с примитивными типами данных
01:07:47
Хэш-коды и требования к объектам:
  • По умолчанию хэш-код генерируется случайно и используется редко, однако рекомендуется переопределять хэш-код для объектов с переопределенным `Equals`
  • Для объектов с примитивными полями существуют стандартные методы (например, `Double.hashCode`) для получения корректного хэш-кода
  • Массивы по умолчанию используют случайный хэш-код, основанный на адресе памяти, и требуют явной переопределенной реализации метода `hashCode` для сравнения по содержимому
01:13:50
Внутренние и внешние компараторы:
  • Обсуждалась тема реализации внутреннего способа задания порядка объектов через интерфейс компаратора
  • Упоминалось использование внешнего компаратора для задания альтернативного порядка объектов, если невозможно изменить объект или задан естественный порядок
  • Рассматривались рекомендации по соблюдению правил поведения методов компаратора (антисимметрия, транзитивность, консистентность цикла)
01:26:03
Практика написания компараторов:
  • 1. Обнаружена ошибка сравнения объектов по булевскому полю, приводящая к дублированию элементов в множестве (сет)
  • 2. Предложено использовать стандартный метод `Boolean.compare` для корректной реализации сравнения булевых полей
  • 3. Указано на проблему некорректного поведения метода сортировки при наличии больших чисел и особых значений типа `NaN`
01:29:38
Сложные компараторы и комбинации условий:
  • 1. Предложена стандартная методика сравнения объектов по нескольким полям (например, сначала сравниваются имена, затем возраст)
  • 2. Если объекты имеют одинаковые значения первого поля, сравнение продолжается по следующему полю
  • 3. В случае совпадения значений всех полей используется изначальное (исходное) значение объекта
01:30:25
Комбинаторы компараторов:
  • 1. Предложено использовать лямбда-функции (компараторы) для внешнего сравнения значений двух объектов, поскольку компараторы обычно не содержат внутреннего состояния
  • 2. Для реализации внешнего способа обращения к объектам добавлены аксессоры (get name, get age)
  • 3. Комбинаторы методов позволяют строить цепочки сравнений, где сначала сравниваются объекты по имени, а затем по числовому полю возраста
01:32:28
Создание кастомных компараторов:
  • Принято решение
  • Использовать специальные компараторы (например, кейс-инсенситив-фордер), чтобы сравнивать имена пользователей без учета регистра букв
  • Определено поведение системы
  • Если пользователь вводит имя вида `нал`, сравнение происходит сначала по полю имени, а затем по возрасту
  • Рекомендовано избегать ошибок
  • Избегать модификации объектов, используемых в качестве ключей в хэш-картах (`HashMap`), поскольку изменение объекта ведет к некорректной работе карты
0: Всем привет, у нас осталось некоторое количество материала по коллекциям, в частности, мы не поговорили про очереди, про ассоциативные массивы или mackie.
1: Вот ещё хотелось бы немножко затронуть тему хэш кода, поговорить про компораторы и немножко про конан modification эксепшен. Хотя на вопросе после прошлой лекции я уже освещал эту тему.
2: Вот. Кроме того, наконец у нас запланировано начать функциональное программирование, но, возможно, мы не успеем. В общем, будем стараться покрыть хотя бы вот эти темы. Давайте двигаться.
3: Помимо списков и множеств, то есть лист и сет, есть ещё 1 стандартная разновидность коллекции, которая называется очередь или q, в принципе.
4: Вам никто не мешает реализовывать интерфейс коллекции напрямую, но тогда никакие дополнительные свойства контракта вы не обещаете в этой коллекции. Иногда это в принципе полезно.
5: Реализовать коллекцию напрямую. Просто у вас коллекция, оно, вы не говорите ни про её упорядоченность, ни про наличие в ней повторных элементов, ничего не говорите, просто коллекция и коллекция. Вот, вот.
6: Есть ещё такая разновидность, называется очередь, у неё есть все стандартные методы коллекции, но помимо них также добавлено набор дополнительных методов, которые позволяют вам соответ.
7: Добавлять элементы в очередь и брать элементы из очереди, добавлять это, соответственно, предполагается как бы в конец очереди, а брать это из начала, ну на самом деле это не
8: Всегда так, потому что у вас может быть, например, очередь с приоритетом, то есть интерфейс очереди вообще, он несколько более абстрактный, он просто говорит, что вы добавляете в очередь и берете. Вот, то есть стандартно в очереди действует фест и last.
9: Соответственно, 1 элемент, который вы a festa festa, да, 1 элемент, который вы добавили, он же будет 1 и получен.
10: И наоборот, тот, который добавили последним, он будет последним и получен. Но если у вас очередь с приоритетом, то это, вообще говоря, не соблюдается. Вот для того, чтобы работать с очередью. Во первых, есть стандартный метод коллег.
11: Ed, который добавляет элемент, то есть в очереди просто он же и переиспользуется. Есть метод remove, который удаляет элемент, но этот метод remove он не принимает.
12: Никаких параметров. То есть в коллекции, если вы помните, можно удалить элемент, передав, собственно, этот элемент, который мы хотим удалить. А в очереди дополнительный метод remove без параметров, который удаляет и, соответственно, возвращает тот элемент, который мы
13: Вытащили из этой самой очереди. То есть обычно это тот элемент, который был добавлен раньше всех. И, наконец, есть метод поглядеть, то есть это посмотреть какой элемент как бы
14: В хвосте очереди торчит 1 кандидат на удаление, но при этом не удалять его вот это метод, элемент и предоставляются 3 дополнительных метода.
15: И пик, которые, собственно, делают все тоже самое, но они по другому реагируют на исключительную ситуацию, то есть в случае с методами ремув и элемент
16: Исключительная ситуация это понятно, что это если у вас очередь пустая, в этом случае, ремув элемент, кидают исключения, а вот методы пол и пик возвращают null в связи с этим хранение налов.
17: В очередях это вообще такая очень странная история. Ну, в целом, налы с коллекциями, они не всегда хорошо дружат. И с этим надо быть крайне осторожным, потому что там очень много подводных камней, то есть просто так брать налы и складывать в произвольную коллекцию. Ну как
18: Иногда можно, но надо внимательно читать документацию. Ну, например, там в релист у вас никаких проблем не будет. Если вы налы, складываете и налы достаёте из него. Вот. А с очередями, как правило, большинство очередей не поддерживают.
19: Налы конкурентные очереди, они прям по контракту налы поддерживают.
20: Вот немного может быть непонятна ситуация, чем отличается эд и off.
21: Но интерфейс очереди, он такой достаточно абстрактный, и бывают особенно в конкурентном многопоточном программировании всякие ограниченные очереди, которые
22: Соответственно, ну, не позволяют вам хранить, например, более определённого количества элементов. И в этом случае, если вы упёрлись в соответствующую границу, то метод эт или офа, они могут работать
23: По разному они могут, соответственно, либо вернуть офа будет возвращать false, а это будет кидать исключения.
24: Но с обычными н. Многопоточными очередями метод о. Особо не нужен вот номенклатура, честно говоря, довольно странная её бывает вообще сложно запомнить, в частности, там легко перепутать пол или mov, потому что у них сигнатура Одина.
25: Одинаковая, то есть возвращаемый тип одинаковый. Просто 1 иногда возвращает налы, а другой кидает исключения. Поэтому тут надо быть осторожным. Если сомневаетесь заглядывайте в документацию более такой продвинутый интерфейс, это dec.
26: Или двунаправленная очередь, которая позволяет вам оперировать обоими хвостами, соответственно добавлять и удалять и в голову, и в хвост.
27: Тут, помимо вот тех методов, которые есть в обычной очереди, добавлены ещё явные методы с суффиксами фест и last вот, ну, вместо элемента они называются get first get lost ну и тут как-то несколько более.
28: Понятно. Хотя все равно та же проблема там вспомнить рему фест и пол фест, какой из них возвращает null, какой кидает исключение вот. Но главное, если вы пользуетесь интерфейсом дэк, он расширяет интерфейс кью, то есть в неё.
29: Вот есть тоже методы типа remove или ed, но в этом случае крайне не рекомендуется ими пользоваться, потому что тут уже вообще легко запутаться, а в конец или в начало он добавляет, то есть понятно, что метод add это.
30: Синоним метода add lst, а метод remove это синоним метода ремув фест вот, но если вы пользуетесь интерфейсом дэк лучше про это не пытаться запомнить, а просто пользоваться методами суффиксами они длиннее, но зато гораздо.
31: Да, понятней. Стандартные реализации очередей есть ray дек собственно это сразу дэк. То есть это изменяемая очередь общего назначения. Даже если вам нужна просто очередь, вас не интересует вот эта вот штука
32: С двунаправленностью вы все равно создавайте рейде хороший, там используется кольцевой буфер. То есть, если вы, например, в 1 конец часто добавляете с другого конца, часто удаляете, то у вас внутри
33: Ничего не будет перевыделяться память у вас там будет просто переиспользоваться один и тот же буфер, и только когда количество элементов в очереди увеличится больше, чем этот размер буфера, тогда уже будет перевыделены. Память. Вот, то есть
34: Сам факт наличия дополнительных операций, то, что вы можете с хвостом и с головой работать, он не создаёт вам никаких накладных расходов, поэтому пользоваться редек как обычной очередью ничего страшного в этом нет.
35: Спокойно пользуйтесь и точно также вы можете пользоваться radek как стеком, то есть если у вас задача, например, first in last out как раз то есть добавлять в конец и брать из конца, и с началом вы никак не взаимодействуете.
36: То почему бы и нет тоже пользуйтесь рей деком, просто берите интерфейс дек и из него используйте там только add lst remove ласт и get lost, а методы ест не используйте вообще и есть проорити кью.
37: Это изменяемая очередь с приоритетом. Если у кого-то были там алгоритмы или какая-нибудь дискретная математика, то вы можете знать, что есть такая структура данных. Очередь с приоритетом, она реализуется на другой.
38: Структуре данных под названием куча. Вот, и там такая довольно интересная. За этим стоит математика. Ну суть в том, что вы в неё можете складывать элементы, сравнимые между собой, и каждый раз доставать как бы сам.
39: Лучший элемент, то есть складываете в произвольном порядке, а достаёте всегда самый лучший. При этом она не поддерживает внутри себя все элементы в сортированном состоянии, то есть
40: Есть, например, присет это множество сортированное, которое автоматически всегда сортирует все элементы, а проорити кью он не поддерживает внутри себя сортированные элементы. Например, если вы итератором её
41: Пытаетесь обойти, вы обнаружите, что они там не сортированы, но при этом, если вы будете по 1 доставать элементы, то вы всегда будете доставать самый наилучший. Вот, то есть, если вы как бы
42: И достаёте довольно часто, но вам не нужно обходить то tq может быть быстрее, чем трисет ну кроме того, в pretty cure возможны дубликаты, то есть вы можете одинаковый элемент несколько раз держать.
43: Вот это важно запомнить, что не надо вообще использовать, ну или как бы стараться избегать по максимуму. Есть довольно старые коллекции, которые появились в джаве 1 0, они просто исторически
44: Присутствуют и по причинам совместимости они до сих пор не выкошены ну, кроме того, они проросли в некоторые api, например, в стандартную библиотеку swing там в каких-то методах.
45: Прям в сигнатурах они это к вопросу о том иногда, что не стоит реализации конкретных классов пропихивать в сигнатуры, а стоит ограничиваться достаточно абстрактными интерфейсами, потому что Инна
46: Вы реализацию подменить не сможете, и у вас все будет, вы как бы будете завязаны на эту реализацию навсегда. Вот, во первых, есть более старый подход к обходу коллекции. Это нумере шен.
47: Его не стоит использовать, вместо него есть трейта, ну, как бы по возможности избегать. Понятно, что если вы столкнулись с каким-то крайне старым api, который вам вернул нумере, и нет никаких вариантов ну ладно, используйте нумере шен там есть.
48: Просто 2 метода типа хазм элементс и next element по моему вместо has next next.
49: А так, в целом он не сильно отличается, но единственное, что через умерен вы не можете удалять элементы, то есть метода как-то remove, там нету. Но есть адаптер, у энумерейт есть метод, точка с итерете, что ли, то есть при необхо.
50: Необходимости вы можете нумере шен превратить в итератор, но только он удалять не будет и дальше пользоваться как отератором. Есть старые коллекции типа вектор. Вместо него используем рей. Лист есть.
51: Старый интерфейс дикшенери, вместо него используем мэпп, старый интерфейс хэш, а старый класс хэштейбл. И вместо него надо использовать hashmap. Вот. Ну и есть старый класс стек. Вот со стеком некоторые люди запинаются.
52: То человек говорит, ну, мне же нужен стек. И вот в стандартной библиотеке есть stuck, но это вот как раз устаревшая реализация, которая надстройкой над вектором является, то есть, если вам нужен стек, на самом деле, используйте адек, ну и используйте интерфейс дэк.
53: Потому что в нём есть все необходимые методы и даже чуть больше, чем плохи вот эти вот старые вектор, стек и хэштейбл.
54: Ну, помимо того, что у них реализация, она не оптимизировалась с годами, и она в среднем будет медленнее и вызывать некоторые трудности, там ещё и все методы синхронизированы, и эта синхро
55: Организация, она может вам добавлять довольно неплохие накладные расходы в тех случаях, где она не нужна, а не нужна она на самом деле в 99 процентах случаев. То есть просто представление о многопоточном программировани.
56: Там в 95 году они существенно отличались от того, как правильно писать многопоточные программы. Сейчас вот, ну и сделать правильную, хорошую, многопоточную программу с помощью вот этих векторов хате.
57: У вас, скорее всего, все равно не получится, поэтому причин их использовать обычно нету. А если вы не используете в нескольких потоках этот вектор или хэштейбл, то вы просто получаете накладные расходы.
58: На синхронизацию, причём довольно долго, в джаве был такой механизм байт локинг, который позволял эти накладные расходы нивелировать, но поддержание его слишком дорого обходится в виртуальной машине.
59: Поэтому в самых свежих версиях джавы этот механизм удаляют и, скорее всего, накладные расходы на вот эту вот синхронизацию в новой джаве они будут только больше. То есть у вас код с этими коллекциями может
60: Стать медленнее, когда вы перейдёте на более свежую джаву. Вот, и есть ещё такой немножко отдельный пример. Это линкед лист, это не вот наследие джавы. 1 0. Он появился в джаве 1 2 вместе с остальными коллекциями, как релист.
61: Там и так далее. Вот. Но это список, который базируется не на массиве внутри себя, а он базируется на связном списке. То есть там для каждого элемента
62: Даётся некий объектик, который хранит собственно, ссылку на элемент, ну и также ссылку там на предыдущий, на следующие элементы. Суть в том, что эта штука, она, во первых, страшно медленная, во вторых, она страшно прожорливая по памяти, то есть в среднем
63: Linked list жрёт в 4 раза больше памяти, чем ray лист, содержащий то же самое число элементов вот иногда люди пытаются придумать какой-нибудь пример кода, где вот линкед лист мог.
64: Быть более оптимален. Ну то есть понятно, да, если вы, например, в середину а листа попытаетесь вставлять элементы, то вам придётся весь хвост массива двигать. То есть у вас представляете, да, у вас в памяти есть некий массив, ну,
65: Положим, там он с запасом выделен и, соответственно, вы пытаетесь в середину вставить новые элементы. Вот в этом случае вам придётся при вставке каждого нового элемента сдвигать весь хвост, то есть копировать там возможно, большой кусок памяти, тогда как в Линке
66: Лист, если вы вставляете в середину, вам надо только там пару ссылок исправить и все остальное автоматом заработает. Вот. То есть можно попытаться придумать теоретически такую ситуацию, когда вроде бы линкит лист будет быстрее, но на самом деле учит
67: То, что линкен лист жрёт в 4 раза больше памяти. Практически всегда любую такую задачу можно переписать просто полным копированием ре листа. Ну то есть, например, вам нужно сделать много нетривиальных вставок в середину. Вы прос.
68: Берете исходный релист, создаёте новый туда копируете там часть элементов, часть новых вставляете и отдаёте наружу новый релист, а про старый забываете. И это все равно будет эффективнее, чем аккуратно пытаться исправлять все ссылки.
69: В линкедлисте. Ну а на самом деле я часто встречал ситуации, когда люди вот говорят, что там линкед лист эффективнее, и приводят пример какого-то алгоритма, на самом деле, даже без всяких моди.
70: Если его померить, оказывается, что линкит лист абсолютно не эффективен в данном случае. Ну, то есть там простой пример, если вы будете вызывать у линкит листа метод точка эд, вставить, да, и вставить вот этот вот с параметром в середи
71: Там будете указывать, куда вставить. Там есть вариация, когда вы говорите, ну, типа, вставить в позицию 1000, например, да, AUVASVSEGO2000 элементов, и человек будет говорить, ну, вот эта вставка проходит быстро, то при
72: Проверки при измерении производительности может оказаться, что на самом деле в ray лист эта вставка проходит быстрее почему так происходит, потому что если вы хотите в linked list в 1000 позицию вставить новый.
73: Элемент вам нужно эту, этот 1000 элемент сперва найти, а чтобы его найти, вам нужно по этому связанному списку пробежаться от начала. То есть вам нужно разыменовать 1000 указателей и а в листе ничего.
74: Именовывать не надо. У вас есть массив, вы просто берете и отсчитываете 1000 элементов от начала. То есть вы точно знаете, как бы смещение в памяти потом, да, вам придётся скопировать хвостик, но копирование хвостика может быть быстрее, чем разименование 1000 указателей.
75: Вот, поэтому, да, с илистом можно аккуратно там создать лист итератор и этим лист итератором там бегать. Но все операции, которые работают с индексом, они будут заведомо медленнее, потому что вам нужно будет бегать там от начала.
76: Или от конца списка, чтобы найти соответствующий узел. В общем, про это все можно забыть. Просто помните 1 вещь, что линкит листом пользоваться не надо. Никогда нет ни 1 там ситуации в практическом программировании, когда линкед лист вам бы пригодил.
77: Я это подчёркиваю, потому что он во многих учебных материалах, там в ответах на стековерфлоу встречается даже в каких-то, ну, вроде бы приличных учебниках можно встретить примеры с линкит листом. Ну, на самом деле, вот практический опыт показывает.
78: Что он не нужен и даже его автор, по моему, это был джош блох. Он когда-то в твиттере писал, типа ребята, а есть какой-то пример вообще использования линки листа? Я вот его создал, но мне он ни разу не пригодился.
79: И люди в ответах сошлись, что, ну да, вроде он не пригодился. Вот на этом мы заканчиваем с коллекциями и поговорим про ассоциативные массивы, это немножко отдельная иерархия классов, то есть
80: Этот map, он не является коллекцией на эту тему. Кстати, тоже были неоднократно споры, типа, почему? Ну на самом деле в этом есть некоторая логика.
81: Отдельный интерфейс у него, соответственно, отдельный там набор методов, но он тем не менее очень тесно связан с коллекциями и поэтому появился одновременно развивается тоже одновременно. Поэтому, конечно,
82: Изучать их тоже надо одновременно. Есть абстрактный класс abstract мэп, который вы можете использовать, если вы реализуете свою собственную мэпу. Вот. Но есть также дополнительные сорта мэпп и navigate интерфейсы, кото
83: Которые вам позволяют ходить по сортированной мэпе более эффективно, там куча дополнительных методов и реализовать вручную то map или навей мэпп. Ну это весьма непростая задача, на самом деле.
84: So mmap на самом деле не сильно нужный интерфейс это артефакт истории он появился в джаве 1 5, а навёл мэпп в джаве 1 6, то есть там просто добавили новых возможностей, а так как тогда нельзя было добавлять дефолт, методы в интерфейсы.
85: Пришлось создавать новый интерфейс, чтобы не сломать совместимость. Вот. То есть, в принципе, сорт от map он не сильно нужен. Можно только navigare всегда пользоваться вот основные реализации. А, ну и важный момент. Есть ещё такая штука. Мэпп, энн.
86: Это на самом деле такая пара, которая состоит из ключа и значения. То есть, собственно, ассоциативный массив можно себе представлять как коллекцию вот таких вот пар, ключ значения, ну на самом деле, он коллекция этих пар.
87: Не является. Чтобы получить эту коллекцию, нужно вызвать метод reset. Мы про это сейчас поговорим. Эта пара интересна тем, что ключ у неё считается неизменяемым всегда. А вот значение в некоторых ситуациях.
88: Может быть изменяема, хотя на самом деле тоже пользоваться этой возможностью, ну, крайне редко приходится.
89: И некоторые используют этот мент просто как, ну, такую абстрактную пару, но на самом деле это пара, которая несёт вполне конкретную семантику. То есть у вас 1 значение это ключ, a2 значение это, собственно,
90: Что-то что связано с этим ключом. Вот есть стандартные реализации. Так, map симпл энтри это изменяемая пара ну в смысле что у неё значение можно поменять и abstract мэпп симпл, иммьютабл энтри это
91: Неизменяемая пара, а также в 9 джаве появился фабричный метод map от entry который просто ну он похож на simple YouTube энтри, но у него отличие то что он аллы не приемлет как раз вот вопрос если вы
92: Хотите работать с наллами, то надо внимательно читать документацию. Симпл мьюта допускает налы в качестве ключа или в качестве значения. A map. Донни сразу вам от exception кинет. Вот.
93: Ну и, соответственно, реализации хэш мэп это так, а давайте мы про реализации потом поговорим, сейчас поговорим, собственно, что мы можем делать с этим интерфейсом? Ну, во первых, можно посмотреть
94: Размер как в любой коллекции, количество собственно этих пар ключ значения также is empty, если вам нужно только проверить на пустоту, обязательно используйте is empty вместо получения размера.
95: Вместо метода контейнс, который есть в коллекции, тут 2 метода контейнс ки и contains well you. И соответственно они тоже принимают обжект. То есть там тут те же самые соображения, что с коллекциями можно
96: Случайно передать что-нибудь не то. И тогда большинство реализаций просто фол свернёт, и вы можете не замечать ошибку. Ну хотя и de вас обычно предупредит вот большинство реализаций мэпп подразумевает, что у вас есть как
97: Какой-то относительно быстрый поиск ключей по разному реализован либо хэш таблица, либо какое-нибудь красно чёрное дерево, либо ещё что-нибудь. Ну в некоторых случаях это может быть просто массив там, если это enum мэпп.
98: Что-нибудь, короче, какая-то быстрый алгоритм поэтому контейнс ки, как правило это быстрая штука. Ну либо за константное, либо за логарифмическое время работает. Однако контейнс велю практически у любой реализации мэки это
99: Линейный поиск, то есть обычно они никак не индексированы значения. И чтобы найти, определить, есть ли определённое значение в мэпе, то вам придётся просто итератором обходить её по порядку. Вот, то есть тут
100: Оптимизировать что-то редко удастся. И поэтому коневе лучше избегать. То есть если вам она часто требуется, скорее всего вы просто неправильно выбрали структуру данных, вам нужно что-то другое как-то по другому её организовать. Гетт.
101: Соответственно, получить значение, которое соответствует данному ключу, возвращает null, если у вас ничего не соответствует данному ключу, но также возвращает null, если у вас данному ключу соответствует.
102: Null. И тут тоже надо быть очень осторожным, потому что в некоторые мэпи, например, в hashmap, вы можете положить в качестве значения null. Но тогда, когда вы вызовете Гетт и получите в результате нал. Вы не знаете, это вы достали нал или.
103: Там просто не было соответствующего значения, чтобы в этом убедиться. Там можно вызвать контейнс ки, и он вам все-таки различит эти 2 случая. Ну либо вызвать гетто, дефолт, гетто, дефолт чуть ниже по списку. Это такая
104: Более продвинутая версия, которая в случае отсутствия значения, она вам вернёт не null, а вот то, что вы передали 2 параметром в качестве default велю, то есть вы можете сказать, что при отсутствии значения вот мне нужно
105: Что-нибудь другое ну в общем да, хранить налы в mac это так, ходить по краю пут, положить новое значение, если мэппа изменяемая, то есть в принципе тоже да не гарантии.
106: Serious, что любая мэппа, которая вам попалась в руки, она изменяемая ремув, соответственно, удалить по ключу, есть также ремув, удалить вот конкретную пару ключ значения. То есть, если у вас данные
107: Ключ есть, но у него другое значение то рему вот этот вот ключ значение он не удалит есть put all когда вы хотите влить 1 мэпу в другую, клиер полностью очистить и вот такие вот связи с коллекциями.
108: Кейсет, он вам возвращает множество всех ключей. То есть в мэпе предполагается, что у вас ключи уникальны и, соответственно, кейсет вам натурально возвращает множество. Вот. Ну, тут иногда возникает вопрос, а по какому критерию они уникальны?
109: Там это тоже не всегда так просто, но обычно они все-таки уникальны по иквел вельюс это как раз пример такого метода, который возвращает просто абстрактную коллекцию, то есть значение
110: Могут быть не уникальные, они также могут быть неупорядочены мы ничего особо про них не знаем мы знаем просто, что это вот некоторая коллекция, поэтому это вот как раз хороший пример вот этого самого базового типа collections и entry.
111: Это, собственно, представление мэпи в качестве множества вот этих самых энтри, вот этих пар, ключ значения. То есть, если вы хотите представить мэку в качестве множества, пожалуйста, в качестве
112: Используете Анисет и дальше пользуетесь как коллекцией. Интересно, что вот эти вот все 3 метода кисет, вельюс и энтрит, они возвращают вьюшки. То есть это никакие не копии ваших объектов и любые дальнейшие
113: Изменения в мэпе, они отразятся вот в этом кейсет и на самом деле наоборот вы можете взять, например, этот кейсет, начать из него удалять ключи или взять Велюс, начать из неё че то удалять или энтрит из неё че то удалять и
114: И у вас все эти удаления опрокинутся в исходную мэпу, то есть у вас исходная мэппа тоже изменится после этого.
115: Есть ещё вот такой метод пут и фесен, который позволяет вам поместить значение, то есть обычный пут. Если значение уже было, он его перезаписывает. А put и фесен, он, соответственно, если значение уже было
116: Перезаписывать он его не будет. Вот. Но тут тоже есть подстава с налом. То есть, если значение уже было, но оно было равно налу, то пути его все равно перезапишет я
117: Не уверен, что я даже знаю, почему так было сделано. То есть эти методы добавили в 8 джаве. Кажется, до этого они были только в конкурентных мэппа, потому что в конкурентных это прям важно, чтобы операция
118: Была атомарной, потому что если вы до этого проверили, есть значение или нет, а потом, например, если нет, то положили, то у вас могло случиться, что в промежутке другой поток тоже что-нибудь положил, и вы его все-таки
119: Епсон для конкурентных мэпп был обязателен, а в конкурентных мэппа налы вообще не не разрешены, поэтому, возможно, вот это связано из за этого, из за того, что
120: В конкурентных мапках не разрешены, когда его перенесли в обычные мапки, сделали такую странную логику. Вот есть ещё методы реплейс, которые вам позволяют заменить существующее значение.
121: K well, you просто заменяет если оно там есть любое, а если там ничего не было, то он ничего и не сделает, и вариант с 3 аргументами, соответственно, заменяет, если старое значение вот было равно кон.
122: Тому, что вы указали. Но эти методы крайне редко нужны в реальной жизни у интерфейса энтри вот 3 метода основных геткей. Гетт велю и сет велю сет велю может не работать, если у вас энтри
123: Неизменяемая ну вообще entry она может быть привязана к той самой мэке, из которой она получена то есть если вы сделали например энтри сет, потом из этого энтри сет достали какую-то entry и у него вызвали.
124: То изменится соответствующая запись в исходной мэпе, из которой мы этот сет получили. Давайте посмотрим, сценарии использования мэпомоои часто нужны?
125: И, собственно, посмотрим, как правильно делать и как неправильно ну вот самый популярный случай с мэппа это когда вы пытаетесь её обойти, например, у вас есть мэппа, которая отображает строки на cheese.
126: Ну почему нет? Вам её вот дали, и вы хотите просто вывести все пары, которые в ней содержатся.
127: Вы можете обходить её с помощью map дот энтри сет вот так вот, как сверху написано. Соответственно, вы получаете объект энтри и из него потом можете извлечь энтри гет ки энтри Гетт велю и дальше использо.
128: По своему усмотрению. Ну, немножко страшненько выглядит вот этот вот тип мэпп донни в угловых скобочках, стринг, запятая интеджер, но если у вас java 10 или старше, вы можете вместо него писать просто Вах, это как раз тот.
129: 1 из таких случаев, где использование var прям очень рекомендуется если во многих случаях это такая стилистическая штука, некоторые любят, некоторые не любят, то здесь мне кажется писать war.
130: Entry вполне разумно, но многим все равно не нравится, что у вас есть вроде бы этот промежуточный объект энтри, из него нужно делать get key get well, you какой-то лишний код и люди пытаются написать вот как снизу мэпп дот кейсет.
131: Мы обходим кейсет, и тогда у нас переменная цикла это собственно ключи, а значение мы получаем через me do get, и это в принципе тоже работает все нормально, но проблема в том, что это может быть суще.
132: Медленней, потому что энтри сет, как правило, переход от предыдущей энтри к следующей. Он реализован очень быстро. Ну, вам не нужно ничего там вычислять. Никаких хэш кодов ни по каким
133: Там странным деревьям бегать. Вы просто там переходите по какой-нибудь ссылочке. Вот, ну, либо там следующую запись в хэш таблице ищите, ну просто следующую по порядку. А если вы делаете get to во многих случаях это медленнее, то есть
134: Если это хэш таблица, то вам нужно реально посчитать хэш код вот этого ключа. Он не всегда у вас заранее готов. Надо там сделать некоторую математику, пройти по хэш таблице, а если там есть коллизии, то соответственно,
135: Ещё пройтись по соответствующему дереву коллизий, найти подходящее значение и так на каждой итерации цикла. То есть эта штука, она может оказаться существенно медленнее. А если злой человек вам специально подгото.
136: Такую мэпу, в которой много коллизий, то можно даже под DDoS атаку попасть вот с этим там активно поборолись как раз в 8 джаве, когда прикрутили туда красно чёрные деревья в том случае, если много коллизий.
137: То есть у вас мэпп Гетт, он никогда не станет линейным по сложности, но может стать все-таки логарифмическим, но все равно это может быть медленнее, чем если вы будете обходить через Aris и есть alternative.
138: Вариант. А, ну во первых, есть вариант, если вам ключи вообще не нужны, может такое вообще то случиться. Некоторые люди вот настолько, как бы у них шаблон с кейсем в голове застрял, что они даже не думают про метод.
139: Что он вообще существует, однако он существует. Если вам ключи в принципе не нужны, вам нужны только значения, то вы можете изящно обойти с помощью вельюс и у вас будет ещё короче код. Вот. Ну и есть вариант функции.
140: Тональном стиле это воспользоваться методом эп дот фо рич. То есть сверху это показано как бы такой java 7 стайл, когда вы передаёте туда анонимный класс, который принимает ключ. Значение, но
141: Мы потихонечку привыкаем к лямбдам. И вот вместо вот этой всей Страшной жути можно написать гораздо более простую конструкцию. Вы просто пишите в скобочках, кей, запятая ввёл ю это параметры лямбды и после
142: Этого пользуетесь ими как угодно. Это красиво, изящно. При этом у вас не создаётся итератор, и в некоторых случаях этот фои, даже он более оптимально реализован. Ну то есть для некоторых
143: Реально нужно создавать вот эти вот объекты энтри, то есть они в готовом виде там могут и не лежать внутри. А если вы делаете мэпп дот фо рич, то объекты энтри у вас не создаются на каждой итерации, вам туда напрямую ключ значение передают.
144: Соответственно, поэтому это будет более оптимально, но тут есть недостатки. Ну, к примеру, вы не можете эту операцию прервать. То есть, если у вас обычный вот цикл for
145: Вы можете там сделать break из него, скажем, а вот, форич, вы прервать можете, ну, разве что кинув исключение, но это некрасиво и имеет свои проблемы. То есть вы не можете каким-то таким легальным способом сказать, вот я
146: Нашёл то, что искал и больше не хочу. Поэтому, ну, это как бы некоторое ограничение вносит в применимость вот этого метода точка форич, но когда он применим, вполне можно использовать. Давайте посмотрим.
147: Некоторые сценарии с модификацией эпок. Ну вот, например, такая история. У вас есть мэппа, которая отображает, ну какие-то ключи на какие-то значения, и мы хотим все значения.
148: По примять, то есть обрезать пробелы в них, если там есть пробелы в начале или в конце, мы хотим их обрезать. И опять же, вот в таких вот сценариях часто можно видеть, что люди вот выучили небольшую часть интерфейса.
149: Выучили хорошо и стараются любую задачу к ней свести. И это в принципе работает. Но это не оптимально и не очень красиво. Когда вы знаете весь интерфейс map, вы можете решать стандартные задачи гораздо более гибко. Ну вот здесь вот такой пример, опять же,
150: Человек пытается обходить мпку с помощью кейсет, потом, соответственно, берет ключи с помощью get и после этого складывает новое значение с помощью пут. Это работает, но это ещё более неэффективно, чем просто обход с помощью get, потому что
151: Мы на самом деле 2 раза делаем лукап этой мэпе. То есть сперва мы находим соответствующую пару, когда вызываем метод get там, или с помощью хэш таблицы, или с помощью красно чёрного дерева.
152: А потом мы ту же самую работу повторяем, когда вызываем метод пут, то есть мы снова спускаемся или по дереву, или по хэш таблице, снова вычисляем хэш код и как бы 2 раза делаем вот это вот дело.
153: Хотя, в принципе, необходимости в этом вроде бы не было. Ну вот есть вариант сентри сет, да, вы можете взять map энтри сет, обойти по энтри и использовать set the. И это, в принципе, работает никаких
154: Проблем с этим нет, и это гораздо быстрее. То есть вы вообще ни разу там не вычисляете хэш коды, вы просто берете там и итерируетесь по хэш таблице. Однако есть ещё более красивый способ это воспользоваться методом.
155: Ооо который вам по сути дела весь этот алгоритм инкапсулирует и вам вообще не нужно думать про entry, вы можете написать простенькую лямбду, ей передаётся все пары ключ значения и результат этой лямбды это новое значение.
156: Ну и, собственно, ключ нам не сильно нужен, а значение мы просто примем. И получается вот такой вот изящный совершенно код.
157: Другой тоже стандартный алгоритм. Это удаление ненужных значений. Ну вот, например, у нас есть мэппа, то есть удалить ненужные ключи. В принципе, просто вы вызываете метод map дот ремув.
158: Да, и у него ему передаётся соответствующий ключ. Ну, представьте, у нас задача удалить именно ненужные значение по какому-нибудь правилу. Ну вот здесь у меня правило такое, что-либо значение равно, фу, либо равно бар, либо равно бас тогда
159: Мы его удаляем из napkey. То есть это вот тоже такой как бы подход из разряда. Задача сведена к небольшому количеству знаний. Когда мы снова берём кейсет, снова берём Гетт.
160: И в случае, если у нас значение вот 1 из списка, то мы вызываем метод map ремув и снова, чтобы выполнить метод map. Ремув, мы опять же ищем соответствующую запись в мэпе, выполняя там обход.
161: Красно чёрного дерева или ещё что-то. Хотя мы на самом деле в me get уже эту запись нашли. Ну вот такой вот классический подход с там, времён 2 джавы, ну с учётом дженерика.
162: Окей. 5 джавы, скажем так, это взять map энтрит итератор, пойти итератором в цикле. И, соответственно, если мы нашли ту запись, которая нас интересует, вызывать метод remove у и
163: Заметьте, что итератор он был взят у entry сет, но так как entry сет это вьюшка, то это все работает, то есть удаление из entry сет, оно влечёт за собой удаление соответствующей записи из мэпи.
164: Однако начиная с джавы 8 появился метод remove иф в коллекциях и вот этот вот стандартный шаблон удаления через оператор он тоже стал крайне редко использоваться, потому что можно написать гораздо более краси.
165: Вы можете у любой коллекции необязательно у сет вызвать ремув иф и, соответственно, просто задать там предикат. То есть условие, которое если выполняется, то мы эту запись удаляем. И мало того, что
166: Мы инкапсулировали вот этот вот некрасивый алгоритм, он ещё в ряде коллекций неплохо оптимизирован. То есть там, естественно, никакой оператор внутри может не создаваться, а уже идти там удаление непосредственно на внутренних структурах.
167: Структуры данных. Вот, но конкретно в данном случае, так как нас на самом деле энтри не интересует, мы можем снова вспомнить, что вообще, то есть прекрасный метод values, который нам возвращает
168: Просто коллекцию значений и на нём вызвать ремув иф. И так как это тоже вьюшка, то даже удаление из вьюшки вельюс приводит к удалению полностью соответствующих пар из исходной мэпи, то есть этого не надо бояться.
169: Хотя мы вроде бы удаляем, только велю, но на самом деле, ремув иф на Велюс, он удаляет всю пару и ключ. Значение, естественно. Ну, иначе как бы было бы непонятно, что делать. Вот. То есть это ещё короче, но и опять же,
170: Конкретно в данном случае можно вообще отказаться от новых айпиай с лямбдами и вернуться снова в старый мир. Там джавы 1 5, так как мы хотим удалить просто некоторые значения по
171: Их, ну, собственно, пиклс, мы просто можем собрать вот эти вот значения, которые нам не нужны в коллекцию, например, там сохранить её в какое-нибудь статик файнал поле и вызвать мэпп.
172: Потому что у любой коллекции есть метод remove all и опять же этот remove all на Велюс он прокинется в исходную мэпу и мы собственно добьёмся того же самого результата то есть это вот яркий пример как можно собственно.
173: Зная эйпиай коллекции, решить 1 и ту же задачу ну вот, переходя от такого некрасивого и неэффективного способа к более красивым и одновременно более эффективным.
174: Ещё есть стандартные задачи с мэппа, и это матип. То есть такая ситуация, когда вы хотите 1 ключу сопоставить несколько
175: Значений, причём тут возможны варианты. Например, вы хотите, чтобы сопоставлялись несколько значений, и среди них могли быть повторы, или чтобы среди них не могли быть повторы, чтобы у этих значе
176: Там поддерживался порядок или не поддерживался порядок. То есть тут тоже, возможно, ну, разные случаи, которые вам могут пригодиться, есть сторонние библиотеки.
177: Например, пачка лекшен или гуава, которые не являются частью стандартной библиотеки java, но они довольно популярны, и в них прям реализован как класс типа мультиме вот, в принципе.
178: Этим можно пользоваться, но мультимап можно сделать на коленке и на сырой джаве. И причём опять же, начиная с джавы 8 это стало несколько более удобно. То есть, по сути дела, мультиме это мэппа, которая отображает ваши
179: Ключи на коллекцию и вы вправе сами выбирать, какая коллекция у вас будет там, ну вот здесь, например, я отображаю на лист, и я использую конкретно релист. То есть я могу сделать у себя такой метод, который добавляет в этот мультимет
180: Пару ключ значение соответственно мы делаем Гетт ну к примеру как это можно реализовать на самом деле вот способов реализовать этот метод add в дикой природе встреча.
181: Ну, довольно много разных. Вот, ну, вот 1 из них такой, мы делаем Гетт, и если там был null так, как мы сами в эту мэпу никогда налы не складываем, мы все-таки считаем, что если me get вернул нам нал, то, значит, там ничего не было.
182: Тогда мы создаём новый релист и складываем его в эту самую мэпу. Если, соответственно, там был не null, то все, мы ничего не делаем, значит мы уже достали какой-то релист, который там ранее la
183: И последним действием мы в этот релист добавляем новое значение. То есть таким образом, если у нас данному ключу ничего не соответствовало, у нас появится новый релист, в котором будет 1 значение, если же там уже был релист с каким-то количеством значении,
184: Мы просто в тот релист добавим новое значение некоторых, кстати, программистов их смущает вот эта вот штука, что
185: Мы как бы достали из мэпи список, если там ничего не было, мы в мэпу положили новый список, а после этого последним действием мы модифицируем список и
186: Человеку кажется странным, но мы модифицировали список, но хотели то мы модифицировать мэпу. Вот, ну то есть тут как бы, если представлять, как устроена куча в джаве, то все нормально. Вы этот список же нигде не копировали. И когда вы в последней строчке в список
187: Добавили новое значение, то, естественно, в этой самой мэпе этот список, как бы он обновится, потому что это изменяемый объект, он лежит в мэпе в качестве значений, и если мы его изменяем, но там уже будет лежать изменённое значение.
188: В этом плане, кстати, с мэппа и, похоже, как с множествами. Вот если у вас изменяемые ключи, то это проблема. Если вы их пытаетесь изменить, когда эти ключи уже в мэпе, это очень большая проблема, то есть ключи
189: Изменять не рекомендуется. Ну, чаще всего в качестве ключей используются все-таки неизменяемые объекты. Здесь на этом примере это строки, поэтому все нормально. А вот в качестве значений в мэпе могут быть изменяемые объекты и довольно часто их изменить. Это
190: Вполне разумное решение, это используется. Весь тот алгоритм с предыдущего слайда сворачивается в 1 строчку если вы пользуетесь там джавой 8 или старше и в ней появился метод компьюте.
191: Вычислить. Если отсутствует в данном случае 1 параметром мы передаём ключ. Этот же самый ключ, он попадает внутрь лямбды. То есть, в принципе можно было и не передавать его туда.
192: Ну, это некоторая оптимизация, потому что захватывать ключ из внешнего контекста это чуть чуть дороже, чем взять его из параметров. Однако нам он не нужен ни в каком случае нам, поэтому мы просто создаём новую переменную, ка,
193: Никак её не используем. И мы говорим, что если данному ключу ничего не соответствует, то создай новый релист. Вот и компьютер эпсент, он, собственно, возвращает либо то значение, которое уже в мэпе соответствует
194: Этому ключу, либо если там ничего не было, то вот результат этой самой лямбды 2 параметром. То есть если там уже что-то было, то лямбда не вызывается и новая реалист не создаётся, накладных расходов нет. И после этого
195: Мы в любом случае получаем релист, или старый, или новый, и мы совершенно безопасно после этого делаем точка add, добавляем в этот релист новое значение, то есть вот таким образом, собственно, можно на коленке сделать свой мутим, ну и понятно, если.
196: Мы хотим в качестве значений этого матипа иметь множество, нам ничего не мешает, нам надо будет просто объявить его мэпп от string, запятая сет от string и в компьютер епсон передать там.
197: New реалист, а, например, new хэш мэп и все после этого a new хэшсет да, можно даже сделать мати мэпп, такую несколько этажную, то есть в качестве значений держать другую мэпу, а у неё там в качестве значения уже лист.
198: Иногда это тоже пригождается и у вас просто в цепочке будет там 2 вызова. Компьютер, например. Ну это более редкий случай, но бывает и так, ещё 1 структура данных, которая полезна, это
199: Её иногда называют или ещё back, или там сумка, да, вот, то есть это множество, в котором допустимы повторы, но тем не менее нас не волнует порядок элементов.
200: Нас, то есть, грубо говоря, это там продуктовая сумка, да, мы туда накидали, например, 3 пачки масла, и мы ожидаем достать 3 пачки масла, но нас не волнует, в каком порядке мы эти 3 пачки засунули, они для нас абсолютно одинаковы. Вот.
201: То есть в целом это просто означает, что нам нужна мэппа, которая отображает наши объекты на их количество.
202: То есть у нас не просто множество, а отображение каждого объекта на их количество, ну, количество всегда не меньше 1. Вот, опять же, на коленке можно такую реализовать. Вот как указано на этом слайде, мы создаём такую мэпу.
203: В качестве количества тоже мы можем использовать интеджеры. Если мы подозреваем, что 2 миллиардов нам не хватит, пожалуйста, используйте long. Вот. Ну да, к сожалению, с примитивными типами тут не сработает.
204: И мы берём старое количество. Опять же, если нам вернули нал, это значит, что мы туда ничего не складывали, потому что налы мы туда сами по доброй воле не складываем. Вот если нам вернули нал, то мы просто заменяем этот
205: На 0. И после этого делаем пут и количество увеличиваем на единичку. Таким образом. Собственно у нас каждому ключу соответствует количество значений и все норма.
206: Мы знаем, то есть это такая очень, очень популярный алгоритм, когда нам нужно подсчитывать какую-нибудь статистику, там сколько раз у нас встречаются определённые слова в тексте, например, или ещё что-нибудь там, сколько раз там.
207: Пользователь, покупатель там купил определённый товар типа вот такие задачи. Ну они очень часто встречаются. Вот, опять же, этот весь алгоритм, он сворачивается в 1 строчку с помощью такого красивого метода меч.
208: Merge он похож на put и похож на пути фесен, но как бы мы ему передаём тоже ключ и новое значение, но в случае, если в
209: Данной эпке уже что-то лежало. Мы передаём алгоритм, как склеить старое значение с новым. И в данном случае этот алгоритм очень простой. Надо просто сложить. То есть у нас новое значение это всегда единица в данном
210: Случае старое значение неизвестно какое. Ну вот если старого значения не было, он просто эту единицу и положит. А если старое значение было, то, а это старое значение б это новое, то есть единица и наш.
211: Это просто их сложить, а плюс б и в итоге мы просто получим если была там тройка, то он тройку исправит на четвёрку вот опять же в стандартных мэппа типа hashmap или tree map метод мерч, он оптимизирован, то есть он
212: 1 раз всего найдёт ключ, и если там уже что-то было, то он заменит значение без повторного поиска, то есть он не будет делать цепочку Гетт и put, которая 2 раза ищет ключ.
213: Давайте потренируемся и напишем свою метку. Ну, собственно, как мы это делали с сетом и списком на прошлой лекции. Тут все тоже выглядит не сложно.
214: Мы пытаемся реализовать мэпп, и оказывается, нам нужно реализовать всего 1 метод. Э, это энтрит. Ну, собственно, это ещё говорит в пользу дуальности того, что
215: Вэпка это просто множество своих энтри, своих пар. Вот. Ну а как реализовать энтри сет, мы уже знаем, мы берём сет, и у него нужно реализовать, если вы помните, 2 метода итератор и
216: Вот здесь у нас такая мэппа странная, которая создаёт в качестве ключей числа от нуля до аккаунт, а в качестве значений создаёт строки строковые представления этих же самых чисел.
217: Вот, ну, странная мапка. Вот. Но вот такой пример на лицо. Но главное, что она ничего нигде не хранит, она просто на лету эти значения генерирует. Ну и она, естественно, неизменяемая. Ни 1 метод, связанный с
218: Изменением тут работать не должен. Вот у нас там в итераторе хранится следующее значение и когда у нас запрашивают следующее, то мы просто создаём вот этот вот simple мьютабл н.
219: И передаём туда, собственно, вот следующее значение в виде Инта и следующее значение в виде строки ну, я там склеил с next плюс, плюс это, конечно,
220: Не очень красиво, по хорошему стоило разнести, но тогда бы на слайд совсем это не влезло. Так или иначе, вот вручную мэпу можно реализовать на 1 слайде, но опять же с условиями мы помним вот эти вот проблемы, связанные со стандартными
221: Методами. Ну и вот в такой мэпе разумно вручную реализовать, например, контейнс ки и get хотя бы потому, что иначе те, кто будет пользоваться этой мэпом, им придётся делегировать
222: К оператору, причём вот эта вот проблема она на самом деле не надуманная, я её не высосал из пальца. Был такой вот пример несколько лет назад в серьёзном продакшн коде в spark.
223: Библиотеки, которая там нужна для кластерных вычислений, она написана на скале, это язык на jvm, но при этом она использует вот эти вот интерфейсы джавовые. И там вот был такая мэппа сериалайза мэпп раппер, которая
224: Расширяла стандартный абстракт мэпп. Ну тут видно экстенс джею это java, util, абстракт, мэпп. Вот. И в ней вот был реализован метод сайс, был реализован. Метод get, был реализован энтрис, а вот
225: Method контейнс ки реализовано не было, а эта мэппа, она, по сути дела, является делегатом к какому-то там кластеру и по факту, когда пользователи пытались на этой мэке делать конте.
226: У них реально создавался итератор, и он начинал обходить все значения мэки, то есть вместо того, чтобы сразу же там быстро получить нужное значение, используя делегата, он Шёл от начала до конца.
227: Ну и, соответственно, вот эту ошибку обнаружили и исправили. То есть такого рода ошибки, они вполне встречаются в реальной жизни, реализации мэпп, они практически все дуальны реализациям сет, которые мы видели на прошлой лекции.
228: И на самом деле многие реализации сет, они внутри себя содержат соответствующую реализацию map. То есть вот есть хэш мэп, это неупорядоченный, изменяемый мэпп общего назначения и ес.
229: Вы помните хэшсет с прошлой лекции? Это по факту внутри себя тот же самый хэш мэп, просто у него значения не используются. То есть как бы это кейсет над хэш мэпом такой вот, и тоже самое.
230: Можно сказать и про линкед хэш мэп линкед хэшсет и про tree map трисет вот то есть это такие дуальные довольно-таки штуки переиспользование кода во все поля линкед хэш мэп кстати такая очень интересная.
231: Штука, она интереснее, чем линк хэшсет, потому что обычно
232: То есть линкит означает то, что вы храните порядок в ней и можете доставать элементы в том порядке, в котором вы доставали, в котором вы их добавляли. Но там есть ещё такой режим, который задаётся специальным конструктором, когда
233: А вы будете доступ к этой мэпе, то есть запрос метода get on. Соответственно, тоже будет менять порядок. И таким образом вы можете доставать те элементы, которые вы, ну,
234: В ней в 1 очередь будут лежать те элементы, которые вы чаще всего на них смотрели, и на этом можно реализовать всякие списки типа lost или just, то есть, если вы часто доступает сь к каким-то элементам.
235: То они у вас будут лежать в верхушке этой мэпи и там даже есть способ удалять наиболее редко используемые как бы элементы. То есть, скажем, если у вас размер мэпи вырос больше определённого размера, то можно старые удалять и на этом Линке
236: Можно такой кэш для бедных построить, как бы хранить в памяти там энное количество наиболее часто используемых элементов, а более старые удалять.
237: Enum мэп это map на инане 3 мэпп, соответственно сортированный мэпп айдентити хэш мэп такая интересная штука, где ключи сравниваются по равно равно, то есть в hashmap они сравниваются по икал.
238: А бывают ситуации, когда вас не устраивает экволс, либо вам нужно больше производительности, и вы можете воспользоваться айдентити хэш, опять же, требуется редко, обычно для каких-то оптимизаций, но когда требуется не
239: Мнимая штука, соответственно, есть пустой, есть синглтон, есть. Вот в 9 джаве появились всякие map of когда вы можете создать неизменяемую мэпу
240: Пар, ключ, значение. Ну там возникла такая проблема. То есть, если там set of или list of, вы можете сделать варак, потому что у вас все элементы одинаковые, то смэп оф вы в Аррах сделать не можете, потому что у вас должны
241: Передаваться ключи значения. Там ключ значение, ключ значения, они могут быть разных типов. Вот поэтому
242: Врагом этого не реализуешь, поэтому там выкрутились таким образом у вас есть там 10 методов или 11 методов мэп оф в 1 там 0 параметров, в другом 2, в 3 4 и так далее. В последнем мэп оф там
243: 10 пар, то есть 20 параметров. Ключ, значение, ключ, значение и так до 10. А если вам нужно создать прям вот конкретную фиксированную мэпу, в которой будет больше 20 параметров, то тогда нужно
244: Использовать map of entries и у него уже варак, но но там варак состоящий из entry и вы просто туда будете передавать мэпп энтри, 1 пара мэпп энтри, 2 пара и так далее. Вот это выглядит не так страшно, если вы
245: Воспользуйтесь статическими импортами, тогда у вас просто будет энтри, 1, пара, энтри, другая и так далее. Вот. Ну и там есть всякие обёрточки, про которые мы не будем говорить. Новий мэпп вам добавляет ещё кучу.
246: Методов, который позволяет найти там, например, наибольший ключ, не превышающий данного, или там наибольший, н, не превышающий данного там, или получить вьюшку, которая является
247: Под мпой начинающейся там с данного ключа, или заканчивающийся на данный ключ, вот, или перевернуть в обратном порядке сделать дисцендент мэпп, то есть все тоже самое, но она, соответственно, итера.
248: Там, если обходить эту мапку, элементы будут идти в противоположном порядке. Вот можно также полёт пола тетри, например, вытащить из неё 1 пару, удалить или последнюю пару вытащить и удалить соответственно.
249: Вот. Ну и есть ещё всякие navigate кейсет, например, когда вы можете вытащить из неё кейсет. И это будет navigate сет тоже со всякими продвинутыми методами. Вот. Ну, тоже просто стоит помнить, что такая штука есть. И когда она вам потребует
250: Вспомнить и почитать документацию поподробней. Я уж не буду примеры приводить про конкарент. Modification эксепшн, давайте немножко повторимся. Выкидывается, если коллекция заметила, что её изменяют.
251: Тогда, когда этого делать нельзя, обычно это происходит при обходе итератором либо если вы изнутри лямбды которую вы передали в foreach пытаетесь изменить, либо например вы вызываете
252: Какой-нибудь компьютер, эпсент и изнутри вот той лямбды, которую передали, вы пытаетесь ещё какую-то модификацию сделать в этой мэпе. Это может быть очень опасно, потому что, ну, компьютер эпсент мог найти, вот, скажем, в красно чёрном
253: Дереве соответствующую запись и потом вызвать вашу лямбду. А если ваша лямбда внесла изменения в эту мэпу, то не пойми, что могло произойти. Там могло вращение произойти.
254: Могла эта запись вообще исчезнуть из дерева или ещё что-нибудь, и когда вы попытаетесь её обновить, окажется, что непонятно, что случится. Поэтому эти методы тоже обычно замечают и кидают эксепшн.
255: Ну и да, модифицировать неконкурентную коллекцию при обходе можно только через тот итератор, которым обходите. То есть итератор, ремув, вызывать легально все остальное нет с конкурентными коллекциями. Там история хитрее. Мы поговорим об этом позже.
256: Ну и важный момент, что никаким образом обрабатывать конкарент, modification эксепшен не надо. То есть это некорректная ситуация, это значит, что у вас ошибка в коде. Вот, ну, в крайнем случае, там где-то залогировать на верхнем уровне.
257: Чтобы пользователь мог сообщить каким-то образом разработчикам вашей программы. Вот, но
258: Так, обрабатывать самим эту ситуацию не нужно, потому что ничего хорошего из этого заведомо не выйдет. Ну, пример, как напороться на modification эксепшен в таком реальной жизни, например, вот у вас есть такие
259: Пользователи и для простоты у них просто есть 1 свойство. Валидный этот пользователь или нет. Ничего другого у нас для простоты нет. Ну соответственно, есть метод из велит, вот, и есть какое-то хранилище.
260: Пользователей, в которых вот, ну мы их храним в виде множества хэшсет, у нас там лежит, соответственно, есть метод add user, который
261: Проверяет. Ну, добавляет нового пользователя. Ничего сложного. Есть метод валидейт юзер, который, ну, вот такая у него логика. Если пользователь не валидный, тогда мы его удаляем из репозитория, то есть он прове,
262: Вот, если если пользователь не валидный, то все мы про него должны забыть. Такой придумали себе метод. И потом мы написали метод валидейт ол, который вам позволяет всех пользователей проверить на предмет валидности, если какие-то
263: Валидные, то взять их и поудалять. Вот. И вот здесь вот мы, собственно, напоролись как раз на эту проблему. То есть она такая возникла немножко неявная у нас есть в методе валидейт ол соответст.
264: Обход коллекции users, то есть там неявно создаётся итератор, а метод валидейт юзер, он, возможно из этой коллекции что-то удалит, причём удалит не через этот итератор, через этот итератор.
265: Удалить невозможно, потому что его, он спрятан внутри компилятора. То есть в исходном коде мы его вообще не видим. И, соответственно, если мы, например, там добавим 2 пользователей, вызовем валидейт ол, то мы увидим, что кидается
266: Modification эксепшен, причём кидается вот из метода итератор некст. То есть в тот момент, когда мы удаляем, мы ещё ничего не знаем, потому что когда мы удаляем, мы не знаем, а вообще обходим мы коллекцию или нет, может мы
267: После этого уже и не будем этим итератором пользоваться. Однако, когда мы переходим на следующую итерацию, следующий элемент, то, соответственно, итератор замечает, ага, коллекция то поменялась. Не буду-ка я дальше обходить, а лучше кину, modification.
268: Вот, то есть в этом случае, ну, надо что-то делать, как-то логику этого кода поменять. Бывает на самом деле, в реальной жизни, когда у вас там цепочка вызовов не через 1, а там их очень много.
269: Может быть и то есть вы где-то обходите, там вызываете 1 метод, он другой, он 3, там 10 метод неожиданно эту же самую коллекцию начинает менять и возникает очень большая головная боль. А собственно, как это все
270: Рефакторить таким образом, чтобы оно работало. Вот
271: Пара слов про хэш код. Мы про него уже несколько раз упоминали. Ну вот, давайте более так продвинуто про контракт. Хэш код есть у любо.
272: Объекта по умолчанию он генерируется случайно. Вы можете встретить какие-то старые тюториал, где говорится, что хешкод по умолчанию это адрес объекта, это давно уже не так. Такой режим можно включить специальной опции.
273: Машины, но его никто не использует. То есть по умолчанию это просто случайное число для объектов с переопределённым Икол. Почти всегда надо переопределить хэш код. Это важно, особенно в том случае, если вы собираетесь эти объект
274: Помещать или там в hashset, или в хэше. Вот контакт предполагает, что если у вас 2 объекта равны по Икол, то у них должны быть одинаковые хэш коды и соответственно, но обрат
275: Не обязательно, верно. То есть, если у вас одинаковые хэш коды, это ещё не значит, что объекты должны быть равны по икв. В любом случае вы можете написать в качестве хэш кода реализацию ретерн 0. Эта реализация корректная, но при этом она плохая. То есть вы всегда можете
276: Возвращать один и тот же хэш код, но у вас объекты ни в каких хэш таблицах тогда не будут распределены, они все будут падать в 1 корзину. То есть пользы от хэш таблицы вы никогда не получите. Соответственно, предполагается, что если у вас объект содержит какие-то поля, то
277: Хэш код должен, ну как-то на этих Полях базироваться, что-то он должен вычисляться на их основе. Ну и самый простой вариант просто взять и навычислял.
278: Вот, то есть если поля у вас объектные, вы можете прям на них подёргать метод hashcode. Если у вас поля примитивные, то во всех классах обёртках есть статические методы. Ну например, если у вас поле типа,
279: Double вы можете вызвать дабл точка хэш код, передать ему вот этот вот ваш примитивный дабл и будет какой-то нормальный хэш код.
280: С массивами проблема, да, потому что хэш код на массивах, так же как и call to string, он не переопределён по дефолту и, соответственно, хэш код на массиве. Это будет вот случайно сгенерированное число, то есть он не будет вычисляться по содержимому.
281: И если вы сделаете 2 массива с одинаковым содержимым, у них, скорее всего, хэшкод будет разным. Если вы хотите именно по содержимому массивы сравнивать, то вам нужно писать рейс от хэшкод. Вот. Ну и, соответственно,
282: Есть вариант реализации хэш кода для ленивых. Это метод, обжект, дот хэш. Вы ему просто передаёте все поля. Вот там есть некоторые накладные расходы, ну и плюс там для при
283: И объектов, он просто вызовет хэш код, но для массивов он тоже вызовет просто хэш код и поэтому с массивами у вас будут проблемы. Но, например, если у вас массив объектов, то вы можете вот так вот взять и обернуть его в рейс.
284: А у списка есть хэш код, который по содержимому этого списка работает, поэтому можно вот так вот. Ну, это как бы совсем для ленивых, если вас, да, быстродействие не сильно волнует, потому что тут будут создаваться, скорее всего, всякие
285: Лишний объект. Ну вот стандартная реализация, например, у вас там несколько полей. Это то, что вам идеее может сгенерировать. Там есть, да, проблемы, что если у вас поля могут быть наллами, то вы можете вам
286: Надо на налы, соответственно, проверки добавить. Вот. И потом у вас результат будет там, добавя, умножаться на 31, добавляться хэшкод следующего поля и так далее. Вот умножение на 30.
287: 1 это такой хороший компромисс, потому что получается хэш коды, более менее неплохо распределённые с 1 стороны, а с другой стороны, умножение на 31 это такая операция, более быстрая, чем умножение.
288: В среднем потому что чтобы умножить на 31, надо сдвинуть на 5 beat, то есть умножить на 32, а потом вычесть само число и jit компилятор умеет эту оптимизацию и это быстрее на процессорах работает, чем реальная.
289: Умножение. Вот, ну опять же можно написать вот обжектс хэш и будет тоже самое немножко можете потерять производительность. Вот если не критично, то можно так писать.
290: Иногда люди сами пишут хэш код. Ну, например, такая задача. У вас есть пара символов. Ну, зачем-то вам потребовалась вот пара символов, вы могли бы умножать на 31, использовать стандартный подход.
291: Но вы решили, я сделаю идеально распределённый хэш код. То есть это значит, что разным парам будет гарантированно соответствовать разные хэш коды и как это сделать? Ну можно, а сдвинуть на 16 влево как бы каждый
292: Это 16 бит а int это 32 бита соответственно мы просто верхние 16 bit у нас будет а а нижние 16 bit это будет б вот и это как раз такой код в котором я напарывался на ошибку с приоритетами операций то есть
293: Тут легко забыть, что у битового сдвига приоритет ниже, чем у сложения на самом деле этот код выполнится 16 плюс б сперва добавится и потом выполнится сдвиг.
294: А на вот это вот значение 16 плюс б. В итоге распределение хэш кода станет наоборот, гораздо хуже, чем если бы вы не беспо, ну как бы реализовали его стандартным способом. Поэтому, когда вы делаете хэш код Нестан,
295: Надо быть очень внимательным. Ну вот исправить надо таким образом, тогда будет то, чего вы добивались, но это такое замечание в сторону. И давайте все-таки поговорим про компараторы.
296: Это полезная вообще штука умение сравнивать объекты, сравнивать не только по кволс, а определять порядок между ними, то есть какой из объектов больше, какой меньше, а какой равен.
297: Это пригождается во многих случаях. То есть, во первых, вы можете сортировать списки, сортировать массивы с помощью соответствующего с помощью сравнения объектов вы можете создавать
298: Трисет или tree map вы можете создавать проорити кью, то есть в принципе много разных есть алгоритмов, в котором вам требуется сравнение вы можете использовать двоичный поиск там Байна ресерч мы упоминали.
299: На уже сортированном списке и опять же ему требуется тоже сравнивать объекты, сравнивать, задать порядок на объектах можно 2 способами внутренним способом, реализовав интерфейс.
300: Компер. Такой порядок называется естественный или по-английски natural order либо внешним способом. То есть, если вы не можете модифицировать объект, либо у объекта уже есть естественный порядок, но вам хочется задать другой порядок на этих
301: Объектах. Вы можете задать порядок внешним способом с помощью компаратора. Вот давайте сперва посмотрим на внутренний способ.
302: Это для этого вам потребуется реализовать интерфейс комппера и реализовать единственный метод этого интерфейса. Это компату, то есть сравнить с интерфей.
303: Come on, причём параметризован. И тут вы его параметризует обычно просто самим своим типом. Тогда этот же самый параметр прилетает в компату. Вот мы видели
304: Всякие проблемы с экволс то что в экволс всегда приходит object и вы должны беспокоиться о том, чтобы иметь возможность сравнить объекты вообще каких-то несовместных типов ну хотя бы false вернуть вот с компе не.
305: Только лучше ситуация. То есть вы можете ограничить, собственно, тип того, с чем вы сравниваете. Ну, обычно ограничивается просто типом текущего класса, и тогда у вас не возникает проблема сравнивать с чем-то, с чем вы вообще в принципе,
306: Не можете. Комп ту возвращает целое число. Его абсолютное значение в смысле конкретное значение. Не важно. Важен только знак. Если это число меньше нуля, то значит,
307: Текущий объект this on меньше вот того объекта, который передали если равно нулю, то объекты равны. И если больше нуля, то, соответственно, текущий больше. Вот, ну это тоже такая реализация спор.
308: То есть, возможно, если бы сегодня делали метод компату, то сделали бы просто и нам с 3 значениями. Ну, потому что реально только 3, возможно, разных исхода. Почему бы и нам не сделать. Вот, ну, понятно, что когда это
309: Появилось инамо в джаве вообще не было. И как бы программирование было в целом более низкоуровневым. Поэтому вот до сих пор имеем Инты, у которых только 3 разных значения имеют смысл. Вот.
310: Ну, давайте посмотрим, как это может выглядеть. Вот самый такой простейший тривиальный случай. У нас есть класс, например, пользователь, да, у него есть всего 1 поле, которое уже само по себе сравнимо. Это строки у строк есть
311: Порядок это просто вот как бы лексикографический, алфавитный порядок.
312: И, соответственно, в нём есть метод компату. То есть в данном случае нам достаточно просто делегировать. Мы, во первых, пишем имплемент с комппера юзер. То есть мы говорим, что наши
313: Пользователи они сравнимы, сравнимы тоже с пользователями, вот реализуем метод компату, да тут not null случайно оказался, можно его пока игнорировать, но суть в том, что по контракту.
314: Come to нельзя передавать null если туда передали null, то он должен кинуть на pointer exception, в отличие от метода экволс, который должен вернуть false компату, ожидает нот налл.
315: И здесь мы просто делегировали к name, то есть так как нам передали объект того же типа, что и мы, мы можем спокойно доступаться к его полям, ну и просто вызвали нейм кому точка name. Ничего сложного.
316: Il. Есть довольно такой объёмистый контракт, который, вообще говоря, очень рекомендуется соблюдать, потому что несоблюдение этого контракта может привести к очень неприятным Багам, которые
317: Будут редко проявляться, сложно воспроизводиться, и их будет сложно отлаживать.
318: Ну, во первых, у вас должна быть антисимметричность, то есть знак x комп ту игрек и знак игрек. Компот у x должен быть противоположный, если 1 отрицательный, другой положительный, ну либо оба на.
319: Нули должны быть вот если x компот у игрек кидает исключение, то это должно быть равносильно тому, что игрек комп у x тоже кидает исключение, то есть тут тоже надо быть осторожным, если по какой-то
320: Причине какая-то пара не может быть сравнима, то и противоположная пара тоже не должна быть сравнима, потому что, ну, вообще говоря, любые алгоритмы, они могут вызывать x компоту, игрек или игрек, кому x по своему усмотрени.
321: И если поведение будет разное, то у вас просто там с изменением версии джавы или изменением версии библиотеки, или просто из за того, что у вас порядок элементов, чуть чуть поменялся порядок вставки там.
322: Коллекцию вы можете получить разные эффекты, это что называется удача, потом это дело отлаживать. Ну и следствие предыдущего пункта это x комп ту на должен кидать на point exception просто потому что на компе у x.
323: В любом случае кинет на exception если вы на null попытаетесь вызвать любой метод, вас на рексена кинет виртуальная машина, но вот x коммунал, чтобы кидал, вам нужно самим позаботиться об этом даль.
324: Транзитивность должна быть. То есть если x компто игрек возвращает больше нуля положительное и игрек комп ту that тоже возвращает больше нуля, то и x компу зет больше нуля ну понятно да, если x больше игрек, а игрек?
325: Больше z то и x должен быть больше z. Если вы это нарушите, то у вас могут быть очень психоделические эффекты при использовании алгоритмов всяких.
326: И такая вот должна быть последовательность, что если 2 элемента у вас равны по компату, то любой другой элемент, который вы сравниваете с иксом, он должен
327: Такой же результат и при сравнении с игреком. Ну, логично, да, если у вас 2 элемента равны между собой, то 3 элемент, он должен быть либо больше их обоих, либо меньше их обоих, либо равен обоим. Будет исключительно странно, если он больше 1 номе.
328: Другого тоже алгоритмы этого могут не ожидать.
329: Далее рекомендуется такая вещь, что компе ату должен быть консистентен цикл. Если у вас это не соблюдается, то у вас коллекции, которые основаны на компе ату.
330: Например, трисет, они не будут консистентны сил. Ну, например, вы сможете в такую коллекцию добавить 2 элемента, которые не равны друг другу по Икол. Вообще говоря, это не всегда соблюдается и даже в стан.
331: В библиотеке джавы есть ситуации, когда и компату не соответствуют друг другу, я знаю как минимум 2 примера это стринг, билдер и big decimal вот, но в этом случае, если у вас несоответствие, то надо прям.
332: Очень явно там жирными буквами в документации к вашему классу написать что в данном классе, компату и equal неконсистентны, будьте осторожны при использовании там комппера и так далее ну и
333: И ещё 1 штука, которая просто позволяет избежать неприятных ошибок, это то, что x комету игрек желательно никогда не возвращать интеджер мин велю. Почему? Потому что такой
334: Нибудь пользователь может ничтоже сумняшеся взять результат этого компату и унарный минус перед ним поставить, и куда-нибудь там дальше передать, чтобы просто поменять знак. Вот. Ну, вы знаете, что у минел ю нету.
335: Парного значения в положительной части. Поэтому вот эта вот смена знака, она не сработает для инвел ю. И в итоге тот, кто этим воспользуется, он, соответственно, получит тоже непри.
336: Эффект ну с точки зрения использования тоже на самом деле не стоит применять унарный минус к результату компату лучше поменять местами параметры, то есть вызвать игрек кому x.
337: Вот, но, тем не менее, лучше защититься с обеих сторон. Компоратор это практически тоже самое, но это внешняя реализация, то есть это отдельный класс, то есть
338: Это функциональный интерфейс и, соответственно, у него надо реализовать тоже 1 метод комп, и ему передаются 2 объекта о 1 и о 2, как бы, и тут все тоже самое только
339: Вы, у вас сам компоратор, он не имеет никакого состояния, в нём по хорошему, не должно быть ничего изменяемого, никаких там полей. Вот, и он просто должен уметь сравнивать любые 2 объекта у 1 о 2.
340: Их там внутреннее состояние и тоже самое меньше нуля, равно нулю или больше нуля возвращать. И тут правила все те же самые, но за исключением 1 момента вот компоратор он имеет
341: Право сравнивать налы, то есть могу, может вы можете вполне сделать null френдли компоратор, которому, ну просто он, например, все налы поместит вперёд, то есть все налы будут меньше любого другого значения или наоборот все налы будут больше.
342: Другого значения, а все остальные правила, в принципе, те же самые, ну да, и рекомендуется, чтобы компораторы, они были консистентны сил для соответствующего объекта, но
343: Так как компоратор задаётся внешним способом и вы его можете использовать только там в каком-то локальном контексте. То есть если это вот компоратор для решения какой-то частной задачи, то тут гораздо более допустимо отступить от этого.
344: Правила, если ваша задача этого требует, ну потому что вы как бы все контролируете, никто снаружи вашим компаратором не воспользуется, просто аккуратно все сделали и все нормально как можно не
345: Правильно реализовать комппера. Ну вот такой там пример. У вас есть класс, в котором 1 булево поле, и вы пытаетесь сравнивать вот чисто по этому булеву полю вылет или нет?
346: Соответственно, вы решили написать, ну, если я велит, а тот не велит, то, соответственно, я тогда буду больше, да, потому что больше нуля, а иначе он будет боль.
347: Например, вот такой компоратор, вот в нём проблема такая, что вы никогда не возвращаете равно, то есть в частности, если вам передали тот же самый объект, что и вы сами, то вы вообще то по хорошему 0 должны вернуть это.
348: Может привести к такой ситуации, что вы можете в трисет добавить один и тот же объект дважды, потому что у вас компоратор сломан. То есть вы в сет добавляете 1, прям один и тот же объект, даже не копию, а прям Ровно его же. И вы взяли у вас 2.
349: Экземпляра просто потому, что компоратор в этом объекте сломан, он не вернул 0 для 1 и того же объекта. Правильный подход не изобретать велосипед, а воспользоваться статическим методом булиан компе, который как раз
350: Собственно, этот алгоритм реализует единицу минус единицу или 0 там вернёт в зависимости от значений булево поля. Идём дальше. Предположим, у нас единственное поле это поле целое число. Ну, кажется, вот это
351: Контракт больше меньше, равно он позволяет вам воспользоваться операцией вычитания, и это выглядит так довольно оптимально. То есть никаких ветвлений, никакого там нарушения её предикшена в процессоре берете и вычита.
352: И все красиво получается. Вот проблема возникнет. Если у вас неожиданно это целое число окажется очень большим и очень маленьким, тогда вы можете переполниться. Ну вот, например, мы добавим пользователей 1, миллиард лет другому.
353: 2 миллиарда лет, 3, 0 лет 4 минус миллиард 5 - 2 миллиарда. Вот. Ну и окажется, что трисет наш вовсе и не хочет сортировать. Он почему-то пользователя с возрастом - 2 миллиарда поместил в самый конец. Вы, конечно,
354: Можете сказать, что у вас таких возрастов никогда не бывает. Вот. Но вот проблема в том, что такие баги, они возникают в самый неожиданный момент, когда о них как раз никто не думает, потому что никто не ожидал, что такие большие числа вам встретятся, а они хоп, взяли и встретили.
355: Вот. Ну, я такие баги в жизни встречал. Можно попытаться, да, реализовать это на коленке вот таким способом с 2 условиями. Если меньше, то - 1, если равны, то 0 иначе. + 1, например, вот.
356: Ну, опять же, рекомендуется не изобретать велосипед. Есть статический метод интеджер компе, который это делает. Вот предположим, у вас дробное поле, ну, там доход, да, и вы хотите по нему сравнивать тоже вы можете
357: Реализовать на коленке, но тут возникнут тоже проблемы, если вдруг это поле неожиданно окажется нан. То есть вот я напихал Нанов, потом вызвал метод сорт, который сортирует и
358: Выясняется, что он ничего и не отсортировал, а дело в том, что наны у нас несравнимы и в итоге, если вы помните, там меньше на Нане возвращает false, равно тоже возвращает false, и вы с нанами всегда попадёте в
359: Единицу без разницы с чем вы сравнивали. Вот. То есть у вас просто никакой сортировки не будет. Ну и опять же есть стандартный метод. Не изобретаем велосипед, используем double компе. Более сложная ситуация, когда вы хотите сравнить по нескольким полям,
360: Ну, например, у вас есть имя, вы сперва сравниваете по имени, если имена совпадают, тогда сравниваете по возрасту. Ну, стандартный подход, примерно такой, что вы делегируете к соответствующему компаратору по
361: Полю. И если вам вернули 0, тогда вы делегируете компаратору по 2 полю, а иначе возвращаете вот исходное значение. То есть вот такой вот код, он не очень красивый, но
362: Как бы нормальный, вполне каноничный, так скажем. Однако если вы используете это внешним способом, то у вас есть более красивый вариант, это
363: Использовать комбинаторы компараторов. Ну, во первых, если вы пишите внешний компоратор, то очень рекомендуется использовать лямбду, потому что на самом деле компоратор это просто функция, вот которой вы передаёте
364: 2 значения, у неё нет состояния. То есть обычно в большинстве компараторов нет состояния, и поэтому вам ничего не мешает использовать лямбду. Вот тоже самое, что было на прошлом слайде, только мы сравниваем
365: Этих же самых пользователей внешним способом. Ну, для того, чтобы внешним нам нужен доступ все-таки к имени и к возрасту. Поэтому я добавил аксессоры, гет нейм, get age. Вот, и, соответственно, вот эти вот, ю, 1, ю 2, мы к ним можем
366: Таким образом, обратиться. Однако, ещё когда вы используете компоратор, там есть такие замечательные штуки, которые называются комбинаторы, методы.
367: Которые вам позволяют как раз построить цепочку сравнений. И вот с комбинаторами это выглядит прям вообще красиво. То есть вот верхний компоратор это тоже самое, что мы пытались написать на прошлом слайде, и мы просто говорим
368: Компоратор точка Коперин. То есть мы сперва сравниваем по имени, мы никак никакие вот эти методы комату или комп не вызываем, мы просто вызываем вот собственно геттер мы сперва сравниваем по имени, а потом
369: Компен инт, а потом мы сравниваем по целочисленному полю возраст. Вот и все. То есть дальше вот эту вот всю сложную логику с нулём не нулём внутри уже эти специальные методы реализуют.
370: Там иногда вывод типа в джаве не справляется и поэтому нужно у лямда параметра указать явно тип в скобочках юзер ю, но мы про это на следующей лекции поговорим, когда и почему это бывает вот в Большин.
371: Случаев он справляется, и тогда его указывать не надо. Вот, ну и давайте посмотрим, что ещё можно сделать. То есть, например, вы можете легко сказать, мы сперва сравниваем по именам, но по именам в нисходящем порядке.
372: То есть как бы от z до, а тогда мы просто 2 параметром в компринт передаём тот компоратор, который мы будем использовать для сравнения имён. Ну и в данном случае есть стандарт.
373: Компаратор, реверс, ордер, который любые объекты, сравнимые в естественном порядке, мы можем их сравнить в обратном порядке. В данном случае он применяется к строкам, которые возвращают гет нейм, вот или, например, в классе string.
374: Есть специальный такой компоратор, кейс инсенситив фордер, который позволяет сравнивать строки без учёта регистра. И, пожалуйста, вот у вас пользователи будут сравниваться по имени, но без учёта регистра. Вот, а потом все ещё по возрасту.
375: Либо, например, вы можете сделать такие штуки, взять и все это обернуть в нас фест, нас фест вам сделает этот компоратор на friendly и поместит налы в самое начало. То есть налы.
376: Будут как бы меньше, меньше любых других пользователей. Вот это означает, что если у нас пользователь дал, то, соответственно, компоратор будет работать. Но если у нас
377: Пользователь не null, а у него имя нал, то этот компоратор работать не будет, а вот самый последний на слайде, он, наоборот, нужен в том случае, если мы хотим сравнивать пользователей, у которых в качестве имён есть нал, то есть
378: В этом случае мы налёт запихиваем внутрь компринт и ему ещё передаём нейчурал ордер. То есть говорим, что ты оберни естественный порядок, но перед ним сперва поставь на вот и так.
379: Таким образом можно из этих компараторов построить из этих комбинаторов довольно такую нетривиальную цепочку, можно там по 3, по 4, по 5 полям сравнивать как угодно. Вот это выглядит ещё красивее, если мы добавим статических импортов.
380: То есть можно статически импортировать вот этот вот класс компоратор и тогда у нас все станет более коротко. И ещё это будет красиво, если мы вместо лямбд будем использовать метод референсы вот такой вот специальный
381: Синтаксис, то есть тут user, компоратор у нас вообще стал такой компринт. Юзер, гет, нейм, зен, компринт, инт юзер, Гетт эйдж практически читается как
382: Более менее понятный английский текст компоратор это сравнивающий пользователя имя, а потом сравнивающий целое число пользователя возраст вот.
383: То есть, в принципе, становится все довольно понятно. Ну и остальные от этого тоже упрощаются. Да, я думаю, на этом мы как раз закончим, и про функциональное программирование уже более так последовательно начнём говорить на следующей.
384: Лекции спасибо всем, кто слушал и если есть какие-то вопросы или, может быть, вопросы с чата в YouTube можете зачитать, я готов на что-нибудь ответить.
385: На YouTube достаточно много вопросов, поэтому можешь открыть вкладку и сам выбрать, на что ответить так, а мне может кто-нибудь ссылочку вот в чат зума, например, скинуть, потому что у меня под рукой её нету, да?
386: Так, это, наверное, какая-то внутренняя ссылка, нет?
387: Блин, вот зачем тебе мой пароль?
388: Ой, блин.
389: Вопрос в чате, это не та. Ладно, хорошо. Да, я смотрю в чат. Так, а вот, вот правильная ссылка, да.
390: У нас будет рекурсивная лекция. Создатель и листа троллил пользователей, сказал, что за 20 лет ни разу не использовал её. Да ну как троллил. Возможно, когда он его создал.
391: Он думал, что будут применения как бы не всегда удаётся при создании чего-то предсказать оно вообще будет полезно или нет, поэтому попробуйте сами написать какую-нибудь библиоте.
392: Теку или какой-нибудь api, или ещё что-нибудь, и вы легко поймёте, что очень легко сделать бесполезную вещь гораздо сложнее сделать полезную, так сколько по времени лекция идёт хорошая новость.
393: Она уже подошла к концу, а хэш код именно случайно генерируется и задаётся как адрес памяти. Да, я сказал про это с адресом в памяти. Это такая какая-то городская легенда. То есть в старых версиях джавы действительно хэш код задавал.
394: Как адрес памяти, но это было прям очень давно. И с этим есть проблемы. Во первых, хэш код это тридцатидвухбитная, а в современных машинах у вас адреса 64 битные, то есть, ну, прост.
395: То как бы 64 бита в 32 не влезет, поэтому даже если вы включите эту опцию в gvm, у вас будет все равно не адрес, а какой-то обрезанный кусок адреса, во вторых, Гарбач коллектор он совершенно спокойно берет и перемещает.
396: Объекты в памяти и поэтому вопрос возникает. А собственно, о каком адресе вообще идёт речь? То есть если у вас объект переместился, у него хешкод то не должен измениться. Ну и в этом режиме у вас
397: Это будет речь идти об адресе, который был, когда вы 1 раз запросили хэш код на этом объекте, но потом объект мог переместиться. И, соответственно, это уже совсем не адрес памяти. Ну а самое главное то, что адреса в памяти, как правило, очень плохо распределены.
398: Гораздо хуже, чем случайные числа. И поэтому, если просто полагаться на адреса в памяти, ну, у вас, хэш, таблицы будут гораздо менее хорошо распределены, будет больше коллизий. И зачем это надо, когда случайное число сгенерировать совсем не сложно.
399: Вопрос про аннотации. У нас будет лекция про аннотации, наверное, самая последняя или предпоследняя в нашем курсе. И мы поговорим, и как создавать аннотации, и как пользоваться рефлексией, и как пользоваться рефлексией для работы с аннотациями. Эти
400: Все вопросы мы обсудим, ты можешь создавать свою аннотацию, запись, компоратор, юзер, user компоратор это создание указателя на интерфейс компора.
401: Нет, так, сейчас мы попробуем вернуть слайды.
402: Вот, например, на этом слайде, если речь идёт об этом здесь мы просто создаём поле, ну, предполагаем, что вот то, что написано на слайде, оно внутри некоторого внешнего
403: Класса просто я его не написал, там сверху написано class hello world, например, и тогда внутри этого класса создаётся поле статическое, то есть не привязанное к экземпляру этого, финальное, то есть не подлежащее измене.
404: Поле с именем user компоратор, у которого тип, это компоратор юзер, а дальше уже инициализатор этого поля, то есть его начальное значение. Ну так как это финальное поле, то это его значение на
405: Всю жизнь, собственно, вот я надеюсь, я ответил на этот вопрос от ильи, да, поздравляют с выходом джавы. 18. Я все.
406: Вас тоже поздравляю. Сегодня действительно состоится выход джавы 18, по моему, ещё не состоялся, но вот до него уже прям довольно близко по времени, так что все
407: Всех вас с этим поздравляю. Джава 18. Это такой не очень важный релиз. Там не очень много интересного произошло, но тем не менее,
408: Так просто, как бы очередная Веха. Раз в полгода выходят новые релизы джавы. Интересная штука, например, там добавилась, это сниппеты в документации. То есть вы можете в
409: Java doc вставлять кусочки прям форматированного джава кода и идее их будет вам красиво подсвечивать там это особым образом поддерживается с особыми тегами вот это наверное моя любимая фича из джавы.
410: 18 вот. Ну, я думаю, у нас будет блок пост в блоге или idea, где будет рассказано и про что нового появилось в джаве 18. И как мы это поддерживаем в нашей идее, реакт.
411: Манифеста я не подписывал, и рикс парадигма мне, наверное, не близка, но я просто работаю, наверное, немножко в другой области и, скажем так, я не часто сталкиваюсь с
412: Проблемами, которые легко бы изящно решались с помощью реакта с помощью реакта. И вполне возможно, что если бы я работал как бы с другими задачами, то
413: У меня было бы другое отношение к этому почему нужно использовать неизменяемые ключи в map? Что будет, если использовать изменяемые ключи?
414: В этом случае вы можете поломать мапку любым способом. Вот давайте, я не знаю, попробуем какой-нибудь пример прям сконструировать в качестве ключа будем использовать. Ну, например, список, да, список это
415: Вменяемый объект. Ну, лист, вот у вас такой ключ.
416: Вот. Соответственно, ну, самое простое, там, например, мы в него добавили строчку 1, да, после этого мы создали мэпу.
417: Мпку заимпортировали и засунули туда лист и ей соответствии добавили там 4, а нет, не string, a list string, у неё в качестве ключей, вот списки используются.
418: Да, вот, ну и, например, вывели эту мэпу. Вот давайте попробуем это дело скомпилировать.
419: Так вот, работает, да, то есть у вас в мэпе в качестве ключа лежит список, содержащий единицу, а соответственно, в качестве значения у вас лежит четвёрка. Теперь давайте, ну и проверим че-нибудь.
420: Get list да, вот это вот, а теперь мы возьмём и модифицируем этот список. Добавим туда ещё двойку, да.
421: Ну давайте выведем список, выведем значение. Вот. И теперь мы вот это вот дело.
422: Вот так вот, например, поступим.
423: То есть, смотрите, мы модифицировали ключ, когда он уже лежал в мэпе, и после этого мы выводим содержимое мэпи вот так вот просто через
424: И он показывает, что да, у нас вот это вот изменённый ключ, он в мэпе находится, но когда мы делаем мэп Гетт, он уже не находится, он возвращает нам нал. То есть наша мэппа, она эффективно сломалась, сломалась она, ну, если вдаваться в детали,
425: Реализации, потому что у нашего списка изменился хэш код, так как изменилось значение, изменился хэш код. И когда мы снова делаем гет, мы уже пытаемся его искать в другой корзине, в которой его нет. А как бы
426: Когда мы модифицировали лист, мэпп же про это никаким образом не узнал его. Мы ему никаким образом не сказали, что мы там че то модифицировали. Соответственно, мэпп не имел возможности переложить этот список в другую корзину. Вот. И в итоге он лежит не в той корзине, которая соответствует
427: Его хэш коду и, соответственно, ну мы его просто найти не можем. Вот, то есть у нас мэппа сломана, но это зависит от реализации мэпи. То есть, если там красно чёрное дерево, можно ещё хуже добиться, наверное, эффектов, просто там он будет лежать совершенно не в той
428: Ветке, в которой должен находиться. Вот, поэтому лучше так не делать. То есть, если у вас вот край конец, такая ситуация лучше перед модификацией, соответственно, достать это значение оттуда удалить из мэпи потом
429: Модификацию сделать. Ну типа там написать map get лист сохранить в какую-нибудь переменную, типа овал там, да, и потом map, точка пут.
430: Lustova. Ну, как-нибудь вот так вот, да. Ну, я говорю, это такой странный сценарий, он крайне редко может в жизни пригодиться. Вот даже не map get, вот это неправильно, кстати, было. Видите, потом
431: Потому что сейчас я ещё и 2 раз тот же самый объект смог сложить, потому что тоже он не заметил дубликата в другой корзине искал. Надо было, конечно, ремув делать. То есть мы старый удаляем, новый добавляем. Вот после этого у нас мэппа остаётся целой
432: То есть пока это значение, изменяемое внутри метки, его изменять не надо. Мы его вытащили, изменили, после этого положили назад. Вот так. И ещё вопрос. Коэффициент заполнения
433: Для увеличения хэш мэп 0 7 ну на самом деле я думаю 0 75 давайте посмотрим реализацию всегда можно глянуть есть default load фактор 0 75 да.
434: Собственно, это как бы у нас есть хэш таблица, это массив и вот когда
435: Размер хэш таблицы, умноженный на 0 75, становится меньше, чем размер мапки. Тогда это знак, что хэш таблицу надо увеличивать в размере. То есть иначе у нас будет слишком
436: Много коллизий. Ну это просто такое, как бы эмпирически установленное правило, что как бы, когда вот мы приближаемся к Такому пределу, как бы, если лот фактор поставить меньше, то у нас меньше будет коллизий, но ценой тому,
437: Что в среднем мы будем требовать больше памяти. То есть у нас будет больше пустых ячеек. Если от фактор увеличивать, то мы сэкономим память, но вероятность коллизий возрастёт и доступ к элементам может стать медленнее. Вот такая
438: Трейдов, что называется, значение по умолчанию там в 99 и 9/10 случаев оно удовлетворяет, но вы можете воспользоваться конструктором, который вам позволит задать другое значение.
439: По умолчанию, если вдруг у вас как раз тот самый 0, 1%, если в листе больше 3 миллиардов записей, как коллекция будет работать, получить размер, интегрироваться, получить по индексу, что дальше максимального значения? Инта, что будет? Да.
440: Интересный тоже вопрос. Большинство коллекций работать не будет вообще, потому что, например, массив вы не можете создать, ну там не 3 миллиарда, а даже 2 миллиарда граница 2 в 31 степени. То есть у массива.
441: Есть длина массива это int, соответственно он не может быть больше чем 2 в 31 там 2 миллиарда с хвостиком, если вы попытаетесь создать массив.
442: Ну, большей длины вы его, в принципе, создать не можете, но даже там очень близко к вот этой длине, там несколько значений тоже виртуальная машина может угнуться. То есть, если вы там сейчас, мы в демку вернёмся,
443: Давайте все удалим, попытаемся аллоцировать массив типа integer max велю то.
444: Этот код, скорее всего, упадёт с исключением, то есть out of memory error и там написано реква ресайз, аксис, вим лимит, то есть у виртуальной машины есть лимит ну там где-то максимальный размер типа max well, you - 8.
445: Соответственно, вот это можно. И когда вы реалист, используете у вас внутри массив, то есть он не может быть больше этого значения. Соответственно, вы не можете просто создать больше 2.
446: Элементов, когда вы используете hashmap, у вас внутри тоже массив. Причём он кончится даже раньше, потому что у вас там есть load, фактор, то есть в hashmap вы не можете даже там, ну, с дефолтным фактором получа
447: Где-то полтора миллио миллиарда элементов можете поместить. Вот. И единственное вот из стандартной библиотеки исключение, которое я знаю, это как раз линкед, лист пресловутый, так как
448: Это просто цепочка, связанный список. Теоретически вы в неё можете запихать больше 2 миллиардов элементов, и тогда мы читаем документацию по классу коллекшн. Ну, если у вас памяти очень много на машине, потому что реально
449: Лист очень развесистый, вот тут тогда написано, возвращает число элементов коллекции. Если содержит больше, чем Макс велью, то тогда просто возвращается максвелл, то есть вы через интерфейс collection не можете узнать, сколько конкретно у вас элементов ну, линкит лист это коррек.
450: Обрабатывает. Вот, собственно, мне, по счастью, не приходилось работать с коллекциями длиной больше 2 миллиардов. Если у вас такая задача, я подозреваю, что эта задача очень
451: Специально и вам просто интерфейс collection вообще не подойдёт по хорошему, вам тогда нужно создать свой собственный интерфейс, потому что ну иначе вы напоритесь на слишком много граблей.
452: А вот, кстати, стрим айпиай, там учли как бы то, что в современном мире у нас уже интов часто не хватает, и в стрим айпиай, например, аккаунт возвращает число элементов в стриме это long, то есть там стримить
453: Можете спокойно больше 2 миллиардов, да? Ну.
454: Последний, давайте вопрос на сегодня Александр спрашивает вик хэш мэп, для каких задач следует использовать? Это такая интересная реализация?
455: Hashmap, у которой ключи являются слабыми ссылками. Вот я не помню, я, наверное, вообще в лекциях не упоминаю про слабые ссылки, но это такая полезная штука.
456: Суть в том, что вы можете иметь разные виды ссылок на ваш объект. По умолчанию все ссылки считаются сильными, но вы можете создать слабую ссылку на объект, и если окажется, что ваш объект связан только слабыми ссыл.
457: То сборщик мусора имеет право его собрать, даже если ссылки все ещё присутствуют и, соответственно,
458: В таком случае, когда вы пройдёте по слабой ссылке в очередной раз у вас просто налом окажется. То есть у вас вроде бы там был объект, но если вы по слабой ссылке прошли, не гарантируется, что он там все ещё сохранится даже
459: Если вы вот только что туда положили эти слу штуки, они активно используются в каких-то местах, где вы либо пытаетесь что-то кэшировать в памяти, тогда как раз, когда у вас памяти остаётся мало срабатывает.
460: Галочка, лектор, и он в 1 очередь будет удалять слабые ссылки. То есть это та информация, которая, ну, она вам нужна, но не очень сильно, скажем, вы её можете при необходимости с диска заново прочитать или как-то вычислить каким-то алгоритмом. Вот.
461: То есть, если памяти мало, то вы можете пожертвовать содержимым по этим слабым ссылкам. И вик hashmap это hashmap, у которого в качестве ключей автоматически используются слабые ссылки, то есть объекты в ключах, они
462: Не держатся сильными ссылками, а в значениях держится. Вот бывает ещё ситуация, когда у вас там есть какие-то подсистемы, ну вот, например, с листенерами это лечат.
463: Скажем, у вас есть какие-то объекты, и вы можете регистрировать листенеры, то есть какие-то другие объекты, которые слушают события. Ну я не знаю.
464: Там, например, у вас есть какая-то специальная коллекция наблюдаемая и вы можете регистрировать листенеров, которые если вы, если в этой коллекции что-то произойдёт, добавится элемент, то листенеру
465: Вызовется функция какая-то, элемент добавлен и этот листенер что-то с этим элементом сделает ну просто как-то отреагирует там в интерфейсе например его отобразит или ещё что-нибудь. Вот и бывает такой подход к этим листенера, что
466: Они держатся на слабых ссылках. То есть суть в том, что если никто другой, кроме вот вашей коллекции, ну, понятно, что ваша коллекция, она должна помнить про те листенеры, которые она держит. Но если никто друг
467: Кроме вашей коллекции это не держит, то вы его тоже как бы автоматически удаляете. И тут вполне можно их в викхэм складывать при желании. Вот с листенерами просто частая задача частая.
468: Проблема это утечка памяти. Грубо говоря, вы зарегистрировали листенер где-то, потом забыли его разрегистрировать. Он уже никому не нужен, никто его нигде не использует в вашей системе, но ваша там коллекция упорно передаёт ему события.
469: И это никак не отображается, но на это расходуется память, расходуется время и со временем просто память может кончиться из за этого вот в таких сценариях тоже используется. Ну вообще, я думаю,
470: Если там по коду интелиджи айдиа поискать, там слабых ссылок очень много. То есть в больших таких серьёзных системах промышленного масштаба они оказываются весьма полезными. Все, я думаю, мы на этом на сегодня закончим ещё раз.
471: Большое всем спасибо. И до следующей лекции, я думаю, следующая лекция может пройти очно, если нам ничего не помешает для студентов.