Pobieranie elementu z zestawu

323

Dlaczego nie Setzapewnia operacji, aby uzyskać element równy innemu elementowi?

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

Mogę zapytać, czy Setzawiera element równy bar, więc dlaczego nie mogę uzyskać tego elementu? :(

Aby wyjaśnić, equalsmetoda jest zastępowana, ale sprawdza tylko jedno z pól, a nie wszystkie. Tak więc dwa Fooobiekty, które są uważane za równe, mogą mieć różne wartości, dlatego nie mogę po prostu użyć foo.

foobar
źródło
2
Ten post jest już szeroko omawiany i zasugerowano dobre odpowiedzi. Jednak jeśli szukasz tylko uporządkowanego zestawu, po prostu użyj SortedSeti jego implementacji, które są oparte na mapie (np. TreeSetUmożliwia dostęp first()).
Eliran Malka
3
Tęsknię również za tą metodą w dokładnie tym samym przypadku, który opisałeś powyżej. Objective-C ( NSSet) ma taką metodę. Jest wywoływany memberi zwraca obiekt w zestawie, który porównuje „równy” z parametrem membermetody (który może oczywiście być innym obiektem i mieć również inne właściwości, które równe mogą nie sprawdzać).
Mecki,

Odpowiedzi:

118

Nie byłoby sensu pozyskiwać elementu, jeśli jest on równy. A Maplepiej nadaje się do tego przypadku użycia.


Jeśli nadal chcesz znaleźć element, nie masz innej opcji niż użycie iteratora:

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);
    }
}
dacwe
źródło
234
Absolutnie może istnieć sens uzyskania tego elementu. Co jeśli chcesz zaktualizować niektóre wartości elementu po tym, jak został już dodany do zestawu? Na przykład, gdy .equals () nie używa wszystkich pól, jak podano PO. Mniej efektywnym rozwiązaniem byłoby usunięcie elementu i ponowne dodanie go ze zaktualizowanymi wartościami.
KyleM,
14
Nadal będę argumentować, że a Maplepiej pasuje ( Map<Foo, Foo>w tym przypadku.)
dacwe
22
@dacwe, dotarłem tutaj, ponieważ zacząłem szukać sposobu, aby tego uniknąć! Obiekt, który działa jednocześnie jako klucz i odpowiadająca mu wartość, jest dokładnie tym, o czym powinien być zestaw. W moim przypadku chciałbym uzyskać jakiś złożony obiekt z zestawu według klucza (String). Ten ciąg znaków jest enkapsulowany (i unikalny) do odwzorowywanego obiektu. W rzeczywistości cały obiekt obraca się wokół wspomnianego klucza. Ponadto, osoba dzwoniąca wie, że powiedział String, ale nie sam obiekt; właśnie dlatego chce to odzyskać kluczem. Teraz oczywiście używam mapy, ale zachowuje się ona dziwnie.
pauluss86
4
@KyleM Rozumiem przypadek użycia, ale chcę podkreślić znaczenie nie dotykania atrybutów, które są częścią hashCode / equals. Z zestawu Javadoc: „Uwaga: Należy zachować szczególną ostrożność, jeśli zmienne obiekty są używane jako elementy zestawu. Zachowanie zestawu nie jest określone, jeśli wartość obiektu jest zmieniana w sposób, który wpływa na równe porównania, gdy obiekt jest obiektem element w zestawie. ” - Polecam, aby te obiekty były niezmienne lub przynajmniej miały niezmienne kluczowe atrybuty.
stivlo
5
Zgadzam się, że możesz użyć Map<Foo, Foo>jako zamiennika, wadą jest to, że mapa zawsze musi przechowywać co najmniej klucz i wartość (a dla wydajności powinna również przechowywać skrót), podczas gdy zestaw może uciec po prostu przechowując wartość (i może skrót do wydajności). Dobra implementacja zestawu może być równie szybka, Map<Foo, Foo>ale zużywa do 50% mniej pamięci. W przypadku Javy nie będzie to miało znaczenia, ponieważ HashSet jest wewnętrznie oparty na HashMap.
Mecki,
372

Aby odpowiedzieć na dokładne pytanie „ Dlaczego nie Setzapewnia się operacji uzyskania elementu, który jest równy innemu elementowi?”, Odpowiedź brzmiałaby: ponieważ projektanci frameworku kolekcji nie byli zbyt przyszłościowi. Nie przewidzieli twojego bardzo uzasadnionego przypadku użycia, naiwnie próbowali „modelować abstrakcję zestawu matematycznego” (z javadoc) i po prostu zapomnieli dodać użyteczną get()metodę.

Przejdźmy do dorozumianego pytania „ skąd masz ten element”: Myślę, że najlepszym rozwiązaniem jest użycie Map<E,E>zamiast a Set<E>, aby zmapować elementy do siebie. W ten sposób można skutecznie odzyskać element z „zestawu”, ponieważ metoda get () Mapznajdzie element przy użyciu wydajnego algorytmu tabeli skrótów lub algorytmu drzewa. Jeśli chcesz, możesz napisać własną implementację, Setktóra oferuje dodatkową get()metodę, obejmującą Map.

Następujące odpowiedzi są moim zdaniem złe lub złe:

„Nie musisz zdobywać elementu, ponieważ masz już równy przedmiot”: twierdzenie jest błędne, jak już wskazano w pytaniu. Dwa obiekty, które są równe, wciąż mogą mieć inny stan, który nie jest istotny dla równości obiektu. Celem jest uzyskanie dostępu do tego stanu elementu zawartego w Set, a nie stanu obiektu używanego jako „zapytanie”.

„Nie masz innej opcji niż użycie iteratora”: jest to liniowe wyszukiwanie w kolekcji, która jest całkowicie nieefektywna w przypadku dużych zbiorów (jak na ironię, wewnętrznie Setjest zorganizowana jako mapa hash lub drzewo, które można wydajnie przeszukiwać). Nie rób tego! Za pomocą tego podejścia widziałem poważne problemy z wydajnością w rzeczywistych systemach. Moim zdaniem okropne w brakującej get()metodzie jest nie tyle, że obejście jej jest nieco kłopotliwe, ale większość programistów zastosuje metodę wyszukiwania liniowego bez zastanowienia się nad konsekwencjami.

jschreiner
źródło
27
meh Problemem jest zastąpienie implementacji równości tak, aby obiekty nierównomierne były „równe”. Pytanie o metodę, która mówi „przynieś mi identyczny obiekt do tego obiektu”, a następnie spodziewaj się zwrotu nieidentycznego obiektu, wydaje się szalone i łatwe do spowodowania problemów konserwacyjnych. Jak sugerują inni, użycie mapy rozwiązuje wszystkie te problemy: i sprawia, że ​​to, co robisz, jest oczywiste. Łatwo zrozumieć, że dwa nierównomierne obiekty mogą mieć ten sam klucz na mapie, a posiadanie tego samego klucza pokazałoby związek między nimi.
David Ogren
20
Mocne słowa, @David Ogren. Meh Zwariowany? Ale w swoim komentarzu używasz słów „identyczny” i „równy”, tak jakby oznaczały to samo. Oni nie. W szczególności w Javie tożsamość wyraża się za pomocą operatora „==”, a równość wyraża się za pomocą metody equals (). Gdyby oznaczały to samo, w ogóle nie byłoby potrzeby stosowania metody equals (). W innych językach może to oczywiście być inne. Na przykład w Groovy tożsamość to metoda is (), a równość to „==”. Zabawne, prawda?
jschreiner
15
Wasza krytyka mojego użycia słowa identycznego, kiedy powinienem był użyć słowa ekwiwalent, jest bardzo uzasadniona. Ale zdefiniowanie równości na obiekcie, tak aby Foo i Bar były „równe”, ale nie były „wystarczająco równe”, aby mógł je używać w równoważny sposób, stworzy wszelkiego rodzaju problemy zarówno pod względem funkcjonalności, jak i czytelności / konserwacji. Ten problem z Ustaw tylko wierzchołek góry lodowej pod kątem potencjalnych problemów. Na przykład równe obiekty muszą mieć jednakowe kody skrótu. Więc będzie miał potencjalne kolizje mieszające. Czy to szalone, że sprzeciwia się wywołaniu .get (foo), aby uzyskać coś innego niż foo?
David Ogren
12
Prawdopodobnie warto zauważyć, że np. HashSet jest zaimplementowany jako wrapper wokół HashMap (który odwzorowuje klucze na wartość fikcyjną). Zatem jawne użycie HashMap zamiast HashSet nie spowodowałoby nadmiernego zużycia pamięci.
Alexey B.
4
@ user686249 Mam wrażenie, że przekształciło się to w debatę akademicką. Przyznaję, że mogłem być przesadzony, sprzeciwiając się zastąpieniu równych. Zwłaszcza w zastosowaniach takich jak twoje. Ale nadal mam sprzeciw wobec pomysłu wywołania tej metody get(). W twoim przykładzie byłbym bardzo zdezorientowany przez customerSet.get (thisCustomer). (Podczas gdy mapa, jak sugeruje wiele odpowiedzi) byłaby w porządku z canonicalCustomerMap.get (ten klient). Byłbym również w porządku z metodą, która jest wyraźniej nazwana (taka jak metoda członka Objective-C w NSSet).
David Ogren,
19

Jeśli masz równy przedmiot, dlaczego potrzebujesz tego z zestawu? Jeśli „równa się” tylko kluczem, Mapbyłby lepszym wyborem.

W każdym razie, zrobią to:

Foo getEqual(Foo sample, Set<Foo> all) {
  for (Foo one : all) {
    if (one.equals(sample)) {
      return one;
    }
  } 
  return null;
}

W Javie 8 może stać się jednym linerem:

return all.stream().filter(sample::equals).findAny().orElse(null);
Arne Burmeister
źródło
Bardziej podoba mi się ta odpowiedź, po prostu unikałbym użycia dwóch instrukcji return, ponieważ jest to sprzeczne z OOP i sprawia, że ​​wartość Cyklomatyczności jest wyższa.
Leo
8
@Leo dzięki, ale paradygmat pojedynczego wyjścia nie jest przeciwny OOP i jest w większości nieważny dla języków nowocześniejszych niż Fortran lub COBOL, zobacz także softwareengineering.stackexchange.com/questions/118703/...
Arne Burmeister
1
Użycie mapy zamiast zestawu wydaje się lepszą opcją: iteracja elementów zestawu jest większym nakładem pracy niż uzyskanie pojedynczej wartości z mapy. (O (N) vs O (1))
Jamie Flournoy
@JamieFlournoy racja, jeśli musisz wielokrotnie sprawdzać ten sam zestaw dla różnych elementów, to znacznie lepiej. Nie do jednorazowego użytku, ponieważ najpierw wymaga więcej wysiłku, aby zbudować mapę.
Arne Burmeister,
18

Konwertuj zestaw na listę, a następnie użyj getmetody listy

Set<Foo> set = ...;
List<Foo> list = new ArrayList<Foo>(set);
Foo obj = list.get(0);
Do Kra
źródło
37
Nie rozumiem tego Spowoduje to pobranie dowolnego obiektu zestawu. Nie przedmiot.
aioobe,
14

Domyślny zestaw w Javie nie jest niestety przeznaczony do wykonywania operacji „get”, jak dokładnie wyjaśnił jschreiner .

Rozwiązania polegające na użyciu iteratora w celu znalezienia interesującego elementu (sugerowanego przez dacwe ) lub usunięcia tego elementu i ponownego dodania go ze zaktualizowanymi wartościami (sugerowanymi przez KyleM ), mogą działać, ale mogą być bardzo nieefektywne.

Przesłonięcie implementacji równości, tak aby nierównomierne obiekty były „równe”, jak poprawnie stwierdził David Ogren , może łatwo powodować problemy z konserwacją.

A użycie mapy jako wyraźnego zamiennika (jak sugeruje wielu), imho sprawia, że ​​kod jest mniej elegancki.

Jeśli celem jest uzyskanie dostępu do oryginalnej instancji elementu zawartego w zestawie (mam nadzieję, że poprawnie zrozumiałem twój przypadek użycia), oto inne możliwe rozwiązanie.


Osobiście miałem taką samą potrzebę podczas opracowywania gry wideo klient-serwer w Javie. W moim przypadku każdy klient miał kopie komponentów przechowywane na serwerze, a problem występował zawsze, gdy klient musiał zmodyfikować obiekt serwera.

Przekazywanie obiektu przez Internet oznaczało, że klient i tak miał różne instancje tego obiektu. Aby dopasować tę „skopiowaną” instancję do oryginalnej, postanowiłem użyć identyfikatorów UUID Java.

Stworzyłem więc abstrakcyjną klasę UniqueItem, która automatycznie nadaje losowy unikalny identyfikator każdemu wystąpieniu jego podklasy.

Ten identyfikator UUID jest współużytkowany przez klienta i instancję serwera, więc w ten sposób można łatwo dopasować je za pomocą mapy.

Jednak bezpośrednie użycie mapy w podobnej sprawie było nadal nieeleganckie. Ktoś może argumentować, że korzystanie z mapy może być bardziej skomplikowane w utrzymaniu i obsłudze.

Z tych powodów wdrożyłem bibliotekę o nazwie MagicSet, która sprawia, że ​​korzystanie z mapy jest „przezroczyste” dla programisty.

https://github.com/ricpacca/magicset


Podobnie jak oryginalny Java HashSet, MagicHashSet (który jest jedną z implementacji MagicSet dostarczonych w bibliotece) używa kopii zapasowej HashMap, ale zamiast elementów jako kluczy i wartości zastępczej jako wartości, używa UUID elementu jako klucza a sam element jako wartość. Nie powoduje to obciążenia pamięci w porównaniu do normalnego zestawu HashSet.

Ponadto MagicSet może być używany dokładnie jako Zestaw, ale z kilkoma innymi metodami zapewniającymi dodatkowe funkcje, takie jak getFromId (), popFromId (), removeFromId () itp.

Jedynym wymaganiem do korzystania z niego jest to, że każdy element, który chcesz przechowywać w MagicSet, musi rozszerzyć klasę abstrakcyjną UniqueItem.


Oto przykład kodu, który wyobraża sobie odzyskanie oryginalnej instancji miasta z MagicSet, biorąc pod uwagę inną instancję tego miasta o tym samym UUID (lub nawet jego 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
}
ricpacca
źródło
11

Jeśli Twój zestaw jest w rzeczywistości NavigableSet<Foo>(np. A TreeSet), i Foo implements Comparable<Foo>możesz użyć

Foo bar = set.floor(foo); // or .ceiling
if (foo.equals(bar)) {
    // use bar…
}

(Dzięki komentarzowi @ eliran-malka za podpowiedź.)

Jesse Glick
źródło
5
Gdybym nie miał nic przeciwko komukolwiek czytającemu mój kod, który początkowo pomyślał, że oszalałem całkowicie, byłoby to świetne rozwiązanie.
Adam,
10

Dzięki Javie 8 możesz:

Foo foo = set.stream().filter(item->item.equals(theItemYouAreLookingFor)).findFirst().get();

Ale bądź ostrożny, .get () zgłasza NoSuchElementException, lub możesz manipulować Opcjonalnym elementem.

pochmurna pogoda
źródło
5
item->item.equals(theItemYouAreLookingFor)można skrócić dotheItemYouAreLookingFor::equals
Henno Vermeulen
5
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);

Jeśli zdobędziesz tylko jedną nagrodę, nie będzie to bardzo skuteczne, ponieważ będziesz zapętlać wszystkie swoje elementy, ale podczas wykonywania wielu operacji wyszukiwania na dużym zestawie zauważysz różnicę.

Xymon
źródło
5

Dlaczego:

Wygląda na to, że Set odgrywa użyteczną rolę w zapewnianiu możliwości porównania. Został zaprojektowany tak, aby nie przechowywać zduplikowanych elementów.

Z powodu tego zamiaru / projektu, jeśli ktoś otrzyma () odwołanie do przechowywanego obiektu, a następnie zmutuje go, możliwe jest, że zamiary projektowe Seta zostaną udaremnione i mogą spowodować nieoczekiwane zachowanie.

Z JavaDocs

Należy zachować szczególną ostrożność, jeśli zmienne obiekty są używane jako elementy zestawu. Zachowanie zestawu nie jest określone, jeśli wartość obiektu jest zmieniana w sposób, który wpływa na równe porównania, podczas gdy obiekt jest elementem zestawu.

W jaki sposób:

Po wprowadzeniu strumieni można wykonać następujące czynności

mySet.stream()
.filter(object -> object.property.equals(myProperty))
.findFirst().get();
Jason M.
źródło
2

Co z użyciem klasy 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);
    }
}

wyjście:
pozycje pierwsza, druga

prędkość
źródło
1

Wiem, że zostało to zadane i udzielone odpowiedzi dawno temu, jednak jeśli ktoś jest zainteresowany, oto moje rozwiązanie - klasa niestandardowego zestawu wspierana przez HashMap:

http://pastebin.com/Qv6S91n9

Możesz łatwo wdrożyć wszystkie inne metody Set.

Lukas Z.
źródło
7
Preferowane jest dołączenie przykładu zamiast tylko linku do jednego.
Wszyscy pracownicy są niezbędni
1

Zostałem tam zrobiony !! Jeśli używasz Guawy, szybkim sposobem przekonwertowania jej na mapę jest:

Map<Integer,Foo> map = Maps.uniqueIndex(fooSet, Foo::getKey);
Heisenberg
źródło
1

możesz użyć klasy 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);   
 }
 }
}
chiha asma
źródło
1

Jeśli chcesz n-ty element z HashSet, możesz przejść z poniższym rozwiązaniem, tutaj dodałem obiekt ModelClass w 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;
    }
}
Hardik Patel
źródło
1

Jeśli spojrzysz na kilka pierwszych wierszy realizacji java.util.HashSet, zobaczysz:

public class HashSet<E>
    ....
    private transient HashMap<E,Object> map;

Tak czy inaczej HashSetużywa wewnętrznie HashMap, co oznacza, że ​​jeśli użyjesz HashMapbezpośrednio i użyjesz tej samej wartości co klucz, a wartość uzyskasz pożądany efekt i zaoszczędzisz sobie trochę pamięci.

rghome
źródło
1

wygląda na to, że właściwym obiektem do użycia jest Interner z guawy:

Zapewnia równoważne zachowanie String.intern () dla innych niezmiennych typów. Typowe implementacje są dostępne w klasie Interners .

Ma również kilka bardzo interesujących dźwigni, takich jak concurrencyLevel lub rodzaj używanych odniesień (warto zauważyć, że nie oferuje SoftInterner, który uważam za bardziej przydatny niż WeakInterner).

molyss
źródło
0

Ponieważ jakakolwiek konkretna implementacja zestawu może, ale nie musi, mieć dostęp losowy .

Zawsze możesz uzyskać iterator i przejść przez zestaw, używając metody iteratorów, next()aby zwrócić pożądany wynik po znalezieniu równego elementu. Działa to niezależnie od implementacji. Jeśli implementacja NIE ma dostępu losowego (wyobrażenie zestawu wspieranego przez listę połączoną), get(E element)metoda w interfejsie byłaby myląca, ponieważ musiałaby iterować kolekcję, aby znaleźć element do zwrócenia, i get(E element)wydaje się, że sugeruje to, że konieczne, aby Zestaw mógł przejść bezpośrednio do elementu, aby uzyskać.

contains() może, ale nie musi, robić to samo, oczywiście w zależności od implementacji, ale nazwa nie wydaje się narażać na takie same nieporozumienia.

Tom Tresansky
źródło
2
Wszystko, co zrobiłaby metoda get (), jest już wykonywane przez metodę zawiera (). Nie można zaimplementować funkcji include () bez uzyskania zawartego obiektu i wywołania jego metody .equals (). Projektanci API wydawali się nie mieć żadnych skrupułów, jeśli chodzi o dodanie get () do java.util.List, chociaż w niektórych implementacjach byłoby to powolne.
Bryan Rink,
Nie sądzę, że to prawda. Dwa obiekty mogą być równe przez równe, ale nie identyczne przez ==. Jeśli miałeś obiekt A i zestaw S zawierający obiekt B i A. równoważniki (B), ale A! = B i chciałeś uzyskać odniesienie do B, możesz wywołać S.get (A), aby uzyskać odniesienie do B, zakładając, że masz metodę get z semantyką metody get List, co stanowi inny przypadek użycia niż sprawdzenie, czy S.contains (A) (co by to zrobił). To nie jest tak rzadki przypadek użycia kolekcji.
Tom Tresansky,
0

Tak, używaj HashMap... ale w wyspecjalizowany sposób: pułapka, którą przewiduję, próbując użyć HashMapjako pseudonimu, Setjest możliwym pomyleniem między „rzeczywistymi” elementami Map/Seti „kandydującymi” elementami, tj. Elementami używanymi do testowania, czy equalelement jest już obecny. Jest to dalekie od niezawodności, ale odsuwa cię od pułapki:

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

Następnie zrób to:

SelfMappingHashMap<SomeClass> selfMap = new SelfMappingHashMap<SomeClass>();
...
SomeClass candidate = new SomeClass();
if( selfMap.contains( candidate ) ){
    SomeClass realThing = selfMap.tryToGetRealFromCandidate( candidate );
    ...
    realThing.useInSomeWay()...
}

Ale ... chcesz teraz candidateautodestrukcji w jakiś sposób, chyba że programista faktycznie natychmiast umieści go w Map/Set... chciałbyś contains"skazić" candidatetak, aby każde użycie go, chyba że dołączy do Mapanatemy „. Być może mógłbyś SomeClasszaimplementować nowy Taintableinterfejs.

Bardziej satysfakcjonującym rozwiązaniem jest GettableSet , jak poniżej. Jednak aby to zadziałało, musisz być odpowiedzialny za projektowanie SomeClass, aby wszystkie konstruktory były niewidoczne (lub ... zdolne i chętne do zaprojektowania i użycia do tego klasy opakowania):

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
}

Realizacja:

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 ...
}

Twoje NoVisibleConstructorzajęcia wyglądają następująco:

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 jeden problem techniczny z taką NoVisibleConstructorklasą: można się sprzeciwić, że taka klasa jest z natury final, co może być niepożądane. W rzeczywistości zawsze można dodać fałszywy protectedkonstruktor bez parametrów :

protected SomeClass(){
    throw new UnsupportedOperationException();
}

... co pozwoliłoby przynajmniej na kompilację podklasy. Będziesz musiał pomyśleć o tym, czy musisz dołączyć inną getOrCreate()podrzędną metodę fabryczną.

Ostatnim krokiem jest abstrakcyjna klasa podstawowa (NB „element” dla listy, „element członkowski” dla zestawu), taki jak ten dla członków zestawu (jeśli to możliwe - ponownie, zakres zastosowania klasy opakowania, w której klasa nie jest pod twoją kontrolą, lub już ma klasę podstawową itp.), aby maksymalnie ukryć implementację:

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

... użycie jest dość oczywiste (wewnątrz SomeClass„s staticmetody fabryki):

SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );
gryzoń mike
źródło
0

Umowa kodu skrótu wyjaśnia, że:

„Jeśli dwa obiekty są równe zgodnie z metodą Object, wówczas wywołanie metody hashCode na każdym z dwóch obiektów musi dać ten sam wynik w postaci liczb całkowitych.”

Więc twoje założenie:

„Aby wyjaśnić, metoda equals jest nadpisywana, ale sprawdza tylko jedno z pól, nie wszystkie. Tak więc dwa obiekty Foo, które są uważane za równe, mogą mieć różne wartości, dlatego nie mogę po prostu użyć foo.”

jest źle i zrywasz umowę. Jeśli spojrzymy na metodę „zawiera” interfejsu Set, mamy to:

boolean zawiera (Object o);
Zwraca true, jeśli ten zestaw zawiera określony element. Bardziej formalnie, zwraca true, jeśli i tylko jeśli ten zestaw zawiera element „e” taki, że o == null? e == null: o.equals (e)

Aby osiągnąć to, czego chcesz, możesz użyć mapy, w której definiujesz klucz i przechowujesz swój element za pomocą klucza, który określa, w jaki sposób obiekty różnią się między sobą lub są sobie równe.

jfajunior
źródło
-2

Metoda szybkiego pomocnika, która może rozwiązać tę sytuację:

<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();
}
Chris Willmore
źródło
8
To bardzo dziwne, że ta odpowiedź ma tak wiele pozytywnych opinii, ponieważ nie odpowiada na pytanie, ani nawet nie próbuje w żaden sposób na nie odpowiedzieć.
David Conrad
-2

Podejście może być podejściem

   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]));
   }
Vikrant
źródło
-2

Spróbuj użyć tablicy:

ObjectClass[] arrayName = SetOfObjects.toArray(new ObjectClass[setOfObjects.size()]);
canni
źródło