Bir hashmap hashmapını başlatan kodun tekrarlanmasını nasıl önleyebilirim?


27

Her müşterinin bir kimliği ve tarihle birlikte müşterilere kimliğe göre Hashmap olarak depolanan birçok faturası vardır.

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 çözümü aşağıdaki gibi görünüyor getOrDefault:

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

Ancak get boş değilse, hala koymak (tarih, fatura) yürütmek istiyorum ve ayrıca "allInvoicesAllClients" için veri eklemek hala gereklidir. Yani pek yardımcı olmuyor.


Anahtarın benzersizliğini garanti edemiyorsanız, ikincil haritanın sadece Fatura yerine Liste <Fatura> değerine sahip olması en iyisidir.
Ryan

Yanıtlar:


39

Bu mükemmel bir kullanım durumudur Map#computeIfAbsent. Snippet'iniz aslında aşağıdakilere eşdeğerdir:

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

Eğer idbir anahtar olarak bulunmaz allInvoicesAllClients, o zaman gelen eşlemesi oluştururuz idbir yeniye HashMapve yeni dönün HashMap. idBir anahtar olarak varsa , var olanı döndürür HashMap.


1
computeIfAbsent, bir get (id) (veya get (id) tarafından döndürülen bir put) yapar, bu nedenle sonraki put, item put (tarih), doğru cevabı düzeltmek için yapılır.
Hernán Eche

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

1
@ Alexander-ReinstateMonica , OP'nin istediğinden emin olmadığım Map.ofdeğiştirilemez bir şey yaratıyor Map.
Jacob

Bu kod OP'nin aslından daha az verimli olur mu? Bunu soruyorum çünkü Java'nın lambda işlevlerini nasıl işlediğini bilmiyorum.
Zecong Hu

16

computeIfAbsentbu özel durum için harika bir çözümdür. Genel olarak, aşağıdakileri not etmek isterim, çünkü henüz kimse bundan bahsetmedi:

"Dış" hashmap sadece "iç" hashmap için bir referans depolar , böylece sadece kod çoğalmasını önlemek için işlemleri yeniden sıralayabilirsiniz:

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'in süslü computeIfAbsent()yöntemiyle gelmeden önce bunu yıllarca böyle yaptık !
Neil Bartlett

1
Bu yaklaşımı bugün hala harita uygulamasının tek bir al ya da koy ve koy ve geri dön yöntemi sağlamadığı dillerde kullanıyorum. Bu soru özellikle Java 8 için etiketlenmiş olsa da, bu diğer dillerde hala en iyi çözüm olabilir.
Quinn Mortimer

11

Hemen hemen hiçbir zaman "çift ayraç" harita başlatma kullanmamalısınız.

{{  put(date, invoice); }}

Bu durumda, computeIfAbsent

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

Bu kimlik için harita yoksa bir tane ekleyeceksiniz. Sonuç mevcut veya hesaplanmış harita olacaktır. Daha sonra putbu haritadaki öğeleri, boş olmayacağını garanti ederek yapabilirsiniz.


1
Ben değil, kimin aşağı oy bilmiyorum, belki de tek satır kodu kafa karıştırıyor allInvoicesAllClients koymak, çünkü tarih yerine id kullanırsanız, bunu düzenleyeceğim
Hernán Eche

1
@ HernánEche Ah. Benim hatam. Teşekkürler. Evet koymak idda yapılır. İsterseniz computeIfAbsentkoşullu olarak düşünebilirsiniz . Ve bu da değeri döndürüyor
Michael

" Asla" çift ayraç "harita başlatma kullanmamalısınız. " Neden? (Haklı olduğundan şüphe duymuyorum; gerçek bir meraktan soruyorum.)
Heinzi

1
@Heinzi Çünkü isimsiz bir iç sınıf yaratıyor. Bu, haritayı açığa çıkarırsanız (örneğin bir alıcı aracılığıyla) çevreleyen sınıfın çöp toplanmasını önleyecek olan, bildiren sınıfa başvuruda bulunur. Ayrıca, Java'ya daha az aşina olan insanlar için kafa karıştırıcı olabileceğini düşünüyorum; başlatıcı blokları neredeyse hiç kullanılmaz ve bu şekilde yazmak {{ }}, özel bir anlamı vardır, ki bunu yapmaz.
Michael

1
@Michael: Mantıklı, teşekkürler. Anonim iç sınıfların her zaman statik olmadıklarını (olmamasına rağmen) tamamen unuttum.
Heinzi

5

Bu, diğer cevaplardan daha uzun, ancak daha okunabilir:

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

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

3
Bu bir HashMap için işe yarayabilir ancak genel yaklaşım uygun değildir. Bunlar ConcurrentHashMaps olsaydı, bu işlemler atomik değildir. Böyle bir durumda, kontrol et ve sonradan hareket yarış koşullarına yol açacaktır. Yine de oy verdiler, nefret edenler.
Michael

0

Burada iki ayrı şey yapıyorsunuz: var olmasını sağlamak HashMapve yeni girişi eklemek.

Mevcut kod, karma haritayı kaydetmeden önce yeni öğeyi eklediğinizden emin olur, ancak bu gerekli değildir, çünkü HashMapburadaki siparişle ilgilenmez. Her iki değişken de güvenli değildir, bu yüzden hiçbir şey kaybetmezsiniz.

Yani, @Heinzi'nin önerdiği gibi, bu iki adımı bölebilirsiniz.

Ne yapmam da olur yaratılması boşaltması olduğu HashMapiçin allInvoicesAllClientsbu nedenle, nesne getyöntemi döndüremez null.

Bu aynı zamanda, hem nullişaretçilerden hem de tek bir girişle yeni bir noktaya getkarar verebilen ayrı dişler arasındaki ırkların olasılığını azaltır - ikincisi nesneyi kaybederek ilkini atacaktır .putHashMapputInvoice

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.