Làm thế nào tôi có thể tránh lặp lại mã khởi tạo một hashmap của hashmap?


27

Mỗi khách hàng có một id và nhiều hóa đơn, có ngày tháng, được lưu dưới dạng Hashmap của khách hàng theo id, của một hashmap hóa đơn theo ngày:

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

Giải pháp Java dường như được sử dụng getOrDefault:

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

Nhưng nếu get không phải là null, tôi vẫn muốn đặt (ngày, hóa đơn) để thực thi và cũng cần thêm dữ liệu vào "allInvoices ALLCl Client". Vì vậy, nó dường như không giúp được gì nhiều.


Nếu bạn không thể đảm bảo tính duy nhất của khóa, tốt nhất bạn nên đặt bản đồ phụ có giá trị Danh sách <Hóa đơn> thay vì chỉ Hóa đơn.
Ryan

Câu trả lời:


39

Đây là một trường hợp sử dụng tuyệt vời cho Map#computeIfAbsent. Đoạn mã của bạn về cơ bản tương đương với:

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

Nếu idkhông có mặt như một khóa trong allInvoicesAllClients, thì nó sẽ tạo ánh xạ từ idmới HashMapvà trả lại cái mới HashMap. Nếu idcó mặt như một khóa, thì nó sẽ trả về cái hiện có HashMap.


1
compute IfAbsent, thực hiện get (id) (hoặc put folllowed by get (id)), vì vậy, lần đặt tiếp theo được thực hiện để sửa mục đặt (ngày), trả lời đúng.
Hernán Eche

allInvoicesAllClients.computeIfAbsent(id, key -> Map.of(date, invoice))
Alexander - Tái lập Monica

1
@ Alexander-ReinstateMonica Map.oftạo ra một thứ không thể thay đổi Map, mà tôi không chắc OP muốn.
Jacob G.

Mã này sẽ kém hiệu quả hơn những gì OP ban đầu? Hỏi điều này bởi vì tôi không quen với cách Java xử lý các hàm lambda.
Zecong Hu

16

computeIfAbsentlà một giải pháp tuyệt vời cho trường hợp cụ thể này. Nói chung, tôi muốn lưu ý những điều sau, vì chưa ai đề cập đến nó:

Hashmap "bên ngoài" chỉ lưu trữ một tham chiếu đến hashmap "bên trong", vì vậy bạn chỉ có thể sắp xếp lại các hoạt động để tránh trùng lặp mã:

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

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

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

Đây là cách chúng tôi đã làm điều này trong nhiều thập kỷ trước khi Java 8 xuất hiện cùng với computeIfAbsent()phương thức ưa thích của nó !
Neil Bartlett

1
Tôi vẫn sử dụng phương pháp này ngày hôm nay trong các ngôn ngữ mà việc triển khai bản đồ không cung cấp một phương thức get-or-put-and-return-if-abs. Rằng đây vẫn có thể là giải pháp tốt nhất trong các ngôn ngữ khác có thể đáng được đề cập mặc dù câu hỏi này được gắn thẻ cụ thể cho Java 8.
Quinn Mortimer

11

Bạn nên không bao giờ sử dụng khởi tạo bản đồ "cú đúp".

{{  put(date, invoice); }}

Trong trường hợp này, bạn nên sử dụng computeIfAbsent

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

Nếu không có bản đồ cho ID này, bạn sẽ chèn một bản đồ. Kết quả sẽ là bản đồ hiện có hoặc được tính toán. Sau đó, bạn có thể putcác mục trong bản đồ đó với đảm bảo rằng nó sẽ không có giá trị.


1
Tôi không biết ai downvote, không phải tôi, có lẽ mã dòng đơn gây nhầm lẫn cho tất cả mọi người, vì bạn sử dụng id thay vì ngày, tôi sẽ chỉnh sửa nó
Hernán Eche

1
@ HernánEche Ah. Lỗi của tôi. Cảm ơn. Vâng, đặt cho idđược thực hiện là tốt. Bạn có thể nghĩ computeIfAbsentnhư là một điều kiện đặt nếu bạn muốn. Và nó cũng trả về giá trị
Michael

" Bạn nên không bao giờ sử dụng khởi tạo bản đồ" cú đúp ". " Tại sao? (Tôi không nghi ngờ rằng bạn đúng; tôi đang hỏi về sự tò mò thực sự.)
Heinzi

1
@Heinzi Vì nó tạo ra một lớp bên trong ẩn danh. Điều này chứa một tham chiếu đến lớp đã khai báo nó, nếu bạn để lộ bản đồ (ví dụ thông qua một getter) sẽ ngăn lớp bao quanh không bị thu gom rác. Ngoài ra, tôi thấy nó có thể gây nhầm lẫn cho những người ít quen thuộc với Java; Các khối khởi tạo hầu như không bao giờ được sử dụng, và viết nó như thế này làm cho nó trông {{ }}có ý nghĩa đặc biệt, điều mà nó không có.
Michael

1
@Michael: Làm cho ý nghĩa, cảm ơn. Tôi hoàn toàn quên rằng các lớp bên trong ẩn danh luôn không tĩnh (ngay cả khi chúng không cần phải có).
Heinzi

5

Câu này dài hơn các câu trả lời khác, nhưng imho dễ đọc hơn nhiều:

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

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

3
Điều này có thể hoạt động cho HashMap nhưng cách tiếp cận chung là không tối ưu. Nếu đây là ConcurrencyHashMaps, các hoạt động này không phải là nguyên tử. Trong trường hợp như vậy, kiểm tra hành động sẽ dẫn đến điều kiện cuộc đua. Dù sao đi nữa, f những người ghét.
Michael

0

Bạn đang làm hai điều riêng biệt ở đây: đảm bảo rằng sự HashMaptồn tại và thêm mục mới vào đó.

Mã hiện tại đảm bảo chèn phần tử mới trước khi đăng ký bản đồ băm, nhưng điều đó không cần thiết, vì HashMapkhông quan tâm đến việc đặt hàng ở đây. Không có biến thể nào là an toàn, vì vậy bạn không mất gì cả.

Vì vậy, như @Heinzi đề xuất, bạn chỉ có thể chia hai bước này.

Những gì tôi cũng sẽ làm là offload việc tạo ra HashMapđể các allInvoicesAllClientsđối tượng, do đó getphương pháp không thể trở về null.

Điều này cũng làm giảm khả năng các cuộc đua giữa các luồng riêng biệt có thể nhận được nullcon trỏ từ getđó và sau đó quyết định putmột cuộc đua mới HashMapvới một mục duy nhất - lần thứ hai putcó thể sẽ loại bỏ Invoiceđối tượng đầu tiên, làm mất đối tượng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.