Mendapatkan elemen dari Set

323

Mengapa tidak Setmenyediakan operasi untuk mendapatkan elemen yang sama dengan elemen lain?

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

Saya bisa bertanya apakah Setelemen itu mengandung elemen yang sama dengan bar, jadi mengapa saya tidak bisa mendapatkan elemen itu? :(

Untuk memperjelas, equalsmetode ini diganti, tetapi hanya memeriksa salah satu bidang, tidak semua. Jadi dua Fooobjek yang dianggap sama sebenarnya dapat memiliki nilai yang berbeda, itu sebabnya saya tidak bisa hanya menggunakan foo.

foobar
sumber
2
Posting ini sudah banyak dibahas, dan jawaban yang baik telah disarankan. Namun jika Anda hanya mencari set yang dipesan, cukup gunakan SortedSetdan implementasinya, yang berbasis peta (mis. TreeSetMemungkinkan untuk mengakses first()).
Eliran Malka
3
Saya merindukan metode itu juga, untuk kasus yang persis sama seperti yang Anda jelaskan di atas. Objective-C ( NSSet) memiliki metode seperti itu. Disebut memberdan mengembalikan objek dalam himpunan yang membandingkan "sama" dengan parameter membermetode (yang tentu saja bisa menjadi objek yang berbeda dan juga memiliki sifat yang berbeda, yang sama mungkin tidak memeriksa).
Mecki

Jawaban:

118

Tidak akan ada gunanya mendapatkan elemen jika itu sama. A Maplebih cocok untuk usecase ini.


Jika Anda masih ingin menemukan elemen, Anda tidak memiliki pilihan lain selain menggunakan iterator:

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
sumber
234
Benar-benar bisa menjadi titik untuk mendapatkan elemen. Bagaimana jika Anda ingin memperbarui beberapa nilai elemen setelah elemen ditambahkan ke Set? Misalnya, ketika .equals () tidak menggunakan semua bidang, seperti yang ditentukan OP. Solusi yang kurang efisien adalah menghapus elemen dan menambahkannya kembali dengan nilainya diperbarui.
KyleM
14
Saya masih berpendapat bahwa a Maplebih cocok ( Map<Foo, Foo>dalam hal ini.)
dacwe
22
@ David, saya tiba di sini karena saya mulai mencari cara untuk menghindari hal itu! Objek yang bertindak, pada saat yang sama, baik sebagai kunci dan nilai yang sesuai adalah apa yang seharusnya ditetapkan untuk semua himpunan. Dalam kasus saya, saya ingin mendapatkan beberapa objek kompleks dari set oleh kunci (String). String ini dienkapsulasi (dan unik) ke objek yang dipetakan. Bahkan, seluruh benda 'berputar' di sekitar kata kunci. Selanjutnya, penelepon tahu kata String, tetapi bukan objek itu sendiri; itulah sebabnya ia ingin mengambilnya dengan kunci. Saya menggunakan Peta sekarang tentu saja, tetapi itu tetap perilaku aneh.
pauluss86
4
@KyleM Saya mengerti use case, tapi saya ingin menekankan pentingnya tidak menyentuh atribut yang merupakan bagian dari kode hash / sama dengan. Dari Set Javadoc: "Catatan: Perhatian besar harus dilakukan jika objek yang dapat berubah digunakan sebagai elemen set. Perilaku set tidak ditentukan jika nilai suatu objek diubah dengan cara yang mempengaruhi perbandingan yang sama ketika objek tersebut merupakan elemen di set. " - Saya merekomendasikan objek-objek tersebut agar tidak berubah, atau setidaknya memiliki atribut kunci yang tidak dapat diubah.
stivlo
5
Saya setuju bahwa Anda dapat menggunakan Map<Foo, Foo>sebagai pengganti, downside adalah bahwa peta selalu harus menyimpan setidaknya kunci dan nilai (dan untuk kinerja itu juga harus menyimpan hash), sementara satu set bisa lolos hanya menyimpan nilai (dan mungkin hash untuk kinerja). Jadi implementasi perangkat yang baik bisa sama cepatnya Map<Foo, Foo>tetapi menggunakan memori hingga 50% lebih sedikit. Dalam kasus Java tidak masalah, karena HashSet secara internal didasarkan pada HashMap.
Mecki
372

Untuk menjawab pertanyaan yang tepat, " Mengapa tidak Setmenyediakan operasi untuk mendapatkan elemen yang sama dengan elemen lain?", Jawabannya adalah: karena perancang kerangka koleksi tidak terlalu melihat ke depan. Mereka tidak mengantisipasi use case Anda yang sangat sah, dengan naif mencoba "memodelkan abstraksi set matematis" (dari javadoc) dan hanya lupa menambahkan yang bergunaget() metode yang .

Sekarang untuk pertanyaan tersirat " bagaimana Anda mendapatkan elemen itu": Saya pikir solusi terbaik adalah dengan menggunakan, Map<E,E>bukan Set<E>, untuk memetakan elemen untuk diri mereka sendiri. Dengan cara itu, Anda dapat secara efisien mengambil elemen dari "set", karena metode get () dari Mapakan menemukan elemen menggunakan tabel hash yang efisien atau algoritma pohon. Jika Anda mau, Anda bisa menulis implementasi Anda sendiri Setyang menawarkan get()metode tambahan , merangkum Map.

Jawaban berikut menurut saya salah atau salah:

"Anda tidak perlu mendapatkan elemen, karena Anda sudah memiliki objek yang sama": pernyataan itu salah, seperti yang sudah Anda tunjukkan dalam pertanyaan. Dua objek yang sama masih dapat memiliki keadaan yang berbeda yang tidak relevan dengan objek kesetaraan. Tujuannya adalah untuk mendapatkan akses ke status elemen yang terkandung dalam Set, bukan status objek yang digunakan sebagai "kueri".

"Anda tidak memiliki pilihan lain selain menggunakan iterator": itu adalah pencarian linier atas koleksi yang sama sekali tidak efisien untuk set besar (ironisnya, secara internal Setdiatur sebagai peta hash atau pohon yang dapat ditanyakan dengan efisien). Jangan lakukan itu! Saya telah melihat masalah kinerja yang parah dalam sistem kehidupan nyata dengan menggunakan pendekatan itu. Menurut pendapat saya apa yang mengerikan tentang get()metode yang hilang tidak begitu banyak sehingga agak sulit untuk mengatasinya, tetapi sebagian besar programmer akan menggunakan pendekatan pencarian linier tanpa memikirkan implikasinya.

jschreiner
sumber
27
meh. Mengganti implementasi dari equals sehingga objek yang tidak sama adalah "equal" adalah masalahnya di sini. Meminta metode yang mengatakan "dapatkan saya objek yang identik dengan objek ini", dan kemudian mengharapkan objek yang tidak identik dikembalikan sepertinya gila dan mudah menyebabkan masalah pemeliharaan. Seperti yang disarankan orang lain, menggunakan peta memecahkan semua masalah ini: dan itu membuat apa yang Anda lakukan cukup jelas. Sangat mudah untuk memahami bahwa dua objek yang tidak sama mungkin memiliki kunci yang sama di peta, dan memiliki kunci yang sama akan menunjukkan hubungan di antara mereka.
David Ogren
20
Kata-kata yang kuat, @ David Ogren. Ah? Gila? Tetapi dalam komentar Anda, Anda menggunakan kata-kata "identik" dan "sama" seolah-olah mereka memiliki arti yang sama. Mereka tidak. Secara khusus, di Jawa, identitas diekspresikan oleh operator "==" dan kesetaraan diekspresikan oleh metode equals (). Jika mereka bermaksud hal yang sama, tidak akan ada kebutuhan untuk metode equals () sama sekali. Dalam bahasa lain, ini tentu saja bisa berbeda. Misalnya, di Groovy, identitas adalah metode is (), dan persamaan adalah "==". Lucu bukan?
jschreiner
15
Kritik Anda terhadap penggunaan kata identik saat saya seharusnya menggunakan kata yang setara sangat valid. Tetapi mendefinisikan sama pada suatu objek sehingga Foo dan Bar "sama" tetapi tidak "cukup sama" baginya untuk menggunakannya secara setara akan menciptakan semua jenis masalah baik dengan fungsi maupun dengan keterbacaan / pemeliharaan. Masalah ini dengan Tetapkan hanya puncak gunung es untuk masalah potensial. Misalnya, objek yang sama harus memiliki kode hash yang sama. Jadi dia akan memiliki potensi tabrakan hash. Apakah gila untuk keberatan memanggil .get (foo) khusus untuk mendapatkan sesuatu selain foo?
David Ogren
12
Mungkin perlu dicatat bahwa, misalnya, HashSet diimplementasikan sebagai pembungkus di sekitar HashMap (yang memetakan kunci ke nilai dummy). Jadi, menggunakan HashMap secara eksplisit alih-alih HashSet tidak akan menyebabkan overhead dalam penggunaan memori.
Alexey B.
4
@ user686249 Saya merasa seperti ini telah berubah menjadi debat akademik. Saya mengakui bahwa saya mungkin berlebihan dalam menolak untuk menyamakan kedudukan. Terutama dalam penggunaan seperti milik Anda. Tapi, saya masih keberatan dengan ide memanggil metode ini get(). Dalam contoh Anda, saya akan sangat bingung dengan customerSet.get (thisCustomer). (Padahal, Peta, seperti yang disarankan oleh banyak jawaban) akan baik-baik saja dengan canonicalCustomerMap.get (pelanggan ini). Saya juga akan OK dengan metode yang lebih jelas namanya (seperti metode anggota Objective-C di NSSet).
David Ogren
19

Jika Anda memiliki objek yang sama, mengapa Anda membutuhkannya dari set? Jika "sama" hanya dengan kunci, maka Mapakan menjadi pilihan yang lebih baik.

Bagaimanapun, berikut ini akan melakukannya:

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

Dengan Java 8 ini bisa menjadi satu liner:

return all.stream().filter(sample::equals).findAny().orElse(null);
Arne Burmeister
sumber
Saya suka jawaban ini lebih baik, saya hanya akan menghindari menggunakan dua pernyataan kembali, karena itu bertentangan dengan OOP dan itu membuat nilai Kompleksitas Siklomatik lebih tinggi.
Leo
8
@Leo terima kasih, tetapi paradigma jalan keluar tunggal tidak menentang OOP dan sebagian besar tidak valid untuk bahasa yang lebih modern daripada Fortran atau COBOL, lihat juga softwareengineering.stackexchange.com/questions/118703/…
Arne Burmeister
1
Menggunakan Peta alih-alih Set tampaknya merupakan pilihan yang lebih baik: beralih ke elemen Set lebih banyak bekerja daripada mendapatkan nilai tunggal dari Peta. (O (N) vs O (1))
Jamie Flournoy
@ JamieFlournoy benar, jika Anda harus memeriksa set yang sama untuk elemen yang berbeda beberapa kali itu jauh lebih baik. Untuk penggunaan tunggal tidak, karena membutuhkan lebih banyak upaya untuk membangun peta terlebih dahulu.
Arne Burmeister
18

Konversikan set ke daftar, lalu gunakan getmetode daftar

Set<Foo> set = ...;
List<Foo> list = new ArrayList<Foo>(set);
Foo obj = list.get(0);
Untuk Kra
sumber
37
Saya tidak mengerti. Ini akan mengambil objek sewenang - wenang dari set. Tidak itu objek.
aioobe
14

Set Default di Java, sayangnya, tidak dirancang untuk memberikan operasi "get", seperti yang dijelaskan jschreiner secara akurat.

Solusi menggunakan iterator untuk menemukan elemen yang menarik (disarankan oleh dacwe ) atau menghapus elemen dan menambahkannya kembali dengan nilainya diperbarui (disarankan oleh KyleM ), dapat bekerja, tetapi bisa sangat tidak efisien.

Meng-override implementasi equals sehingga objek non-equal adalah "equal", sebagaimana dinyatakan dengan benar oleh David Ogren , dapat dengan mudah menyebabkan masalah pemeliharaan.

Dan menggunakan Peta sebagai pengganti eksplisit (seperti yang disarankan oleh banyak orang), imho, membuat kode kurang elegan.

Jika tujuannya adalah untuk mendapatkan akses ke instance asli dari elemen yang terkandung dalam set (harap saya mengerti benar kasus penggunaan Anda), berikut adalah solusi lain yang mungkin.


Saya pribadi memiliki kebutuhan yang sama saat mengembangkan videogame client-server dengan Java. Dalam kasus saya, setiap klien memiliki salinan komponen yang disimpan di server dan masalahnya adalah kapan pun klien perlu memodifikasi objek server.

Melewati sebuah objek melalui internet berarti bahwa klien memiliki instance berbeda dari objek itu pula. Untuk mencocokkan contoh "disalin" ini dengan yang asli, saya memutuskan untuk menggunakan Java UUIDs.

Jadi saya membuat kelas abstrak UniqueItem, yang secara otomatis memberikan id unik acak untuk setiap instance dari subkelasnya.

UUID ini dibagi antara klien dan server contoh, jadi dengan cara ini bisa mudah untuk mencocokkan mereka hanya dengan menggunakan Peta.

Namun secara langsung menggunakan Peta di usecase yang sama masih tidak sempurna. Seseorang mungkin berpendapat bahwa menggunakan Peta mungkin lebih rumit untuk mempertahankan dan menangani.

Untuk alasan ini saya mengimplementasikan perpustakaan yang disebut MagicSet, yang membuat penggunaan Peta "transparan" kepada pengembang.

https://github.com/ricpacca/magicset


Seperti Java HashSet asli, MagicHashSet (yang merupakan salah satu implementasi MagicSet yang disediakan di perpustakaan) menggunakan backing HashMap, tetapi alih-alih memiliki elemen sebagai kunci dan nilai dummy sebagai nilai, ia menggunakan UUID elemen sebagai kunci dan elemen itu sendiri sebagai nilai. Ini tidak menyebabkan overhead dalam penggunaan memori dibandingkan dengan HashSet normal.

Selain itu, MagicSet dapat digunakan tepat sebagai Set, tetapi dengan beberapa metode lagi menyediakan fungsionalitas tambahan, seperti getFromId (), popFromId (), removeFromId (), dll.

Satu-satunya syarat untuk menggunakannya adalah elemen apa pun yang ingin Anda simpan di MagicSet perlu memperpanjang kelas abstrak UniqueItem.


Berikut adalah contoh kode, membayangkan untuk mengambil contoh asli dari sebuah kota dari MagicSet, memberikan contoh lain dari kota itu dengan UUID yang sama (atau bahkan hanya UUID-nya).

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
sumber
11

Jika set Anda sebenarnya a NavigableSet<Foo>(seperti a TreeSet), dan Foo implements Comparable<Foo>, Anda dapat menggunakan

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

(Terima kasih atas komentar @ eliran-malka untuk petunjuknya.)

Jesse Glick
sumber
5
Jika saya tidak keberatan ada orang yang membaca kode saya memiliki pemikiran awal bahwa saya sudah benar-benar gila, ini akan menjadi solusi yang bagus.
Adam
10

Dengan Java 8 Anda dapat melakukan:

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

Tapi hati-hati, .get () melempar NoSuchElementException, atau Anda dapat memanipulasi item opsional.

cuaca berawan
sumber
5
item->item.equals(theItemYouAreLookingFor)dapat disingkat menjaditheItemYouAreLookingFor::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);

Jika Anda hanya mendapatkan satu, ini tidak akan sangat berkinerja karena Anda akan mengulang semua elemen Anda tetapi ketika melakukan beberapa pengambilan pada set besar Anda akan melihat perbedaannya.

Xymon
sumber
5

Mengapa:

Tampaknya Set memainkan peran yang berguna dalam menyediakan sarana perbandingan. Ini dirancang untuk tidak menyimpan elemen duplikat.

Karena niat / desain ini, jika seseorang mendapatkan () referensi ke objek yang disimpan, kemudian memutasinya, mungkin saja niat desain Set dapat digagalkan dan dapat menyebabkan perilaku yang tidak terduga.

Dari JavaDocs

Perhatian yang besar harus dilakukan jika benda yang dapat berubah digunakan sebagai elemen yang ditetapkan. Perilaku suatu himpunan tidak ditentukan jika nilai suatu objek diubah dengan cara yang mempengaruhi sama dengan perbandingan sementara objek adalah elemen dalam himpunan.

Bagaimana:

Sekarang, ketika Stream telah diperkenalkan, seseorang dapat melakukan hal berikut

mySet.stream()
.filter(object -> object.property.equals(myProperty))
.findFirst().get();
Jason M.
sumber
2

Bagaimana dengan menggunakan kelas Array?

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

output:
item satu, dua

kecepatan
sumber
1

Saya tahu, ini telah ditanyakan dan dijawab lama, namun jika ada yang tertarik, inilah solusi saya - kelas set khusus yang didukung oleh HashMap:

http://pastebin.com/Qv6S91n9

Anda dapat dengan mudah menerapkan semua metode Set lainnya.

Lukas Z.
sumber
7
Lebih disukai menyertakan contoh daripada hanya menautkan ke satu.
Semua Pekerja Sangat Penting
1

Telah ada yang melakukannya !! Jika Anda menggunakan Guava, cara cepat untuk mengubahnya menjadi peta adalah:

Map<Integer,Foo> map = Maps.uniqueIndex(fooSet, Foo::getKey);
Heisenberg
sumber
1

Anda dapat menggunakan kelas 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
sumber
1

Jika Anda ingin nth Element dari HashSet, Anda dapat pergi dengan solusi di bawah ini, di sini saya telah menambahkan objek ModelClass di 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
sumber
1

Jika Anda melihat beberapa baris pertama implementasi java.util.HashSetAnda akan melihat:

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

Jadi, HashSetgunakan HashMapinterally, yang berarti bahwa jika Anda hanya menggunakan secara HashMaplangsung dan menggunakan nilai yang sama dengan kunci dan nilai Anda akan mendapatkan efek yang Anda inginkan dan menghemat sedikit memori.

rghome
sumber
1

sepertinya objek yang tepat untuk digunakan adalah Interner dari jambu biji:

Memberikan perilaku yang setara ke String.intern () untuk tipe tidak berubah lainnya. Implementasi umum tersedia dari kelas Interners .

Ini juga memiliki beberapa tuas yang sangat menarik, seperti concurrencyLevel, atau jenis referensi yang digunakan (mungkin perlu dicatat bahwa itu tidak menawarkan SoftInterner yang saya lihat lebih berguna daripada WeakInterner).

molyss
sumber
0

Karena implementasi tertentu dari Set mungkin atau mungkin bukan akses acak .

Anda selalu bisa mendapatkan iterator dan melangkah melalui Set, menggunakan metode iterators next()untuk mengembalikan hasil yang Anda inginkan setelah Anda menemukan elemen yang sama. Ini berfungsi terlepas dari implementasinya. Jika implementasinya BUKAN akses acak (gambar Set yang didukung daftar-tertaut), get(E element)metode di antarmuka akan menipu, karena harus mengulangi koleksi untuk menemukan elemen yang akan dikembalikan, dan get(E element)tampaknya akan menyiratkan ini akan menjadi perlu, bahwa Set bisa melompat langsung ke elemen untuk mendapatkan.

contains() mungkin atau mungkin tidak harus melakukan hal yang sama, tentu saja, tergantung pada implementasinya, tetapi nama tersebut sepertinya tidak cocok dengan kesalahpahaman yang sama.

Tom Tresansky
sumber
2
Apa pun yang dilakukan oleh metode get () sudah dilakukan oleh metode berisi (). Anda tidak dapat mengimplementasikan isi () tanpa mendapatkan objek yang terkandung dan memanggil metode .equals (). Para desainer API tampaknya tidak memiliki keraguan untuk menambahkan get () ke java.util.List meskipun itu akan lambat dalam beberapa implementasi.
Bryan Rink
Saya pikir ini tidak benar. Dua objek bisa sama melalui sama, tetapi tidak identik melalui ==. Jika Anda memiliki objek A, dan himpunan S yang berisi objek B, dan A. sama dengan (B) tetapi A! = B dan Anda ingin mendapatkan referensi ke B, Anda bisa memanggil S.get (A) untuk mendapatkan referensi ke B, dengan asumsi Anda memiliki metode get dengan semantik metode get List, yang merupakan use case yang berbeda dari memeriksa apakah S.contains (A) (yang akan). Ini bahkan bukan kasus penggunaan yang jarang untuk koleksi.
Tom Tresansky
0

Ya, gunakan HashMap... tetapi dengan cara khusus: jebakan yang saya ramalkan dalam mencoba menggunakan a HashMapsebagai pseudo- Setadalah kemungkinan kebingungan antara elemen "aktual" elemen Map/Set, dan "kandidat", yaitu elemen yang digunakan untuk menguji apakah suatu equalelemen sudah ada. Ini jauh dari sangat mudah, tetapi mendorong Anda menjauh dari perangkap:

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

Kemudian lakukan ini:

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

Tapi ... Anda sekarang ingin candidatemerusak diri sendiri dalam beberapa cara kecuali programmer benar-benar segera meletakkannya di Map/Set... Anda ingin contains"mencemari" candidatesehingga setiap penggunaannya kecuali jika bergabung dengan Mapmembuatnya menjadi "kutukan" ". Mungkin Anda bisa membuat SomeClassimplement yang baruTaintable antarmuka .

Solusi yang lebih memuaskan adalah GettableSet , seperti di bawah ini. Namun, agar ini berfungsi, Anda harus bertanggung jawab atas desain SomeClassuntuk membuat semua konstruktor tidak terlihat (atau ... mampu dan mau merancang dan menggunakan kelas wrapper untuk itu):

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
}

Penerapan:

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

NoVisibleConstructorKelas Anda kemudian terlihat seperti ini:

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 satu masalah teknis dengan NoVisibleConstructorkelas seperti itu: mungkin keberatan bahwa kelas seperti itu secara inheren final, yang mungkin tidak diinginkan. Sebenarnya Anda selalu bisa menambahkan protectedkonstruktor tanpa parameter dummy :

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

... yang paling tidak akan membiarkan kompilasi subclass. Anda kemudian harus memikirkan apakah Anda perlu memasukkan getOrCreate()metode pabrik lain dalam subkelas.

Langkah terakhir adalah kelas dasar abstrak (NB "elemen" untuk daftar, "anggota" untuk set) seperti ini untuk anggota set Anda (bila mungkin - lagi, ruang lingkup untuk menggunakan kelas wrapper di mana kelas tidak di bawah kendali Anda, atau sudah memiliki kelas dasar, dll.), untuk menyembunyikan implementasi maksimal:

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

... penggunaan cukup jelas (dalam Anda SomeClass's staticmetode pabrik):

SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );
mike rodent
sumber
0

Kontrak kode hash memperjelas bahwa:

"Jika dua objek sama menurut metode Objek, maka memanggil metode kode hash pada masing-masing dua objek harus menghasilkan hasil integer yang sama."

Jadi asumsi Anda:

"Untuk memperjelas, metode equals diganti, tetapi hanya memeriksa salah satu bidang, tidak semua. Jadi dua objek Foo yang dianggap sama sebenarnya dapat memiliki nilai yang berbeda, itu sebabnya saya tidak bisa hanya menggunakan foo."

salah dan Anda melanggar kontrak. Jika kita melihat metode "berisi" Set antarmuka, kita memiliki itu:

boolean berisi (Objek o);
Mengembalikan nilai true jika set ini berisi elemen yang ditentukan. Lebih formal, mengembalikan true jika dan hanya jika set ini mengandung elemen "e" sehingga o == null? e == null: o.equals (e)

Untuk mencapai apa yang Anda inginkan, Anda bisa menggunakan Peta tempat Anda mendefinisikan kunci dan menyimpan elemen Anda dengan kunci yang mendefinisikan bagaimana benda-benda berbeda atau sama satu sama lain.

jfajunior
sumber
-2

Metode penolong cepat yang dapat mengatasi situasi ini:

<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
sumber
8
Sangat aneh bahwa jawaban ini mendapatkan begitu banyak upvotes karena tidak menjawab pertanyaan atau bahkan mencoba mengatasinya dengan cara apa pun.
David Conrad
-2

Mengikuti bisa menjadi pendekatan

   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
sumber
-2

Coba gunakan array:

ObjectClass[] arrayName = SetOfObjects.toArray(new ObjectClass[setOfObjects.size()]);
canni
sumber