สำเนาตื้นของแผนที่ใน Java


107

ตามที่ฉันเข้าใจมีสองวิธี (อาจเป็นวิธีอื่นเช่นกัน) ในการสร้างสำเนาตื้นของMapJava:

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

// first way
shallowCopy = new HashMap<String, Object>(data);

// second way
shallowCopy = (Map<String, Object>) ((HashMap<String, Object>) data).clone();

วิธีหนึ่งเป็นที่ต้องการมากกว่าอีกวิธีหนึ่งและถ้าเป็นเช่นนั้นทำไม?

สิ่งหนึ่งที่ควรกล่าวถึงคือวิธีที่สองให้คำเตือน "Unchecked Cast" ดังนั้นคุณต้องเพิ่ม@SuppressWarnings("unchecked")เพื่อหลีกเลี่ยงซึ่งเป็นเรื่องที่น่ารำคาญเล็กน้อย (ดูด้านล่าง)

@SuppressWarnings("unchecked")
public Map<String, Object> getDataAsMap() {
    // return a shallow copy of the data map
    return (Map<String, Object>) ((HashMap<String, Object>) data).clone();
}

ใน Java เวอร์ชันใหม่กว่า (ตั้งแต่ Java 10 เป็นต้นไป) คุณสามารถใช้เมธอด Map.copyOf static factory ได้ แต่โปรดทราบว่าจะส่งคืนแผนที่ที่ไม่สามารถแก้ไขได้!
Oleksandr Pyrohov

คำตอบ:


106

การคัดลอกโดยใช้ตัวสร้างการคัดลอกจะดีกว่าเสมอ clone()ใน Java เสีย (ดู SO: วิธีการแทนที่วิธีการโคลนอย่างถูกต้อง? )

Josh Bloch เกี่ยวกับการออกแบบ - ตัวสร้างการคัดลอกเทียบกับการโคลน

หากคุณเคยอ่านเนื้อหาเกี่ยวกับการโคลนนิ่งในหนังสือของฉันโดยเฉพาะอย่างยิ่งถ้าคุณอ่านระหว่างบรรทัดคุณจะรู้ว่าฉันคิดว่าcloneมันเสีย [... ] มันน่าเสียดายที่Cloneableเสีย แต่มันเกิดขึ้น

Bloch (ซึ่งเป็นผู้ออกแบบและใช้กรอบการเก็บรวบรวม) กล่าวเพิ่มเติมว่าเขาให้clone()วิธีการเพียงแค่ "เพราะผู้คนคาดหวัง" เขาไม่แนะนำให้ใช้เลย


ฉันคิดว่าการถกเถียงที่น่าสนใจกว่าคือว่าตัวสร้างสำเนาดีกว่าโรงงานคัดลอกหรือไม่ แต่นั่นเป็นการอภิปรายที่แตกต่างกันโดยสิ้นเชิง


1
ใช่นี่เป็นส่วนที่ฉันชอบที่สุดในหนังสือเล่มนี้
polygenelubricants

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

4
การไม่ใช้ ctor คัดลอกต้องการให้คุณทราบว่าคุณกำลังคัดลอกแผนที่ไปใช้งานใด? ดูเหมือนเป็นข้อ จำกัด ที่ไม่จำเป็น
jon-hanson

"ที่เขาให้โคลน () วิธีการเพียง" เพราะคนคาดหวัง "" - ที่มา?
Adam Parkin

60

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

ในทางกลับกันไม่แนะนำให้ใช้clone()วิธีนี้ตามที่ Josh Bloch อธิบาย

ในส่วนของอินเทอร์เฟซแผนที่ (และคำถามของคุณซึ่งคุณถามวิธีคัดลอกแผนที่ไม่ใช่ HashMap) คุณควรใช้Map # putAll () :

คัดลอกการแมปทั้งหมดจากแผนที่ที่ระบุไปยังแผนที่นี้ (การดำเนินการทางเลือก) ผลของการเรียกนี้เทียบเท่ากับการเรียกใส่ (k, v) บนแผนที่นี้หนึ่งครั้งสำหรับการแมปแต่ละครั้งจากคีย์ k ถึงค่า v ในแผนที่ที่ระบุ

ตัวอย่าง:

// HashMap here, but it works for every implementation of the Map interface
Map<String, Object> data = new HashMap<String, Object>();
Map<String, Object> shallowCopy = new HashMap<String, Object>();

shallowCopy.putAll(data);

2
ดังนั้นเพื่อชี้แจง: หากคุณรู้ว่ากำลังคัดลอกไปยังการนำไปใช้งานMapที่มีตัวสร้างการคัดลอกไม่มีเหตุผลที่จะไม่ใช้ตัวสร้างการคัดลอกแล้ว?
Adam Parkin

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

1
แน่นอนว่าโดยทั่วไปแล้วฉันชอบ 1-liners ดีกว่า 2-liners ;)
Adam Parkin

11

คัดลอกแผนที่โดยไม่ทราบถึงการนำไปใช้:

static final Map shallowCopy(final Map source) throws Exception {
    final Map newMap = source.getClass().newInstance();
    newMap.putAll(source);
    return newMap;
}

3
พิจารณาเพิ่ม<K,V>พารามิเตอร์ประเภทเพื่อช่วยให้มั่นใจในความปลอดภัยของประเภท
บาเร็ตต์

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