อะไรคือความแตกต่างระหว่างวัตถุ HashMap และแผนที่ใน Java?


349

อะไรคือความแตกต่างระหว่างแผนที่ต่อไปนี้ที่ฉันสร้าง (ในอีกคำถามหนึ่งผู้คนตอบว่าใช้พวกเขาแทนกันได้และฉันสงสัยว่า / พวกเขาแตกต่างกันอย่างไร):

HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();

สมมติว่าคุณใช้งานโดยใช้ HashMap และ Mary ใช้แผนที่ มันจะรวบรวมหรือไม่
GilbertS

คำตอบ:


446

ไม่มีความแตกต่างระหว่างวัตถุ; คุณมีHashMap<String, Object>ทั้งสองกรณี มีความแตกต่างในส่วนต่อประสานที่คุณมีกับวัตถุ ในกรณีแรกที่อินเตอร์เฟซเป็นในขณะที่สองมันเป็นHashMap<String, Object> Map<String, Object>แต่วัตถุต้นแบบนั้นเหมือนกัน

ข้อได้เปรียบในการใช้Map<String, Object>คือคุณสามารถเปลี่ยนวัตถุต้นแบบให้เป็นแผนที่ชนิดอื่นโดยไม่ทำลายสัญญาของคุณด้วยรหัสใด ๆ ที่ใช้งาน หากคุณประกาศเป็นHashMap<String, Object>คุณต้องเปลี่ยนสัญญาของคุณหากคุณต้องการเปลี่ยนการใช้งานพื้นฐาน


ตัวอย่าง: สมมุติว่าฉันเขียนคลาสนี้:

class Foo {
    private HashMap<String, Object> things;
    private HashMap<String, Object> moreThings;

    protected HashMap<String, Object> getThings() {
        return this.things;
    }

    protected HashMap<String, Object> getMoreThings() {
        return this.moreThings;
    }

    public Foo() {
        this.things = new HashMap<String, Object>();
        this.moreThings = new HashMap<String, Object>();
    }

    // ...more...
}

ชั้นมีแผนที่ภายในของวัตถุ string-> ที่ใช้ร่วมกัน (ผ่านวิธีการเข้าถึง) กับ subclasses สมมติว่าฉันเขียนด้วยHashMaps เพื่อเริ่มต้นด้วยเพราะฉันคิดว่านั่นเป็นโครงสร้างที่เหมาะสมที่จะใช้เมื่อเขียนชั้นเรียน

ต่อมาแมรีเขียนรหัสการทำคลาสย่อย เธอมีบางอย่างที่เธอต้องการทำกับทั้งคู่thingsและmoreThingsโดยปกติแล้วเธอวางไว้ในวิธีการทั่วไปและเธอใช้ประเภทเดียวกับที่ฉันใช้บนgetThings/ getMoreThingsเมื่อกำหนดวิธีการของเธอ:

class SpecialFoo extends Foo {
    private void doSomething(HashMap<String, Object> t) {
        // ...
    }

    public void whatever() {
        this.doSomething(this.getThings());
        this.doSomething(this.getMoreThings());
    }

    // ...more...
}

ต่อมาผมตัดสินใจว่าจริง ๆ แล้วมันจะดีกว่าถ้าผมใช้TreeMapแทนในHashMap FooฉันปรับปรุงFooเปลี่ยนแปลงไปHashMap TreeMapตอนนี้SpecialFooไม่ได้รวบรวมอีกต่อไปเพราะฉันทำผิดสัญญา: Fooเคยบอกว่ามันให้HashMaps แต่ตอนนี้มันให้TreeMapsแทน ดังนั้นเราต้องแก้ไขทันทีSpecialFoo(และสิ่งนี้สามารถกระเพื่อมผ่าน codebase)

นอกจากว่าฉันมีเหตุผลที่ดีจริงๆสำหรับการแบ่งปันว่าการนำไปใช้ของฉันกำลังใช้HashMap(และนั่นเกิดขึ้น) สิ่งที่ฉันควรทำคือประกาศgetThingsและgetMoreThingsกลับมาMap<String, Object>โดยไม่ต้องเจาะจงมากไปกว่านั้น ในความเป็นจริงการจํากัดเป็นเหตุผลที่ดีที่จะทำอย่างอื่นแม้จะอยู่ในFooฉันอาจจะประกาศthingsและmoreThingsเป็นMapไม่HashMap/ TreeMap:

class Foo {
    private Map<String, Object> things;             // <== Changed
    private Map<String, Object> moreThings;         // <== Changed

    protected Map<String, Object> getThings() {     // <== Changed
        return this.things;
    }

    protected Map<String, Object> getMoreThings() { // <== Changed
        return this.moreThings;
    }

    public Foo() {
        this.things = new HashMap<String, Object>();
        this.moreThings = new HashMap<String, Object>();
    }

    // ...more...
}

สังเกตว่าตอนนี้ฉันกำลังใช้Map<String, Object>ทุกที่ฉันทำได้เฉพาะเจาะจงเมื่อฉันสร้างวัตถุจริงเท่านั้น

ถ้าฉันทำอย่างนั้นแล้วแมรี่ก็จะทำสิ่งนี้:

class SpecialFoo extends Foo {
    private void doSomething(Map<String, Object> t) { // <== Changed
        // ...
    }

    public void whatever() {
        this.doSomething(this.getThings());
        this.doSomething(this.getMoreThings());
    }
}

... และการเปลี่ยนแปลงFooจะไม่ทำให้SpecialFooการคอมไพล์หยุด

อินเทอร์เฟซ (และคลาสพื้นฐาน) ให้เราเปิดเผยเท่าที่จำเป็นเท่านั้นทำให้ความยืดหยุ่นของเราอยู่ภายใต้การคุ้มครองเพื่อทำการเปลี่ยนแปลงตามความเหมาะสม โดยทั่วไปเราต้องการให้การอ้างอิงของเราเป็นพื้นฐานที่สุด ถ้าเราไม่จำเป็นต้องรู้ว่ามันเป็นเพียงแค่เรียกมันว่าHashMapMap

นี่ไม่ใช่กฎตาบอด แต่โดยทั่วไปการเข้ารหัสไปยังอินเทอร์เฟซทั่วไปส่วนใหญ่จะมีความเปราะน้อยกว่าการเขียนโค้ดลงในบางสิ่งที่เฉพาะเจาะจงมากขึ้น ถ้าฉันจำได้ว่าฉันจะไม่ได้สร้างที่ตั้งแมรี่สำหรับความล้มเหลวด้วยFoo SpecialFooหากแมรี่จำได้ว่าถึงแม้ฉันจะทำผิดFooเธอก็จะประกาศวิธีการส่วนตัวของเธอMapแทนHashMapและFooสัญญาการเปลี่ยนแปลงของฉันจะไม่ส่งผลกระทบต่อรหัสของเธอ

บางครั้งคุณทำไม่ได้บางครั้งคุณต้องเจาะจง แต่ถ้าคุณไม่มีเหตุผลให้ไปที่ส่วนต่อประสานที่เจาะจงน้อยที่สุด


56

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

มันอาจจะสมเหตุสมผลมากกว่าถ้าคุณอ่านบทช่วยสอนของส่วนต่อประสานของ Sun


ฉันถือว่า: ก่อน = HashMap <String, Object> map = ใหม่ HashMap <String, Object> ();
OneWorld

มันคล้ายกับความถี่ที่รายการถูกนำไปใช้เป็น ArrayList
เจอราร์ด

26

ป้อนคำอธิบายรูปภาพที่นี่

แผนที่มีการนำไปใช้ดังนี้:

  1. HashMap Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

  3. แผนผังต้นไม้ Map m = new TreeMap();

  4. WeakHashMap Map m = new WeakHashMap();

สมมติว่าคุณได้สร้างวิธีหนึ่ง (นี่เป็นเพียง pseudocode)

public void HashMap getMap(){
   return map;
}

สมมติว่าความต้องการโครงการของคุณเปลี่ยนไป:

  1. วิธีการที่ควรจะกลับเนื้อหาแผนที่ - HashMapต้องการที่จะกลับมา
  2. วิธีการที่ควรกลับของแผนที่สำคัญในการสั่งซื้อแทรก - ต้องเปลี่ยนประเภทการกลับไป HashMapLinkedHashMap
  3. วิธีการที่ควรกลับของแผนที่สำคัญในการเรียงลำดับ - ต้องเปลี่ยนประเภทการกลับไป LinkedHashMapTreeMap

หากวิธีการของคุณส่งคืนคลาสที่ระบุแทนสิ่งที่ใช้Mapอินเทอร์เฟซคุณต้องเปลี่ยนชนิดของgetMap()วิธีการส่งคืนในแต่ละครั้ง

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


17

ฉันเพิ่งจะทำเช่นนี้เป็นความคิดเห็นเกี่ยวกับคำตอบที่ยอมรับ แต่มันก็ขี้ขลาดเกินไป (ฉันเกลียดที่จะไม่มีตัวแบ่งบรรทัด)

อาดังนั้นความแตกต่างก็คือโดยทั่วไปแผนที่มีวิธีการบางอย่างที่เกี่ยวข้อง แต่มีวิธีการต่าง ๆ หรือสร้างแผนที่เช่น HashMap และวิธีต่าง ๆ เหล่านี้ให้วิธีการที่ไม่ซ้ำกันซึ่งแผนที่ทั้งหมดมี

ตรง - และคุณต้องการใช้อินเทอร์เฟซทั่วไปที่คุณสามารถทำได้ พิจารณา ArrayList กับ LinkedList ความแตกต่างอย่างมากในวิธีที่คุณใช้พวกเขา แต่ถ้าคุณใช้ "รายการ" คุณสามารถสลับระหว่างพวกเขาได้อย่างง่ายดาย

ในความเป็นจริงคุณสามารถแทนที่ด้านขวามือของเครื่องมือเริ่มต้นด้วยคำสั่งแบบไดนามิกมากขึ้น เกี่ยวกับบางสิ่งเช่นนี้:

List collection;
if(keepSorted)
    collection=new LinkedList();
else
    collection=new ArrayList();

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

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

แก้ไขการตอบสนองต่อความคิดเห็น:

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

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

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

นอกจากนี้ยังสังเกตเห็นลักษณะกึ่งบทบาทที่ระบุโดยอินเทอร์เฟซ LinkedList สร้างสแต็กหรือคิวที่ดี ArrayList สร้างสแต็กที่ดี แต่คิวที่น่ากลัว (อีกครั้งการลบจะทำให้เกิดการเปลี่ยนแปลงของรายการทั้งหมด) ดังนั้น LinkedList จะใช้อินเตอร์เฟส Queue, ArrayList ไม่


แต่ในตัวอย่างนี้ฉันจะได้รับวิธีการจากรายการทั่วไปเท่านั้นใช่มั้ย ไม่ว่าฉันจะเป็น LinkedList () หรือ ArrayList () หรือไม่ เป็นเพียงว่าถ้าฉันใช้การเรียงลำดับการแทรก (ซึ่งฉันคิดว่าต้องเป็นวิธีการสำหรับรายการที่ LinkedList และ ArrayList รับโดยการสืบทอด) มันทำงานได้เร็วขึ้นใน LinkedList หรือไม่
Tony Stark

ฉันเดาว่าฉันกำลังมองหาอยู่หรือไม่เมื่อฉันพูด Map <string, string> m = new HashMap <string, string> () Map m ของฉันสามารถใช้วิธีการเฉพาะกับ HashMap ได้หรือไม่ ฉันคิดว่ามันไม่สามารถ?
Tony Stark

อ๊ะเดี๋ยวก่อน Map m ของฉันจากด้านบนต้องมีเมธอดจาก HashMap
Tony Stark

ดังนั้นโดยพื้นฐานแล้วการใช้แผนที่เพียงอย่างเดียวใน 'interface sense' คือถ้าฉันมีวิธีที่ต้องใช้แผนที่ฉันรับประกันได้ว่าแผนที่ชนิดใดจะทำงานในวิธีนี้ แต่ถ้าฉันใช้ hashmap ฉันกำลังบอกว่าวิธีนี้ใช้ได้กับ hashmaps เท่านั้น หรืออีกวิธีหนึ่งวิธีของฉันใช้เฉพาะวิธีที่กำหนดไว้ในคลาสแผนที่ แต่สืบทอดโดยคลาสอื่นที่ขยายแผนที่
Tony Stark

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

12

ตามที่ระบุไว้โดย TJ Crowder และ Adamski การอ้างอิงหนึ่งไปยังอินเตอร์เฟสคืออีกอินเทอร์เฟซสำหรับการใช้งานเฉพาะของอินเตอร์เฟส ตาม Joshua Block คุณควรพยายามเขียนรหัสไปยังส่วนต่อประสานเพื่อให้คุณสามารถจัดการกับการเปลี่ยนแปลงการใช้งานได้ดีขึ้น - เช่นหาก HashMap ไม่เหมาะสำหรับโซลูชันของคุณและคุณต้องการเปลี่ยนการใช้งานแผนที่คุณยังสามารถใช้แผนที่ได้ อินเตอร์เฟซและเปลี่ยนประเภทการเริ่ม


8

ในตัวอย่างที่สองของคุณการอ้างอิง "แผนที่" เป็นประเภทMapซึ่งเป็นอินเทอร์เฟซที่ดำเนินการโดยHashMap(และประเภทอื่น ๆMap) อินเตอร์เฟซนี้เป็นสัญญาบอกว่าวัตถุแผนที่กุญแจสู่ค่านิยมและสนับสนุนการดำเนินงานต่างๆ (เช่นput, get) มันพูดอะไรเกี่ยวกับการดำเนินการของMap(ในกรณีนี้กHashMap)

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


8

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

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

กฎง่ายๆคือให้คงอยู่ในระดับนามธรรมที่สุดเท่าที่จะเป็นไปได้ในระดับ API: ถ้าเช่นวิธีที่คุณใช้ในการเขียนโปรแกรมต้องทำงานบนแผนที่ก็เพียงพอแล้วที่จะประกาศพารามิเตอร์เป็น Map แทนที่จะเป็นแบบเข้มงวด . ด้วยวิธีนี้ผู้บริโภคของ API ของคุณสามารถยืดหยุ่นเกี่ยวกับการใช้งานแผนที่ที่พวกเขาต้องการส่งผ่านไปยังวิธีการของคุณ


4

การเพิ่มคำตอบที่ลงคะแนนสูงสุดและหลายคำตอบที่เน้น "ทั่วไปมากกว่าดีกว่า" ฉันอยากจะขุดอีกเล็กน้อย

Mapเป็นสัญญาโครงสร้างในขณะที่HashMapกำลังดำเนินการให้วิธีการของตัวเองในการจัดการกับปัญหาที่แท้จริงที่แตกต่างกัน: วิธีการคำนวณดัชนีสิ่งที่เป็นกำลังการผลิตและวิธีการเพิ่มขึ้นวิธีการแทรกวิธีการเก็บดัชนีที่ไม่ซ้ำกัน ฯลฯ

ลองดูที่ซอร์สโค้ด:

ในMapเรามีวิธีการcontainsKey(Object key):

boolean containsKey(Object key);

JavaDoc:

บูลีน java.util.Map.containValue (ค่าวัตถุ)

ผลตอบแทนจริงถ้าแผนที่นี้แผนที่หนึ่งหรือมากกว่าหนึ่งคีย์เป็นค่าที่ระบุ เพิ่มเติมอย่างเป็นทางการผลตอบแทนจริงถ้าหากแผนที่นี้มีอย่างน้อยหนึ่งการทำแผนที่ให้มีค่าดังกล่าวว่าv (value==null ? v==null : value.equals(v))การดำเนินการนี้อาจต้องใช้เวลาเชิงเส้นในขนาดแผนที่สำหรับการนำไปใช้งานส่วนใหญ่ของอินเทอร์เฟซแผนที่

พารามิเตอร์: ค่า

ค่าที่มีอยู่ในแผนที่นี้คือการวางเดิมพัน

ผลตอบแทน: จริง

หากแผนที่นี้แผนที่หนึ่งหรือมากกว่าหนึ่งปุ่มเพื่อระบุ

valueThrows:

ClassCastException - ถ้าค่าเป็นประเภทที่ไม่เหมาะสมสำหรับแผนที่นี้ (ไม่บังคับ)

NullPointerException - ถ้าค่าที่ระบุเป็นโมฆะและแผนที่นี้ไม่อนุญาตให้มีค่า Null (เป็นทางเลือก)

มันต้องการการติดตั้งใช้งานเพื่อนำไปใช้ แต่ "วิธีการ" เป็นอิสระเพียงเพื่อให้แน่ใจว่าผลตอบแทนถูกต้อง

ในHashMap:

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

ปรากฎว่าHashMapใช้ hashcode เพื่อทดสอบว่าแผนที่นี้มีรหัสหรือไม่ ดังนั้นมันจึงมีข้อดีของอัลกอริทึมแฮช


3

คุณสร้างแผนที่เดียวกัน

แต่คุณสามารถเติมความแตกต่างเมื่อคุณจะใช้ ในกรณีแรกคุณจะสามารถใช้วิธีการ HashMap พิเศษ (แต่ฉันจำไม่ได้ว่าใครมีประโยชน์จริงๆ) และคุณจะสามารถผ่านมันเป็นพารามิเตอร์ HashMap:

public void foo (HashMap<String, Object) { ... }

...

HashMap<String, Object> m1 = ...;
Map<String, Object> m2 = ...;

foo (m1);
foo ((HashMap<String, Object>)m2); 

3

แผนที่คืออินเทอร์เฟซและ Hashmap เป็นคลาสที่ใช้แผนที่อินเทอร์เฟซ


1

แผนที่เป็นอินเทอร์เฟซและ Hashmap เป็นคลาสที่ใช้งานนั้น

ดังนั้นในการดำเนินการนี้คุณสร้างวัตถุเดียวกัน


0

HashMap เป็นการใช้งานแผนที่ดังนั้นจึงค่อนข้างเหมือนกัน แต่มีวิธี "clone ()" ตามที่ฉันเห็นในคู่มืออ้างอิง)


0
HashMap<String, Object> map1 = new HashMap<String, Object>();
Map<String, Object> map2 = new HashMap<String, Object>();  

ครั้งแรกของทั้งหมดMapเป็นอินเตอร์เฟซที่จะมีการดำเนินงานที่แตกต่างกันเช่น - HashMap, TreeHashMap, LinkedHashMapฯลฯ การเชื่อมต่อการทำงานเช่นชั้นยอดสำหรับการเรียนการดำเนินการ ดังนั้นตามกฎของ OOP คลาสที่Mapเป็นรูปธรรมใด ๆ ที่ดำเนินการก็เป็นMapเช่นนั้น นั่นหมายความว่าเราสามารถกำหนด / วางHashMapตัวแปรประเภทใด ๆให้กับMapตัวแปรชนิดโดยไม่ต้องใช้การหล่อชนิดใด ๆ

ในกรณีนี้เราสามารถกำหนดmap1ให้map2โดยไม่ต้องแคสต์หรือสูญเสียข้อมูลใด ๆ -

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