Получение элемента из набора
Почему 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
.
Ответы
Ответ 1
Невозможно получить элемент, если он равен. A Map
лучше подходит для этой утилиты.
Если вы все еще хотите найти элемент, у вас нет другого варианта, кроме использования итератора:
public static void main(String[] args) {
Set<Foo> set = new HashSet<Foo>();
set.add(new Foo("Hello"));
for (Iterator<Foo> it = set.iterator(); it.hasNext(); ) {
Foo f = it.next();
if (f.equals(new Foo("Hello")))
System.out.println("foo found");
}
}
static class Foo {
String string;
Foo(String string) {
this.string = string;
}
@Override
public int hashCode() {
return string.hashCode();
}
@Override
public boolean equals(Object obj) {
return string.equals(((Foo) obj).string);
}
}
Ответ 2
Чтобы ответить на точный вопрос " Почему не выполняет Set
операцию, чтобы получить элемент, который равен другому элементу?", ответ будет следующим: поскольку разработчики структуры коллекции были не очень перспективный взгляд. Они не ожидали вашего законного варианта использования, наивно пытались "моделировать абстрактную абстракцию математики" (из javadoc) и просто забывали добавить полезный метод get()
.
Теперь к подразумеваемому вопросу " как вы получите элемент then": я думаю, что лучшим решением является использование Map<E,E>
вместо Set<E>
, чтобы сопоставить элементы с самим собой, Таким образом, вы можете эффективно извлекать элемент из "set", потому что метод get() Map
найдет элемент, используя эффективную хеш-таблицу или алгоритм дерева. Если вы хотите, вы можете написать собственную реализацию Set
, которая предлагает дополнительный метод get()
, инкапсулируя Map
.
Следующие ответы, на мой взгляд, плохие или неправильные:
"Вам не нужно получать элемент, потому что у вас уже есть равный объект": утверждение неверно, как вы уже показали в вопросе. Два объекта, которые все равно равны, могут иметь другое состояние, которое не имеет отношения к равенству объекта. Цель состоит в том, чтобы получить доступ к этому состоянию элемента, содержащегося в Set
, а не к состоянию объекта, используемого в качестве "запроса".
"У вас нет другого выбора, кроме использования итератора": это линейный поиск по коллекции, которая совершенно неэффективна для больших наборов (по иронии судьбы, Set
внутри) организована как хэш-карта или дерево, которые могут быть запрошены эффективно). Не делай этого! Я видел серьезные проблемы с производительностью в реальных системах, используя этот подход. На мой взгляд, что ужасно насчет отсутствующего метода get()
- это не так много, что немного обходиться с ним, но большинство программистов будут использовать линейный подход поиска, не думая о последствиях.
Ответ 3
Преобразовать набор в список, а затем использовать get
метод списка
Set<Foo> set = ...;
List<Foo> list = new ArrayList<Foo>(set);
Foo obj = list.get(0);
Ответ 4
Если у вас есть равный объект, зачем вам нужен один из набора? Если он "равен" только клавишам, лучше выбрать Map
.
В любом случае, это сделает следующее:
Foo getEqual(Foo sample, Set<Foo> all) {
for (Foo one : all) {
if (one.equals(sample)) {
return one;
}
}
return null;
}
С Java 8 это может стать одним лайнером:
return all.stream().filter(sample::equals).findAny().orElse(null);
Ответ 5
Набор по умолчанию в 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).
class City extends UniqueItem {
// Somewhere in this class
public void doSomething() {
// Whatever
}
}
public class GameMap {
private MagicSet<City> cities;
public GameMap(Collection<City> cities) {
cities = new MagicHashSet<>(cities);
}
/*
* cityId is the UUID of the city you want to retrieve.
* If you have a copied instance of that city, you can simply
* call copiedCity.getId() and pass the return value to this method.
*/
public void doSomethingInCity(UUID cityId) {
City city = cities.getFromId(cityId);
city.doSomething();
}
// Other methods can be called on a MagicSet too
}
Ответ 6
Если ваш набор фактически является NavigableSet<Foo>
(например, TreeSet
) и Foo implements Comparable<Foo>
, вы можете использовать
Foo bar = set.floor(foo); // or .ceiling
if (foo.equals(bar)) {
// use bar…
}
(Спасибо за комментарий @eliran-malkas за подсказку.)
Ответ 7
С помощью Java 8 вы можете:
Foo foo = set.stream().filter(item->item.equals(theItemYouAreLookingFor)).findFirst().get();
Но будьте осторожны .get() генерирует исключение NoSuchElementException или вы можете манипулировать необязательным элементом.
Ответ 8
Object objectToGet = ...
Map<Object, Object> map = new HashMap<Object, Object>(set.size());
for (Object o : set) {
map.put(o, o);
}
Object objectFromSet = map.get(objectToGet);
Если вы только сделаете это, это не будет очень эффективным, потому что вы будете перебирать все свои элементы, но при выполнении нескольких запросов на большом наборе вы заметите разницу.
Ответ 9
Почему:
Кажется, что Set играет полезную роль в обеспечении средств сравнения. Он предназначен не для хранения повторяющихся элементов.
Из-за этого намерения/дизайна, если нужно было() ссылаться на сохраненный объект, а затем мутировать его, возможно, что намерения дизайна Set могут быть сорваны и могут вызвать неожиданное поведение.
Из JavaDocs
Следует проявлять большую осторожность, если изменяемые объекты используются в качестве заданных элементов. Поведение набора не указывается, если значение объекта изменяется таким образом, который влияет на равные сравнения, когда объект является элементом в наборе.
Как
Теперь, когда были введены потоки, можно сделать следующее
mySet.stream()
.filter(object -> object.property.equals(myProperty))
.findFirst().get();
Ответ 10
Для этого лучше использовать объект Java HashMap http://download.oracle.com/javase/1,5.0/docs/api/java/util/HashMap.html
Ответ 11
Я знаю, об этом просили и ответили давно, однако, если кому-то интересно, вот мое решение - класс настраиваемых наборов, поддерживаемый HashMap:
http://pastebin.com/Qv6S91n9
Вы можете легко реализовать все другие методы Set.
Ответ 12
Было это сделано!! Если вы используете Guava, быстрый способ конвертировать его в карту:
Map<Integer,Foo> map = Maps.uniqueIndex(fooSet, Foo::getKey);
Ответ 13
вы можете использовать класс Iterator
import java.util.Iterator;
import java.util.HashSet;
public class MyClass {
public static void main(String[ ] args) {
HashSet<String> animals = new HashSet<String>();
animals.add("fox");
animals.add("cat");
animals.add("dog");
animals.add("rabbit");
Iterator<String> it = animals.iterator();
while(it.hasNext()) {
String value = it.next();
System.out.println(value);
}
}
}
Ответ 14
Если вы хотите nth Element из HashSet, вы можете пойти ниже,
здесь я добавил объект ModelClass в HashSet.
ModelClass m1 = null;
int nth=scanner.nextInt();
for(int index=0;index<hashset1.size();index++){
m1 = (ModelClass) itr.next();
if(nth == index) {
System.out.println(m1);
break;
}
}
Ответ 15
Если вы посмотрите на первые несколько строк реализации java.util.HashSet
вы увидите:
public class HashSet<E>
....
private transient HashMap<E,Object> map;
Таким образом, HashSet
любом случае использует HashMap
внутренне, что означает, что если вы просто используете HashMap
напрямую и используете то же значение, что и ключ и значение, вы получите желаемый эффект и сэкономите себе немного памяти.
Ответ 16
похоже, что правильным объектом для использования является Interner из guava:
Обеспечивает эквивалентное поведение для String.intern() для других неизменяемых типов. Общие реализации доступны из класса Interners.
У него также есть несколько очень интересных рычагов, таких как concurrencyLevel или тип используемых ссылок (возможно, стоит отметить, что он не предлагает SoftInterner, который я считаю более полезным, чем WeakInterner).
Ответ 17
Как насчет использования класса Arrays?
import java.util.Arrays;
import java.util.List;
import java.util.HashSet;
import java.util.Arrays;
public class MyClass {
public static void main(String args[]) {
Set mySet = new HashSet();
mySet.add("one");
mySet.add("two");
List list = Arrays.asList(mySet.toArray());
Object o0 = list.get(0);
Object o1 = list.get(1);
System.out.println("items " + o0+","+o1);
}
}
выход:
пункты один, два
Ответ 18
Поскольку любая конкретная реализация Set может быть или не быть произвольным доступом.
Вы всегда можете получить iterator и пройти через Set, используя метод itaterators next()
, чтобы вернуть результат, который вы хотите, как только вы найти равный элемент. Это работает независимо от реализации. Если реализация НЕ является произвольным доступом (изображение связанного списка с установленным набором), метод get(E element)
в интерфейсе был бы обманчив, так как ему пришлось бы перебирать коллекцию, чтобы найти возвращаемый элемент, а get(E element)
кажется, подразумевает, что это было бы необходимо, чтобы Set мог перейти непосредственно к элементу, чтобы получить.
contains()
может или не обязательно делать то же самое, конечно, в зависимости от реализации, но имя, похоже, не поддается такому же недоразумению.
Ответ 19
Да, используйте HashMap
... но специализированным способом: ловушка, которую я предвижу при попытке использовать HashMap
в качестве псевдо-Set
, - это возможная путаница между "актуальными" элементами Map/Set
и "кандидат", т.е. элементы, используемые для проверки того, присутствует ли элемент equal
. Это далеко не надежное, но подталкивает вас от ловушки:
class SelfMappingHashMap<V> extends HashMap<V, V>{
@Override
public String toString(){
// otherwise you get lots of "... object1=object1, object2=object2..." stuff
return keySet().toString();
}
@Override
public V get( Object key ){
throw new UnsupportedOperationException( "use tryToGetRealFromCandidate()");
}
@Override
public V put( V key, V value ){
// thorny issue here: if you were indavertently to `put`
// a "candidate instance" with the element already in the `Map/Set`:
// these will obviously be considered equivalent
assert key.equals( value );
return super.put( key, value );
}
public V tryToGetRealFromCandidate( V key ){
return super.get(key);
}
}
Затем сделайте следующее:
SelfMappingHashMap<SomeClass> selfMap = new SelfMappingHashMap<SomeClass>();
...
SomeClass candidate = new SomeClass();
if( selfMap.contains( candidate ) ){
SomeClass realThing = selfMap.tryToGetRealFromCandidate( candidate );
...
realThing.useInSomeWay()...
}
Но... теперь вы хотите, чтобы candidate
был самоуничтожен каким-то образом, если программист фактически не отправил его в Map/Set
... вы бы хотели, чтобы contains
"испортил" candidate
, так что любое использование его, если оно не присоединяется к Map
, делает его "анафемой". Возможно, вы могли бы сделать SomeClass
реализовать новый Taintable
интерфейс.
Более удовлетворительным решением является GettableSet, как показано ниже. Однако для этого вам нужно либо отвечать за дизайн SomeClass
, чтобы сделать все конструкторы невидимыми (или... умеющими и желающими разработать и использовать для него класс-оболочку):
public interface NoVisibleConstructor {
// again, this is a "nudge" technique, in the sense that there is no known method of
// making an interface enforce "no visible constructor" in its implementing classes
// - of course when Java finally implements full multiple inheritance some reflection
// technique might be used...
NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet );
};
public interface GettableSet<V extends NoVisibleConstructor> extends Set<V> {
V getGenuineFromImpostor( V impostor ); // see below for naming
}
Реализация:
public class GettableHashSet<V extends NoVisibleConstructor> implements GettableSet<V> {
private Map<V, V> map = new HashMap<V, V>();
@Override
public V getGenuineFromImpostor(V impostor ) {
return map.get( impostor );
}
@Override
public int size() {
return map.size();
}
@Override
public boolean contains(Object o) {
return map.containsKey( o );
}
@Override
public boolean add(V e) {
assert e != null;
V result = map.put( e, e );
return result != null;
}
@Override
public boolean remove(Object o) {
V result = map.remove( o );
return result != null;
}
@Override
public boolean addAll(Collection<? extends V> c) {
// for example:
throw new UnsupportedOperationException();
}
@Override
public void clear() {
map.clear();
}
// implement the other methods from Set ...
}
Затем ваши классы NoVisibleConstructor
выглядят следующим образом:
class SomeClass implements NoVisibleConstructor {
private SomeClass( Object param1, Object param2 ){
// ...
}
static SomeClass getOrCreate( GettableSet<SomeClass> gettableSet, Object param1, Object param2 ) {
SomeClass candidate = new SomeClass( param1, param2 );
if (gettableSet.contains(candidate)) {
// obviously this then means that the candidate "fails" (or is revealed
// to be an "impostor" if you will). Return the existing element:
return gettableSet.getGenuineFromImpostor(candidate);
}
gettableSet.add( candidate );
return candidate;
}
@Override
public NoVisibleConstructor addOrGetExisting( GettableSet<? extends NoVisibleConstructor> gettableSet ){
// more elegant implementation-hiding: see below
}
}
PS одна техническая проблема с таким классом NoVisibleConstructor
: можно возразить, что такой класс по своей природе final
, что может быть нежелательным. На самом деле вы всегда можете добавить конструктор protected
фиктивный без параметров:
protected SomeClass(){
throw new UnsupportedOperationException();
}
... который, по крайней мере, позволит скомпилировать подкласс. Затем вам нужно подумать о необходимости включить в подкласс еще один метод getOrCreate()
factory.
Заключительный шаг - это абстрактный базовый класс (элемент "NB" для списка, "член" для набора), как это для ваших членов набора (когда это возможно - снова, область использования обертки класс, в котором класс не находится под вашим контролем или уже имеет базовый класс и т.д.), для максимального скрытия реализации:
public abstract class AbstractSetMember implements NoVisibleConstructor {
@Override
public NoVisibleConstructor
addOrGetExisting(GettableSet<? extends NoVisibleConstructor> gettableSet) {
AbstractSetMember member = this;
@SuppressWarnings("unchecked") // unavoidable!
GettableSet<AbstractSetMembers> set = (GettableSet<AbstractSetMember>) gettableSet;
if (gettableSet.contains( member )) {
member = set.getGenuineFromImpostor( member );
cleanUpAfterFindingGenuine( set );
} else {
addNewToSet( set );
}
return member;
}
abstract public void addNewToSet(GettableSet<? extends AbstractSetMember> gettableSet );
abstract public void cleanUpAfterFindingGenuine(GettableSet<? extends AbstractSetMember> gettableSet );
}
... использование довольно очевидно (внутри метода SomeClass
static
factory):
SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );
Ответ 20
Быстрый вспомогательный метод, который может решить эту проблему:
<T> T onlyItem(Collection<T> items) {
if (items.size() != 1)
throw new IllegalArgumentException("Collection must have single item; instead it has " + items.size());
return items.iterator().next();
}
Ответ 21
Попробуйте использовать массив:
ObjectClass[] arrayName = SetOfObjects.toArray(new ObjectClass[setOfObjects.size()]);
Ответ 22
Следующим может быть подход
SharedPreferences se_get = getSharedPreferences("points",MODE_PRIVATE);
Set<String> main = se_get.getStringSet("mydata",null);
for(int jk = 0 ; jk < main.size();jk++)
{
Log.i("data",String.valueOf(main.toArray()[jk]));
}