รับองค์ประกอบจากชุด


323

เหตุใดจึงไม่มี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ได้


2
โพสต์นี้มีการพูดคุยกันอย่างกว้างขวางและได้รับคำแนะนำที่ดี อย่างไรก็ตามหากคุณเพียงแค่มองหาชุดที่สั่งซื้อเพียงใช้SortedSetและการนำไปใช้งานซึ่งเป็นแบบแผนที่ (เช่นTreeSetอนุญาตให้เข้าถึงfirst())
— Eliran Malka

3
ฉันพลาดวิธีนั้นเช่นกันสำหรับกรณีเดียวกันกับที่คุณอธิบายไว้ข้างต้น วัตถุประสงค์ -C ( NSSet) มีวิธีการดังกล่าว มันถูกเรียกmemberและส่งคืนวัตถุภายในชุดที่เปรียบเทียบ "เท่ากับ" กับพารามิเตอร์ของmemberวิธีการ (ซึ่งแน่นอนอาจเป็นวัตถุที่แตกต่างกันและยังมีคุณสมบัติที่แตกต่างกันที่เท่ากับอาจไม่ตรวจสอบ)
— Mecki

คำตอบ:


118

จะไม่มีจุดรับองค์ประกอบถ้ามันเท่ากัน 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);
    }
}

234
อาจมีจุดที่จะได้รับองค์ประกอบ จะทำอย่างไรถ้าคุณต้องการอัปเดตค่าบางอย่างขององค์ประกอบหลังจากที่เพิ่มไปยังชุดแล้ว ตัวอย่างเช่นเมื่อ. equals () ไม่ได้ใช้ฟิลด์ทั้งหมดตามที่ระบุไว้ OP โซลูชันที่มีประสิทธิภาพน้อยกว่าคือการลบองค์ประกอบและเพิ่มองค์ประกอบนั้นใหม่ด้วยค่าที่อัปเดตแล้ว
— KyleM

14
ฉันยังคงยืนยันว่าMapมีความเหมาะสมดีกว่า ( Map<Foo, Foo>ในกรณีนี้.)
— dacwe

22
@dacwe ฉันมาที่นี่เพราะฉันเริ่มมองหาวิธีที่จะหลีกเลี่ยงสิ่งนั้น! วัตถุที่ทำหน้าที่ในเวลาเดียวกันทั้งที่เป็นคีย์และค่าที่สอดคล้องกันเป็นสิ่งที่ชุดควรจะเกี่ยวกับ ในกรณีของฉันฉันต้องการรับวัตถุที่ซับซ้อนบางอย่างจากชุดโดยคีย์ (String) สตริงนี้ถูกห่อหุ้ม (และไม่ซ้ำกัน) กับวัตถุที่ถูกแม็พ ในความเป็นจริงวัตถุทั้งหมด 'หมุนรอบ' ที่สำคัญกล่าวว่า นอกจากนี้ผู้เรียกรู้ว่า String แต่ไม่ใช่วัตถุ นั่นคือสาเหตุที่มันต้องการดึงข้อมูลด้วยกุญแจ แน่นอนว่าฉันใช้แผนที่ในตอนนี้ แต่มันก็ยังมีพฤติกรรมแปลก
— pauluss86

4
@ KyleM ฉันเข้าใจกรณีการใช้งาน แต่ฉันต้องการเน้นถึงความสำคัญของการไม่แตะต้องคุณลักษณะที่เป็นส่วนหนึ่งของ hashCode / เท่ากับ จากชุด Javadoc: "หมายเหตุ: ต้องใช้ความระมัดระวังอย่างยิ่งหากใช้วัตถุที่ไม่แน่นอนเป็นองค์ประกอบชุดพฤติกรรมของชุดไม่ได้ระบุไว้ถ้าค่าของวัตถุมีการเปลี่ยนแปลงในลักษณะที่มีผลต่อการเปรียบเทียบเท่ากับในขณะที่วัตถุเป็น องค์ประกอบในชุด " - ฉันขอแนะนำให้วัตถุเหล่านั้นไม่เปลี่ยนรูปหรืออย่างน้อยก็มีคุณลักษณะสำคัญที่ไม่เปลี่ยนรูป
— stivlo

5
ฉันยอมรับว่าคุณสามารถใช้Map<Foo, Foo>แทนข้อเสียคือแผนที่จะต้องเก็บกุญแจและค่าอย่างน้อย (และเพื่อประสิทธิภาพมันควรเก็บแฮช) ในขณะที่ชุดสามารถเก็บได้เพียงแค่เก็บค่า (และ อาจแฮสำหรับประสิทธิภาพ) ดังนั้นการติดตั้งที่ดีสามารถทำได้อย่างรวดเร็วเท่ากันMap<Foo, Foo>แต่ใช้หน่วยความจำน้อยลงมากถึง 50% ในกรณีของ Java มันจะไม่สำคัญเนื่องจาก HashSet ขึ้นอยู่กับ HashMap ภายใน
— Mecki

372

เพื่อตอบคำถามที่แม่นยำ " ทำไมไม่Setให้การดำเนินการเพื่อให้ได้องค์ประกอบที่เท่ากับอีกองค์ประกอบหนึ่ง" คำตอบคือ: เนื่องจากนักออกแบบของกรอบงานการรวบรวมไม่ได้มองไปข้างหน้ามากนัก พวกเขาไม่ได้คาดหวังกรณีการใช้ที่ถูกต้องของคุณมากนักพยายามอย่างไร้เหตุผลที่จะ "สร้างแบบจำลองนามธรรมที่เป็นนามธรรม" (จาก javadoc) และเพียงลืมที่จะเพิ่มget()วิธีที่มีประโยชน์

ตอนนี้คำถามโดยนัย " วิธีการที่คุณจะได้รับธาตุแล้ว": ผมคิดว่าทางออกที่ดีที่สุดคือการใช้Map<E,E>แทนSet<E>การแมองค์ประกอบเพื่อตัวเอง ด้วยวิธีนี้คุณสามารถดึงองค์ประกอบจาก "set" ได้อย่างมีประสิทธิภาพเนื่องจากวิธี get () ของMapจะค้นหาองค์ประกอบโดยใช้ตารางแฮชที่มีประสิทธิภาพหรืออัลกอริทึมทรี หากคุณต้องการคุณสามารถเขียนการดำเนินการของตัวเองของSetข้อเสนอพิเศษที่เพิ่มเติมget()วิธีการห่อหุ้มเซลล์แสงอาทิตย์Map

คำตอบต่อไปนี้อยู่ในความเห็นของฉันไม่ดีหรือผิด:

"คุณไม่จำเป็นต้องได้รับองค์ประกอบเพราะคุณมีวัตถุที่เท่ากันแล้ว": การยืนยันเป็นสิ่งที่ผิดตามที่คุณแสดงให้เห็นในคำถาม วัตถุสองชิ้นที่เท่ากันยังคงมีสถานะต่างกันซึ่งไม่เกี่ยวข้องกับความเท่าเทียมกันของวัตถุ เป้าหมายคือการเข้าถึงสถานะขององค์ประกอบนี้ที่มีอยู่ในSetไม่ใช่สถานะของวัตถุที่ใช้เป็น "แบบสอบถาม"

"คุณไม่มีตัวเลือกอื่น ๆ แต่จะใช้ iterator ว่า" นั่นคือการค้นหาเส้นตรงในคอลเลกชันที่มีทั้งหมดไม่มีประสิทธิภาพสำหรับชุดใหญ่ (แดกดันภายในSetมีการจัดแผนที่กัญชาหรือต้นไม้ที่สามารถสอบถามได้อย่างมีประสิทธิภาพ) อย่าทำมัน! ฉันได้เห็นปัญหาประสิทธิภาพการทำงานที่รุนแรงในระบบในชีวิตจริงโดยใช้วิธีการที่ ในความคิดของฉันสิ่งที่แย่มากเกี่ยวกับget()วิธีการที่หายไปนั้นไม่ได้ยุ่งยากมากนัก แต่ก็ยากที่จะแก้ไข แต่ผู้เขียนโปรแกรมส่วนใหญ่จะใช้วิธีการค้นหาแบบเชิงเส้นโดยไม่ต้องนึกถึงผลกระทบ


27
Meh การเอาชนะการใช้งานของเท่ากับเพื่อให้วัตถุที่ไม่เท่ากันคือ "เท่ากัน" เป็นปัญหาที่นี่ การขอวิธีการที่บอกว่า "ให้ฉันเป็นวัตถุที่เหมือนกันกับวัตถุนี้" แล้วคาดว่าวัตถุที่ไม่เหมือนกันที่จะถูกส่งกลับมานั้นดูจะบ้าและง่ายที่จะทำให้เกิดปัญหาในการบำรุงรักษา อย่างที่คนอื่น ๆ แนะนำไว้การใช้แผนที่จะช่วยแก้ปัญหาเหล่านี้ทั้งหมดและทำให้สิ่งที่คุณกำลังอธิบายตนเอง มันง่ายที่จะเข้าใจว่าวัตถุที่ไม่เท่ากันสองตัวอาจมีคีย์เดียวกันในแผนที่และการมีคีย์เดียวกันจะแสดงความสัมพันธ์ระหว่างพวกเขา
— David Ogren

20
คำพูดที่แข็งแกร่ง @David Ogren Meh? บ้า? แต่ในความคิดเห็นของคุณคุณกำลังใช้คำว่า "เหมือนกัน" และ "เท่าเทียมกัน" ราวกับว่าพวกเขามีความหมายเหมือนกัน พวกเขาไม่ได้. โดยเฉพาะอย่างยิ่งใน Java ข้อมูลประจำตัวจะถูกแสดงโดยโอเปอเรเตอร์ "==" และความเท่าเทียมกันแสดงด้วยเมธอด equals () หากพวกเขาหมายถึงสิ่งเดียวกันมันจะไม่จำเป็นต้องมีวิธีการเท่ากับ () เลย ในภาษาอื่น ๆ หลักสูตรนี้อาจแตกต่างกัน ตัวอย่างเช่นใน Groovy identity คือเมธอด is () และความเสมอภาคคือ "==" ตลกใช่มั้ย
— jschreiner

15
การวิจารณ์ของคุณเกี่ยวกับการใช้คำว่าเหมือนกันเมื่อฉันควรใช้คำที่เทียบเท่านั้นมีความถูกต้องมาก แต่การกำหนดเท่ากับบนวัตถุเพื่อให้ Foo และ Bar นั้น "เท่ากัน" แต่ไม่เท่ากับ "เท่ากัน" สำหรับเขาที่จะใช้พวกเขาอย่างเท่าเทียมกันจะสร้างปัญหาทุกประเภททั้งกับการทำงานและการอ่าน / การบำรุงรักษา ปัญหานี้กับ Set just the iceberg สุดยอดสำหรับปัญหาที่อาจเกิดขึ้น ตัวอย่างเช่นวัตถุเท่ากันจะต้องมีรหัสแฮชเท่ากัน ดังนั้นเขาจะมีการชนกันของแฮชที่อาจเกิดขึ้น มันบ้าที่จะคัดค้านการเรียก. get (foo) โดยเฉพาะเพื่อให้ได้สิ่งอื่นที่ไม่ใช่ foo หรือไม่?
— David Ogren

12
มันอาจจะน่าสังเกตว่าเช่น HashSet ถูกนำมาใช้เป็น wrapper รอบ HashMap (ที่แมปกุญแจกับค่าหุ่น) ดังนั้นการใช้ HashMap อย่างชัดเจนแทน HashSet จะไม่ทำให้เกิดค่าใช้จ่ายในการใช้หน่วยความจำ
— Alexey B.

4
@ user686249 ฉันรู้สึกว่าสิ่งนี้ได้กลายเป็นเพียงการอภิปรายทางวิชาการ ฉันยอมรับว่าฉันอาจลงน้ำในการคัดค้านการเอาชนะเท่ากับ โดยเฉพาะอย่างยิ่งในการใช้งานเช่นคุณ แต่ฉันยังคงมีความคิดที่จะเรียกวิธีget()นี้ ในตัวอย่างของคุณฉันจะสับสนมากโดย customerSet.get (thisCustomer) (ในขณะที่แผนที่ตามคำแนะนำหลายคำตอบ) จะดีกับ canonicalCustomerMap.get (ลูกค้ารายนี้) ฉันก็จะตกลงกับวิธีการที่มีชื่อชัดเจนมากขึ้น (เช่นวิธีสมาชิกของ Objective-C บน NSSet)
— David Ogren

19

หากคุณมีวัตถุที่เท่ากันทำไมคุณถึงต้องการวัตถุนั้นจากชุด? หากเป็น "เท่ากัน" โดยคีย์เท่านั้น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);

ฉันชอบคำตอบนี้ดีกว่าฉันจะหลีกเลี่ยงการใช้คำสั่ง return สองรายการเนื่องจากมันขัดกับ OOP และทำให้ค่า Cyclomatic Complexity สูงขึ้น
— ลีโอ

8
@Leo ขอบคุณ แต่กระบวนทัศน์ทางออกเดียวไม่ได้ต่อต้าน OOP และส่วนใหญ่ไม่ถูกต้องสำหรับภาษาที่ทันสมัยกว่า Fortran หรือ COBOL โปรดดูsoftwareengineering.stackexchange.com/questions/118703/ …
— Arne Burmeister

1
การใช้แผนที่แทนที่จะเป็นชุดนั้นดูเหมือนจะเป็นตัวเลือกที่ดีกว่าการวนซ้ำองค์ประกอบต่างๆของชุดจะทำงานได้ดีกว่าการรับค่าเดียวจากแผนที่ (O (N) vs O (1))
— Jamie Flournoy

@JamieFlournoy ถูกต้องถ้าคุณต้องตรวจสอบชุดเดียวกันสำหรับองค์ประกอบต่าง ๆ หลายครั้งดีกว่ามาก สำหรับการใช้งานครั้งเดียวไม่ได้เนื่องจากต้องใช้ความพยายามมากขึ้นในการสร้างแผนที่ก่อน
— Arne Burmeister

18

แปลงชุดเป็นรายการแล้วใช้getวิธีรายการ

Set<Foo> set = ...;
List<Foo> list = new ArrayList<Foo>(set);
Foo obj = list.get(0);

37
ฉันไม่ได้รับสิ่งนี้ นี้จะดึงพลวัตถุของชุด ไม่วัตถุ
— aioobe

14

ตั้งค่าเริ่มต้นใน Java เป็นโชคไม่ดีที่ไม่ได้ออกแบบมาเพื่อให้การดำเนินงาน "รับ" ตามที่jschreinerอธิบายอย่างถูกต้อง

วิธีแก้ปัญหาของการใช้ตัววนซ้ำเพื่อค้นหาองค์ประกอบที่น่าสนใจ (แนะนำโดยdacwe ) หรือลบองค์ประกอบแล้วเพิ่มเข้าไปใหม่ด้วยค่าที่อัปเดต (แนะนำโดยKyleM ) สามารถใช้งานได้ แต่ไม่มีประสิทธิภาพมาก

การเอาชนะการใช้งานของเท่ากับเพื่อให้วัตถุที่ไม่เท่ากันคือ "เท่ากัน" ตามที่ระบุไว้อย่างถูกต้องโดยDavid Ogrenสามารถทำให้เกิดปัญหาการบำรุงรักษาได้อย่างง่ายดาย

และการใช้แผนที่เป็นสิ่งทดแทนอย่างชัดเจน (ตามคำแนะนำของหลาย ๆ คน) ทำให้โค้ดนั้นดูหรูหราน้อยลง

หากเป้าหมายคือการเข้าถึงอินสแตนซ์ดั้งเดิมขององค์ประกอบที่มีอยู่ในชุด (หวังว่าฉันเข้าใจกรณีการใช้งานของคุณอย่างถูกต้อง) นี่เป็นอีกวิธีหนึ่งที่เป็นไปได้


ฉันเองมีความต้องการเดียวกันกับคุณในขณะที่พัฒนาวิดีโอเกมไคลเอนต์เซิร์ฟเวอร์ด้วย Java ในกรณีของฉันลูกค้าแต่ละรายมีสำเนาของส่วนประกอบที่เก็บไว้ในเซิร์ฟเวอร์และปัญหาคือเมื่อใดก็ตามที่ลูกค้าต้องการแก้ไขวัตถุของเซิร์ฟเวอร์

การส่งวัตถุผ่านอินเทอร์เน็ตหมายความว่าลูกค้ามีอินสแตนซ์ต่าง ๆ ของวัตถุนั้นอยู่ดี เพื่อให้ตรงกับอินสแตนซ์ "คัดลอก" นี้กับอินสแตนซ์ดั้งเดิมฉันตัดสินใจใช้ Java UUID

ดังนั้นฉันจึงสร้างคลาสนามธรรม UniqueItem ซึ่งจะให้รหัสเฉพาะที่ไม่ซ้ำแบบสุ่มให้กับแต่ละอินสแตนซ์ของคลาสย่อย

UUID นี้ใช้ร่วมกันระหว่างไคลเอนต์และอินสแตนซ์ของเซิร์ฟเวอร์ดังนั้นวิธีนี้จึงสามารถจับคู่ได้ง่ายโดยใช้แผนที่

อย่างไรก็ตามการใช้แผนที่ใน usecase ที่คล้ายกันโดยตรงนั้นยังไม่เหมาะสม บางคนอาจโต้แย้งว่าการใช้แผนที่อาจมีความซับซ้อนมากขึ้นในการจัดการและจัดการ

ด้วยเหตุผลเหล่านี้ฉันจึงติดตั้งไลบรารีชื่อ MagicSet ซึ่งทำให้การใช้แผนที่ "โปร่งใส" แก่ผู้พัฒนา

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
}

11

หากชุดของคุณเป็นจริงแล้วNavigableSet<Foo>(เช่น a TreeSet) และFoo implements Comparable<Foo>คุณสามารถใช้

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

(ขอบคุณที่ความคิดเห็นของ @ eliran-malka สำหรับคำใบ้)


5
ถ้าฉันไม่คิดว่าใครที่อ่านโค้ดของฉันโดยมีความคิดเริ่มต้นว่าฉันเสียสติไปแล้วนี่จะเป็นทางออกที่ดี
— อดัม

10

ด้วย Java 8 คุณสามารถทำได้:

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

แต่ระวัง,. get () ขว้าง NoSuchElementException หรือคุณสามารถจัดการไอเท็มเสริมได้


5
item->item.equals(theItemYouAreLookingFor)สามารถย่อให้เล็กลงได้theItemYouAreLookingFor::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);

หากคุณทำอย่างใดอย่างหนึ่งจะได้รับสิ่งนี้จะไม่ได้ผลมากเพราะคุณจะวนซ้ำองค์ประกอบทั้งหมดของคุณ แต่เมื่อทำการดึงหลายครั้งในชุดใหญ่คุณจะสังเกตเห็นความแตกต่าง


5

ทำไม:

ดูเหมือนว่าชุดมีบทบาทที่มีประโยชน์ในการจัดให้มีวิธีการเปรียบเทียบ มันถูกออกแบบมาไม่ให้เก็บองค์ประกอบที่ซ้ำกัน

เนื่องจากความตั้งใจ / การออกแบบนี้หากมีใครที่จะได้รับ () อ้างอิงถึงวัตถุที่เก็บไว้จากนั้นกลายพันธุ์มันเป็นไปได้ที่ความตั้งใจในการออกแบบของ Set อาจถูกขัดขวางและอาจทำให้เกิดพฤติกรรมที่ไม่คาดคิด

จากJavaDocs

ต้องใช้ความระมัดระวังอย่างยิ่งถ้าใช้วัตถุที่ไม่แน่นอนเป็นองค์ประกอบชุด พฤติกรรมของชุดไม่ได้ระบุหากค่าของวัตถุมีการเปลี่ยนแปลงในลักษณะที่มีผลต่อการเปรียบเทียบเท่ากับในขณะที่วัตถุเป็นองค์ประกอบในชุด

วิธี:

ตอนนี้สตรีมได้รับการแนะนำให้รู้จัก

mySet.stream()
.filter(object -> object.property.equals(myProperty))
.findFirst().get();

2

แล้วการใช้คลาส Arays ล่ะ?

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

เอาท์พุท:
รายการหนึ่งสอง



1

ฉันรู้ว่าสิ่งนี้ถูกถามและตอบนานแล้ว แต่ถ้าใครสนใจนี่คือทางออกของฉัน - ชุดแบบกำหนดเองที่สนับสนุนโดย HashMap:

http://pastebin.com/Qv6S91n9

คุณสามารถใช้วิธีการตั้งค่าอื่น ๆ ทั้งหมดได้อย่างง่ายดาย


7
เราขอแนะนำให้ใส่ตัวอย่างแทนที่จะเชื่อมโยงกับสิ่งใดสิ่งหนึ่ง
— คนงานทุกคนสำคัญเมื่อ

1

เคยทำแบบนั้นมาก่อน !! หากคุณใช้ Guava วิธีที่รวดเร็วในการแปลงเป็นแผนที่คือ:

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

1

คุณสามารถใช้คลาส 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);   
 }
 }
}

1

หากคุณต้องการองค์ประกอบที่ n จาก 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;
    }
}

1

หากคุณดูสองสามบรรทัดแรกของการนำไปใช้java.util.HashSetคุณจะเห็น:

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

ดังนั้นHashSetใช้HashMapinterally ซึ่งหมายความว่าถ้าคุณเพียงแค่ใช้HashMapโดยตรงและใช้ค่าเดียวกับคีย์และค่าที่คุณจะได้รับผลกระทบที่คุณต้องการและบันทึกหน่วยความจำบางอย่างด้วยตัวคุณเอง


1

ดูเหมือนว่าวัตถุที่เหมาะสมที่จะใช้คือInternerจากฝรั่ง:

จัดให้มีพฤติกรรมที่เทียบเท่ากับ String.intern () สำหรับประเภทที่ไม่เปลี่ยนรูปอื่น ๆ การใช้งานทั่วไปมีอยู่ใน คลาสInterners

มันมีคันโยกที่น่าสนใจไม่กี่อย่างเช่น concurrencyLevel หรือประเภทของการอ้างอิงที่ใช้ (อาจจะคุ้มค่าที่จะสังเกตว่ามันไม่ได้ให้ SoftInterner ซึ่งฉันเห็นว่ามีประโยชน์มากกว่า WeakInterner)


0

เพราะการดำเนินการใด ๆ โดยเฉพาะอย่างยิ่งของชุดอาจหรือไม่อาจจะเข้าถึงโดยสุ่ม

คุณสามารถรับตัววนซ้ำและก้าวผ่านเซตโดยใช้วิธีตัววนซ้ำnext()เพื่อส่งคืนผลลัพธ์ที่คุณต้องการเมื่อคุณพบองค์ประกอบที่เท่ากัน สิ่งนี้ทำงานได้โดยไม่คำนึงถึงการใช้งาน หากการใช้งานไม่ใช่การเข้าถึงแบบสุ่ม (รูปภาพเชื่อมโยงชุดรายการสำรอง) get(E element)วิธีการในอินเทอร์เฟซจะหลอกลวงเนื่องจากจะต้องย้ำคอลเลกชันเพื่อหาองค์ประกอบที่จะกลับมาและget(E element)ดูเหมือนจะหมายความว่านี่จะ จำเป็นที่ Set สามารถข้ามไปยังองค์ประกอบได้โดยตรง

contains() อาจหรืออาจไม่ต้องทำสิ่งเดียวกันแน่นอนขึ้นอยู่กับการใช้งาน แต่ชื่อไม่ดูเหมือนจะยืมตัวเองไปยังประเภทของความเข้าใจผิดที่เหมือนกัน


2
ทุกสิ่งที่เมธอด get () จะทำเสร็จสิ้นแล้วโดยเมธอด contain () คุณไม่สามารถใช้ contain () โดยไม่ได้รับออบเจ็กต์ที่มีอยู่และเรียกใช้เมธอด. equals () ผู้ออกแบบ API ดูเหมือนจะไม่มีความมั่นใจในการเพิ่ม get () ไปยัง java.util.List แม้ว่ามันจะช้าในการใช้งานบางอย่าง
— ไบรอันริงค์

ฉันไม่คิดว่านี่เป็นเรื่องจริง วัตถุสองชิ้นสามารถเท่ากันผ่านทางเท่ากับ แต่ไม่เหมือนกันผ่าน == หากคุณมีวัตถุ A และชุด S ที่มีวัตถุ B และ A.equals (B) แต่ A! = B และคุณต้องการได้รับการอ้างอิงถึง B คุณสามารถโทรหา S.get (A) เพื่อรับการอ้างอิงถึง B สมมติว่าคุณมีวิธีรับด้วยวิธีการรับความหมายของรายการซึ่งเป็นกรณีการใช้งานที่แตกต่างจากการตรวจสอบว่า S.contain (A) (ซึ่งจะ) นี่ไม่ใช่กรณีที่ใช้สำหรับคอลเลกชันที่หายาก
— Tom Tresansky

0

ใช่ใช้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เพื่อที่จะทำให้คอนสตรัคเตอร์ทั้งหมดไม่สามารถมองเห็นได้ (หรือ ... สามารถและเต็มใจที่จะออกแบบและใช้คลาส wrapper สำหรับมัน):

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()วิธีการโรงงานอื่นในคลาสย่อยหรือไม่

ขั้นตอนสุดท้ายคือระดับฐานนามธรรม (องค์ประกอบ "NB" สำหรับรายการ "สมาชิก" สำหรับชุด) เช่นนี้สำหรับสมาชิกชุดของคุณ (เมื่อเป็นไปได้ - อีกครั้งขอบเขตสำหรับการใช้คลาส wrapperที่ชั้นไม่ได้อยู่ภายใต้การควบคุมของคุณ หรือมีคลาสพื้นฐานแล้ว) สำหรับการซ่อนการใช้งานสูงสุด:

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's staticวิธีโรงงาน):

SomeClass setMember = new SomeClass( param1, param2 ).addOrGetExisting( set );

0

สัญญาของรหัสแฮชทำให้ชัดเจนว่า:

"หากวัตถุสองชิ้นมีค่าเท่ากันตามวิธีการของวัตถุการเรียกใช้วิธี hashCode บนวัตถุทั้งสองนั้นจะต้องให้ผลลัพธ์ที่เป็นจำนวนเต็มเหมือนกัน"

ดังนั้นสมมติฐานของคุณ:

"เพื่อให้ความกระจ่างมากขึ้นวิธีการที่เท่าเทียมกันจะถูกแทนที่ แต่มันจะตรวจสอบเพียงหนึ่งในฟิลด์เท่านั้นไม่ใช่ทั้งหมดดังนั้นวัตถุสองรายการที่ถือว่าเท่ากันอาจมีค่าแตกต่างกันนั่นคือเหตุผลที่ฉันไม่สามารถใช้ foo ได้"

ผิดและคุณผิดสัญญา หากเราดูที่ "มี" วิธีการตั้งค่าอินเตอร์เฟซเรามี:

บูลีนมี (วัตถุ o);
ผลตอบแทนจริงถ้าชุดนี้มีองค์ประกอบที่ระบุ เป็นทางการมากขึ้นส่งคืนจริงถ้าหากเซตนี้มีองค์ประกอบ "e" เช่นนั้น == null? e == null: o.equals (e)

เพื่อให้บรรลุสิ่งที่คุณต้องการคุณสามารถใช้แผนที่ที่คุณกำหนดคีย์และจัดเก็บองค์ประกอบของคุณด้วยคีย์ที่กำหนดว่าวัตถุนั้นแตกต่างกันหรือเท่ากัน


-2

วิธีช่วยเหลือด่วนที่อาจแก้ไขสถานการณ์นี้:

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

8
มันแปลกมากที่คำตอบนี้ได้รับ upvotes มากมายเพราะมันไม่ได้ตอบคำถามหรือแม้แต่พยายามที่จะตอบมันในทางใดทางหนึ่ง
— David Conrad

-2

ต่อไปนี้เป็นวิธีการ

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.