Почему не Set
предусмотрена операция для получения элемента, равного другому элементу?
Set<Foo> set = ...;
...
Foo foo = new Foo(1, 2, 3);
Foo bar = set.get(foo); // get the Foo element from the Set that equals foo
Я могу спросить, Set
содержит ли элемент элемент, равный bar
, так почему я не могу получить этот элемент? :(
Чтобы уточнить, equals
метод переопределен, но он проверяет только одно из полей, а не все. Таким образом, два Foo
объекта, которые считаются равными, на самом деле могут иметь разные значения, поэтому я не могу просто использовать foo
.
java
collections
set
equals
Foobar
источник
источник
SortedSet
и его реализации, которые основаны на карте (например,TreeSet
позволяет получить доступfirst()
).NSSet
) имеет такой метод. Он вызываетсяmember
и возвращает объект в наборе, который сравнивает «равный» с параметромmember
метода (который, конечно, может быть другим объектом и также иметь разные свойства, которые равенство может не проверять).Ответы:
Не было бы смысла получать элемент, если он равен. А
Map
лучше подходит для этого варианта использования.Если вы все еще хотите найти элемент, у вас нет другого выбора, кроме как использовать итератор:
источник
Map
лучше подходит (Map<Foo, Foo>
в данном случае)Map<Foo, Foo>
в качестве замены, недостатком является то, что карта всегда должна хранить, по крайней мере, ключ и значение (и для производительности она также должна хранить хэш), в то время как набор может уйти, просто сохраняя значение (и возможно хэш для производительности). Таким образом, хорошая реализация набора может быть одинаково быстрой,Map<Foo, Foo>
но использовать до 50% меньше памяти. В случае Java это не имеет значения, так как HashSet все равно основан на HashMap.Чтобы ответить на точный вопрос « Почему не
Set
предусмотрена операция для получения элемента, равного другому элементу?», Ответ был бы таков: дизайнеры каркаса коллекции были не очень дальновидны. Они не ожидали вашего вполне законного варианта использования, наивно пытались «смоделировать абстракцию математического набора» (из javadoc) и просто забыли добавить полезныйget()
метод.Теперь к подразумеваемому вопросу « как вы получаете элемент тогда»: я думаю, что лучшее решение состоит в том, чтобы использовать
Map<E,E>
вместоSet<E>
, чтобы отобразить элементы на себя. Таким образом, вы можете эффективно извлекать элемент из «set», потому что метод get ()Map
найдет элемент, используя эффективную хеш-таблицу или алгоритм дерева. Если вы хотите, вы можете написать свою собственную реализацию,Set
которая предлагает дополнительныйget()
метод, инкапсулируяMap
.Следующие ответы, на мой взгляд, плохие или неправильные:
«Вам не нужно получать элемент, потому что у вас уже есть равный объект»: утверждение неверно, как вы уже показали в вопросе. Два равных объекта все же могут иметь различное состояние, которое не имеет отношения к равенству объектов. Цель состоит в том, чтобы получить доступ к этому состоянию элемента, содержащегося в
Set
, а не к состоянию объекта, используемого в качестве «запроса».«У вас нет другого выбора, кроме как использовать итератор»: это линейный поиск по коллекции, который совершенно неэффективен для больших наборов (по иронии судьбы, внутренне
Set
он организован как хэш-карта или дерево, которое можно эффективно запрашивать). Не делай этого! Я видел серьезные проблемы с производительностью в реальных системах с использованием этого подхода. На мой взгляд, что ужасного в пропущенномget()
методе, так это то, что обходиться с ним немного громоздко, а то, что большинство программистов будут использовать метод линейного поиска, не задумываясь о последствиях.источник
get()
. В вашем примере я был бы очень смущен customerSet.get (thisCustomer). (Принимая во внимание, что карта, как подсказывают многие ответы) будет просто хорошо с canonicalCustomerMap.get (этот клиент). Я также согласился бы с методом с более четким именем (таким как метод члена Objective C в NSSet).Если у вас есть равный объект, зачем вам тот из набора? Если он «равен» только по ключу,
Map
то лучшим вариантом будет.Во всяком случае, следующее будет делать это:
С Java 8 это может стать одним вкладышем:
источник
Преобразовать набор в список, а затем использовать
get
метод спискаисточник
Набор по умолчанию в Java, к сожалению, не предназначен для обеспечения операции get, как точно объяснил jschreiner .
Решения использования итератора для поиска интересующего элемента (предложенного dacwe ) или для удаления элемента и его повторного добавления с обновленными значениями (предложенными KyleM ) могут работать, но могут быть очень неэффективными.
Переопределение реализации equals, так что неравные объекты «равны», как правильно сказал Дэвид Огрен , может легко вызвать проблемы с обслуживанием.
И использование Map в качестве явной замены (как предлагают многие), imho, делает код менее элегантным.
Если цель состоит в том, чтобы получить доступ к исходному экземпляру элемента, содержащегося в наборе (надеюсь, я правильно понял ваш вариант использования), вот еще одно возможное решение.
Лично у меня была такая же потребность при разработке видеоигры клиент-сервер с Java. В моем случае у каждого клиента были копии компонентов, хранящихся на сервере, и проблема заключалась в том, что клиенту нужно было изменить объект сервера.
Передача объекта через Интернет означала, что у клиента все равно были разные экземпляры этого объекта. Чтобы сопоставить этот «скопированный» экземпляр с исходным, я решил использовать Java UUID.
Поэтому я создал абстрактный класс UniqueItem, который автоматически дает случайный уникальный идентификатор каждому экземпляру его подклассов.
Этот UUID разделяется между клиентом и экземпляром сервера, поэтому таким образом может быть легко сопоставить их, просто используя карту.
Однако непосредственное использование карты в аналогичном сценарии использования было все еще не элегантным. Кто-то может поспорить, что использование карты может быть более сложным в обслуживании и обработке.
По этим причинам я реализовал библиотеку под названием MagicSet, которая делает использование Map «прозрачным» для разработчика.
https://github.com/ricpacca/magicset
Как и исходный Java HashSet, MagicHashSet (который является одной из реализаций MagicSet, представленных в библиотеке) использует вспомогательный HashMap, но вместо того, чтобы использовать элементы в качестве ключей и фиктивное значение в качестве значений, он использует UUID элемента в качестве ключа и сам элемент как ценность. Это не вызывает накладных расходов при использовании памяти по сравнению с обычным HashSet.
Более того, MagicSet может использоваться именно как Set, но с некоторыми другими методами, обеспечивающими дополнительные функциональные возможности, такие как getFromId (), popFromId (), removeFromId () и т. Д.
Единственное требование для его использования - это то, что любой элемент, который вы хотите сохранить в MagicSet, должен расширять абстрактный класс UniqueItem.
Вот пример кода, представляющий, как извлечь оригинальный экземпляр города из MagicSet, учитывая другой экземпляр этого города с тем же UUID (или даже просто его UUID).
источник
Если ваш набор на самом деле
NavigableSet<Foo>
(например,TreeSet
), иFoo implements Comparable<Foo>
вы можете использовать(Спасибо за комментарий @ eliran-malka за подсказку.)
источник
С Java 8 вы можете сделать:
Но будьте осторожны, .get () генерирует исключение NoSuchElementException, или вы можете манипулировать необязательным элементом.
источник
item->item.equals(theItemYouAreLookingFor)
можно сократить доtheItemYouAreLookingFor::equals
Если вы получите только одно, то это не будет очень эффективным, потому что вы будете циклически перебирать все ваши элементы, но при выполнении множественного извлечения на большом наборе вы заметите разницу.
источник
Зачем:
Кажется, что Set играет полезную роль в обеспечении средств сравнения. Он предназначен для хранения дубликатов элементов.
Из-за этого намерения / дизайна, если кто-то должен получить () ссылку на сохраненный объект, а затем изменить его, возможно, что конструктивные намерения Set могут быть сорваны и могут вызвать неожиданное поведение.
Из JavaDocs
Как:
Теперь, когда потоки были введены, можно сделать следующее
источник
Как насчет использования класса Arrays?
вывод:
элементы один, два
источник
Для этого вам лучше использовать объект Java HashMap http://download.oracle.com/javase/1,5.0/docs/api/java/util/HashMap.html
источник
Я знаю, об этом уже давно спрашивали и отвечали, однако, если кому-то интересно, вот мое решение - пользовательский класс, поддерживаемый HashMap:
http://pastebin.com/Qv6S91n9
Вы можете легко реализовать все другие методы Set.
источник
Там было сделано это! Если вы используете Guava, быстрый способ преобразовать его в карту:
источник
Вы можете использовать класс Iterator
источник
Если вам нужен n-й элемент из HashSet, вы можете воспользоваться приведенным ниже решением, здесь я добавил объект ModelClass в HashSet.
источник
Если вы посмотрите на первые несколько строк реализации,
java.util.HashSet
вы увидите:Таким образом, в любом случае
HashSet
используетсяHashMap
внутренне, что означает, что если вы просто используете aHashMap
и используете то же значение, что и ключ, и значение, вы получите желаемый эффект и сэкономите себе немного памяти.источник
похоже, что правильным объектом для использования является Interner из guava:
У него также есть несколько очень интересных рычагов, таких как concurrencyLevel или тип используемых ссылок (возможно, стоит отметить, что он не предлагает SoftInterner, который я считаю более полезным, чем WeakInterner).
источник
Потому что любая конкретная реализация Set может иметь или не иметь произвольный доступ .
Вы всегда можете получить итератор и пройти через Set, используя метод итераторов,
next()
чтобы вернуть желаемый результат, когда вы найдете равный элемент. Это работает независимо от реализации. Если реализация НЕ является произвольным доступом (представьте набор на основе связанного списка),get(E element)
метод в интерфейсе был бы обманчивым, так как он должен был бы выполнить итерацию коллекции, чтобы найти возвращаемый элемент, и aget(E element)
, по-видимому, подразумевает, что это будет Необходимо, чтобы Набор мог перейти непосредственно к элементу, чтобы получить.contains()
может или не может делать то же самое, конечно, в зависимости от реализации, но название, похоже, не поддается тому же виду недоразумений.источник
Да, использовать
HashMap
... но специализированным способом: ловушка, которую я предвижу при попытке использовать aHashMap
в качестве псевдо,Set
- это возможная путаница между "фактическими" элементамиMap/Set
и "потенциальными" элементами, то есть элементами, используемыми для проверки, является лиequal
элемент уже присутствует. Это далеко не надежно, но выталкивает вас из ловушки:Затем сделайте это:
Но ... теперь вы хотите
candidate
каким-то образом самоуничтожиться, если программист на самом деле не поместит его вMap/Set
... вы хотитеcontains
"испортить" его,candidate
чтобы любое его использование, если оно не присоединялось,Map
превращало его в "анафему" ». Возможно, вы могли быSomeClass
реализовать новыйTaintable
интерфейс.Более удовлетворительное решение - GettableSet , как показано ниже. Однако, чтобы это работало, вы должны либо отвечать за разработку
SomeClass
, чтобы сделать все конструкторы невидимыми (или ... иметь возможность и желать разрабатывать и использовать для этого класс-оболочку):Реализация:
Ваши
NoVisibleConstructor
занятия выглядят так:PS Одна техническая проблема с таким
NoVisibleConstructor
классом: можно возразить, что такой класс неотъемлемоfinal
, что может быть нежелательно. На самом деле вы всегда можете добавить фиктивныйprotected
конструктор без параметров :... который по крайней мере позволит компиляции подкласса. Затем вам нужно подумать о том, нужно ли вам включать другой
getOrCreate()
фабричный метод в подкласс.Последний шаг - это абстрактный базовый класс (NB «элемент» для списка, «член» для набора), подобный этому для членов вашего набора (когда это возможно - опять же, область применения класса-оболочки, где этот класс не находится под вашим контролем, или уже имеет базовый класс и т. д.) для максимального сокрытия реализации:
... использование достаточно очевидно (внутри
SomeClass
«sstatic
фабричного метода):источник
Контракт хеш-кода дает понять, что:
Итак, ваше предположение:
неправильно, и вы нарушаете контракт. Если мы посмотрим на «содержащий» метод интерфейса Set, мы увидим, что:
Чтобы выполнить то, что вы хотите, вы можете использовать карту, где вы определяете ключ и сохраняете свой элемент с ключом, который определяет, как объекты отличаются или равны друг другу.
источник
Быстрый вспомогательный метод, который может решить эту ситуацию:
источник
После может быть подход
источник
Попробуйте использовать массив:
источник