Bagaimana saya bisa menghindari mengulang kode yang menginisialisasi hashmap dari hashmap?

27

Setiap klien memiliki id, dan banyak faktur, dengan tanggal, disimpan sebagai Hashmap klien dengan id, dari hashmap faktur berdasarkan tanggal:

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

Solusi Java tampaknya menggunakan getOrDefault:

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

Tetapi jika get bukan null, saya masih ingin mengeksekusi (tanggal, faktur), dan juga menambahkan data ke "allInvoicesAllClients" masih diperlukan. Jadi sepertinya tidak banyak membantu.

Hernán Eche
sumber
Jika Anda tidak dapat menjamin keunikan kunci, Anda sebaiknya memiliki peta sekunder yang memiliki nilai Daftar <Invoice> bukan hanya Faktur.
Ryan

Jawaban:

39

Ini adalah kasus penggunaan yang sangat baik untuk Map#computeIfAbsent. Cuplikan Anda pada dasarnya setara dengan:

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

Jika idtidak hadir sebagai kunci allInvoicesAllClients, maka itu akan membuat pemetaan dari idke yang baru HashMapdan mengembalikan yang baru HashMap. Jika idada sebagai kunci, maka itu akan mengembalikan yang sudah ada HashMap.

Jacob G.
sumber
1
computeIfAbsent, melakukan get (id) (atau put diikuti oleh get (id)), jadi put selanjutnya dilakukan untuk memperbaiki item put (tanggal), jawaban yang benar.
Hernán Eche
allInvoicesAllClients.computeIfAbsent(id, key -> Map.of(date, invoice))
Alexander - Pasang kembali Monica
1
@ Alexander-ReinstateMonica Map.ofmenciptakan yang tidak dapat dimodifikasi Map, yang saya tidak yakin ingin OP.
Yakub G.
Apakah kode ini kurang efisien daripada apa yang OP miliki pada awalnya? Bertanya ini karena saya tidak terbiasa dengan bagaimana Java menangani fungsi lambda.
Zecong Hu
16

computeIfAbsentadalah solusi tepat untuk kasus khusus ini. Secara umum, saya ingin mencatat yang berikut, karena belum ada yang menyebutkannya:

Hashmap "outer" hanya menyimpan referensi ke hashmap "inner", jadi Anda bisa menyusun ulang operasi untuk menghindari duplikasi kode:

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

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

allInvoices.put(date, invoice);      // <--- no longer repeated
Heinzi
sumber
Ini adalah bagaimana kami melakukan ini selama beberapa dekade sebelum Java 8 datang dengan computeIfAbsent()metode mewahnya !
Neil Bartlett
1
Saya masih menggunakan pendekatan ini hari ini dalam bahasa-bahasa di mana implementasi peta tidak menyediakan metode get-or-put-and-return-if-absen tunggal. Bahwa ini masih bisa menjadi solusi terbaik dalam bahasa lain mungkin layak disebut meskipun pertanyaan ini secara khusus ditandai untuk Java 8.
Quinn Mortimer
11

Anda seharusnya tidak pernah menggunakan inisialisasi peta "penjepit ganda".

{{  put(date, invoice); }}

Dalam hal ini, Anda harus menggunakan computeIfAbsent

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

Jika tidak ada peta untuk ID ini, Anda akan memasukkan satu. Hasilnya adalah peta yang ada atau yang dihitung. Anda kemudian dapat putitem di peta itu dengan jaminan bahwa itu tidak akan nol.

Michael
sumber
1
Saya tidak tahu siapa yang downvote, bukan saya, mungkin kode baris tunggal membingungkan semuaInvoicesAllClients put, karena Anda menggunakan id bukan tanggal, saya akan mengeditnya
Hernán Eche
1
@ Hernáncheche Ah. Kesalahanku. Terima kasih. Ya put untuk iddilakukan juga. Anda dapat menganggap computeIfAbsentsebagai putusan bersyarat jika Anda suka. Dan itu mengembalikan nilainya juga
Michael
" Anda seharusnya tidak pernah menggunakan inisialisasi peta" penjepit ganda ". " Mengapa? (Saya tidak meragukan bahwa Anda benar; Saya meminta dengan rasa ingin tahu yang tulus.)
Heinzi
1
@Heinzi Karena itu menciptakan kelas batin anonim. Ini memegang referensi ke kelas yang mendeklarasikannya, yang jika Anda mengekspos peta (misalnya melalui pengambil) akan mencegah kelas penutup dari pengumpulan sampah. Juga, saya merasa itu bisa membingungkan bagi orang yang kurang terbiasa dengan Jawa; blok penginisialisasi hampir tidak pernah digunakan, dan menulis seperti ini membuatnya tampak {{ }}memiliki makna khusus, yang tidak.
Michael
1
@Michael: Masuk akal, terima kasih. Saya benar-benar lupa bahwa kelas batin anonim selalu non-statis (bahkan jika mereka tidak perlu).
Heinzi
5

Ini lebih panjang dari jawaban yang lain, tetapi jauh lebih mudah dibaca:

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

allInvoicesAllClients.get(id).put(date, invoice);
Serigala
sumber
3
Ini mungkin bekerja untuk HashMap tetapi pendekatan umum tidak optimal. Jika ini ConcurrentHashMaps, operasi ini bukan atom. Dalam kasus seperti itu, check-then-act akan menyebabkan kondisi balapan. Terpilih pula, bagi para pembenci.
Michael
0

Anda sedang melakukan dua hal terpisah di sini: memastikan bahwa HashMapada, dan menambahkan entri baru ke sana.

Kode yang ada memastikan untuk memasukkan elemen baru terlebih dahulu sebelum mendaftarkan peta hash, tetapi itu tidak perlu, karena HashMaptidak peduli tentang pemesanan di sini. Varian tidak ada threadsafe, sehingga Anda tidak kehilangan apa pun.

Jadi, seperti yang disarankan @Heinzi, Anda bisa membagi dua langkah ini.

Apa yang saya juga akan lakukan adalah offload penciptaan HashMapke allInvoicesAllClientsobjek, sehingga getmetode tidak dapat kembali null.

Ini juga mengurangi kemungkinan balapan antara utas-utas terpisah yang keduanya bisa mendapatkan nullpointer dari getdan kemudian memutuskan untuk putyang baru HashMapdengan satu entri - yang kedua putmungkin akan membuang yang pertama, kehilangan Invoiceobjek.

Simon Richter
sumber