ฉันจะหลีกเลี่ยงการทำซ้ำรหัสเริ่มต้น hashmap ของ hashmap ได้อย่างไร


27

ลูกค้าทุกคนมีรหัสและใบแจ้งหนี้จำนวนมากที่มีวันที่เก็บไว้เป็น Hashmap ของลูกค้าโดย id ของ hashmap ของใบแจ้งหนี้ตามวันที่:

HashMap<LocalDateTime, Invoice> allInvoices = allInvoicesAllClients.get(id);

if(allInvoices!=null){
    allInvoices.put(date, invoice);      //<---REPEATED CODE
}else{
    allInvoices = new HashMap<>();
    allInvoices.put(date, invoice);      //<---REPEATED CODE
    allInvoicesAllClients.put(id, allInvoices);
}

โซลูชัน Java ดูเหมือนว่าจะใช้getOrDefault:

HashMap<LocalDateTime, Invoice> allInvoices = allInvoicesAllClients.getOrDefault(
    id,
    new HashMap<LocalDateTime, Invoice> (){{  put(date, invoice); }}
);

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


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

คำตอบ:


39

Map#computeIfAbsentนี่คือกรณีการใช้งานที่ดีเยี่ยมสำหรับ ตัวอย่างข้อมูลของคุณเทียบเท่ากับ:

allInvoicesAllClients.computeIfAbsent(id, key -> new HashMap<>()).put(date, invoice);

ถ้าidไม่ได้เป็นปัจจุบันเป็นกุญแจสำคัญในallInvoicesAllClientsแล้วมันจะสร้างแผนที่จากidไปอยู่ที่ใหม่และกลับไปใหม่HashMap HashMapถ้าเป็นปัจจุบันเป็นสำคัญแล้วก็จะกลับมาที่มีอยู่idHashMap


1
computeIfAbsent, get (id) (หรือ put ที่ folllowed โดย get (id)) ดังนั้น put ถัดไปจะทำเพื่อแก้ไข item put (date), คำตอบที่ถูกต้อง
Hernán Eche

allInvoicesAllClients.computeIfAbsent(id, key -> Map.of(date, invoice))
Alexander - Reinstate Monica

1
@ Alexander-ReinstateMonica Map.ofสร้าง unmodifiable Mapซึ่งฉันไม่แน่ใจว่า OP ต้องการ
Jacob G.

รหัสนี้จะมีประสิทธิภาพน้อยกว่าที่ OP ใช้หรือไม่ ถามสิ่งนี้เพราะฉันไม่คุ้นเคยกับวิธีที่ Java จัดการฟังก์ชั่นแลมบ์ดา
Zecong Hu

16

computeIfAbsentเป็นทางออกที่ดีสำหรับกรณีนี้โดยเฉพาะ โดยทั่วไปฉันต้องการทราบสิ่งต่อไปนี้เนื่องจากยังไม่มีใครพูดถึง:

hashmap "outer" เพียงเก็บการอ้างอิงถึง hashmap "inner" ดังนั้นคุณสามารถจัดลำดับการดำเนินการใหม่เพื่อหลีกเลี่ยงการทำซ้ำรหัส:

HashMap<LocalDateTime, Invoice> allInvoices = allInvoicesAllClients.get(id);

if (allInvoices == null) {           
    allInvoices = new HashMap<>();
    allInvoicesAllClients.put(id, allInvoices);
}

allInvoices.put(date, invoice);      // <--- no longer repeated

นี่คือวิธีที่เราทำสิ่งนี้มานานหลายทศวรรษก่อนที่ Java 8 จะมาพร้อมกับcomputeIfAbsent()วิธีการที่ยอดเยี่ยม!
Neil Bartlett

1
ฉันยังคงใช้วิธีนี้ในภาษาที่การใช้งานแผนที่ไม่ได้ให้วิธีการแบบ get-or-put-and-return-if-absent ว่านี่อาจเป็นทางออกที่ดีที่สุดในภาษาอื่นอาจจะคุ้มค่าที่จะกล่าวถึงแม้ว่าคำถามนี้จะถูกติดแท็กเฉพาะสำหรับ Java 8
Quinn Mortimer

11

คุณไม่ควรใช้การเริ่มต้นแผนที่ "double brace"

{{  put(date, invoice); }}

ในกรณีนี้คุณควรใช้ computeIfAbsent

allInvoicesAllClients.computeIfAbsent(id, (k) -> new HashMap<>())
                     .put(date, allInvoices);

หากไม่มีแผนที่สำหรับ ID นี้คุณจะแทรกหนึ่งแผนที่ ผลลัพธ์จะเป็นแผนที่ที่มีอยู่หรือคำนวณ จากนั้นคุณสามารถputรายการในแผนที่นั้นพร้อมรับประกันว่าจะไม่เป็นโมฆะ


1
ฉันไม่ทราบว่าใคร downvote ไม่ใช่ฉันบางทีรหัสบรรทัดเดียวอาจทำให้ allInvoicesAllClients ใส่เพราะคุณใช้ ID แทนวันที่ฉันจะแก้ไขมัน
Hernán Eche

1
@ HernánEche Ah ความผิดพลาดของฉัน. ขอบคุณ ใช่ใส่สำหรับidจะทำเช่นกัน คุณสามารถคิดว่าcomputeIfAbsentเป็นแบบมีเงื่อนไขถ้าคุณชอบ และมันก็คืนค่าเช่นกัน
Michael

" คุณไม่ควรใช้การกำหนดค่าเริ่มต้นของแผนที่" double brace "ทำไมล่ะ? (ฉันไม่สงสัยเลยว่าคุณพูดถูกฉันกำลังขอความอยากรู้อยากเห็นอย่างแท้จริง)
Heinzi

1
@Heinzi เพราะมันสร้างคลาสภายในที่ไม่ระบุชื่อ สิ่งนี้เก็บการอ้างอิงไปยังคลาสที่ประกาศไว้ซึ่งหากคุณเปิดเผยแผนที่ (เช่นผ่านผู้ทะเยอทะยาน) จะป้องกันไม่ให้ชั้นล้อมรอบถูกรวบรวมขยะ นอกจากนี้ฉันคิดว่ามันอาจสร้างความสับสนให้กับคนที่ไม่คุ้นเคยกับ Java บล็อก initializer นั้นแทบจะไม่เคยใช้งานเลยและการเขียนแบบนี้ทำให้ดูเหมือนว่า{{ }}มีความหมายพิเศษซึ่งไม่ได้
Michael

1
@Michael: เข้าท่าขอบคุณ ฉันลืมไปเลยว่าคลาสภายในที่ไม่ระบุชื่อนั้นไม่คงที่เสมอ (แม้ว่าพวกเขาไม่จำเป็นต้องเป็น)
Heinzi

5

นี่เป็นคำตอบที่ยาวกว่าคำตอบอื่น ๆ แต่ฉันอ่านได้มากขึ้น:

if(!allInvoicesAllClients.containsKey(id))
    allInvoicesAllClients.put(id, new HashMap<LocalDateTime, Invoice>());

allInvoicesAllClients.get(id).put(date, invoice);

3
สิ่งนี้อาจใช้งานได้กับ HashMap แต่วิธีการทั่วไปนั้นไม่เหมาะสม หากสิ่งเหล่านี้คือ ConcurrentHashMaps การดำเนินการเหล่านี้ไม่ใช่อะตอมมิก ในกรณีเช่นนี้การตรวจสอบจากนั้นจะนำไปสู่สภาพการแข่งขัน โหวตให้แล้วเกลียดผู้อื่น
Michael

0

คุณกำลังทำสองสิ่งที่แตกต่างกันที่นี่: ตรวจสอบให้แน่ใจว่าHashMapมีอยู่และเพิ่มรายการใหม่

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

ดังนั้นตามที่ @Hezi แนะนำคุณสามารถแยกสองขั้นตอนนี้ออกได้

สิ่งที่ผมจะทำคือการสร้าง Offload ของHashMapกับallInvoicesAllClientsวัตถุดังนั้นวิธีการที่ไม่สามารถกลับgetnull

นอกจากนี้ยังช่วยลดความเป็นไปได้สำหรับการแข่งขันระหว่างหัวข้อที่แยกต่างหากที่จะได้รับทั้งnullคำแนะนำจากgetนั้นตัดสินใจที่จะputใหม่HashMapกับรายการเดียว - สองputอาจจะทิ้งคนแรกที่สูญเสียInvoiceวัตถุ

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