Як я можу уникнути повторення коду ініціалізації хешмапу хешмапу?


27

У кожного клієнта є ідентифікатор, і багато рахунків-фактур із датами, що зберігаються як Hashmap клієнтів за id, хеш-мапи рахунків-фактур за датою:

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

Але якщо get не є нульовим, я все одно хочу виконати put (дата, рахунок-фактура), а також додавання даних до "allInvoicesAllClients" все ще потрібно. Тож, здається, це не дуже допомагає.


Якщо ви не можете гарантувати унікальність ключа, найкраще зробити, щоб вторинна карта мала значення List <Invoice>, а не лише рахунок-фактура.
Райан

Відповіді:


39

Це відмінний приклад для використання Map#computeIfAbsent. Ваш фрагмент по суті еквівалентний:

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

Якщо idвін не є ключовим allInvoicesAllClients, воно створить зіставлення з idнового HashMapта поверне нове HashMap. Якщо idвін присутній як ключ, то він поверне існуючий HashMap.


1
computeIfAbsent, робить get (id) (або поставлений наступний get (id)), тому наступне ставлення робиться для виправлення пункту put (дата), правильної відповіді.
Ернан Еше

allInvoicesAllClients.computeIfAbsent(id, key -> Map.of(date, invoice))
Олександр - Відновіть Моніку

1
@ Alexander-ReinstateMonica Map.ofстворює незмінне Map, чого я не впевнений, що ОП хоче.
Яків Г.

Чи був би цей код менш ефективним, ніж те, що було в ОР спочатку? Запитуючи це, тому що я не знайомий з тим, як Java обробляє функції лямбда.
Зеконг Ху

16

computeIfAbsentє прекрасним рішенням для даного конкретного випадку. Загалом, я хотів би зазначити наступне, оскільки ніхто ще не згадував про це:

"Зовнішня" хешмап просто зберігає посилання на "внутрішню" хешмап, тому ви можете просто змінити порядок операцій, щоб уникнути дублювання коду:

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()методом!
Ніл Бартлетт

1
Я все ще використовую цей підхід сьогодні мовами, де реалізація карти не забезпечує єдиного методу get-or-put-and-return-if-absent. Що це все-таки може бути найкращим рішенням на інших мовах, можливо, варто згадати, навіть якщо це питання спеціально позначено для Java 8.
Квінн Мортімер

11

Ви майже не повинні використовувати ініціалізацію карт "подвійної дужки".

{{  put(date, invoice); }}

У цьому випадку вам слід скористатися computeIfAbsent

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

Якщо для цього ідентифікатора немає картки, ви вставите її. Результатом буде наявна або обчислена карта. Потім ви можете putрозміщувати елементи на цій карті з гарантією, що вона не буде нульовою.


1
Я не знаю, хто знищив, а не я, можливо, один код рядка плутає всі InvoicesAllClients, оскільки ви використовуєте id замість дати, я редагую його
Hernán Eche

1
@ HernánEche Ах. Моя помилка. Дякую. Так, також idзроблено. computeIfAbsentЯкщо ви хочете, ви можете вважати це умовно. І це також повертає цінність
Майкл

" Ви майже не повинні використовувати ініціалізацію карт" подвійної дужки ". " Чому? (Я не сумніваюся, що ти маєш рацію; я прошу від щирої цікавості.)
Хайнці,

1
@Heinzi Тому що це створює анонімний внутрішній клас. Тут міститься посилання на клас, який його оголосив, що, якщо ви відкриєте карту (наприклад, за допомогою getter), перешкоджатиме збору класу збирати сміття. Крім того, я вважаю, що це може бентежити людей, які менш знайомі з Java; Блоки ініціалізаторів майже ніколи не використовуються, і написання цього тексту робить його схожим на {{ }}особливе значення, якого він не має.
Майкл

1
@Michael: Має сенс, дякую. Я повністю забув, що анонімні внутрішні класи завжди нестатичні (навіть якщо їх не потрібно).
Хайнзі

5

Це довше, ніж інші відповіді, але набагато легше читати:

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

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

3
Це може працювати для HashMap, але загальний підхід не є оптимальним. Якщо це були ConcurrentHashMaps, ці операції не є атомними. У такому випадку перевірка-дія-дія призведе до умов перегонів. У будь-якому разі, з ненависниками.
Майкл

0

Тут ви робите дві окремі речі: переконайтесь у HashMapіснуванні та додайте до нього новий запис.

Існуючий код обов'язково спочатку вставить новий елемент перед реєстрацією хеш-карти, але це не потрібно, оскільки тут HashMapне важливо впорядковувати. Жоден варіант не є безпечним для ниток, тому ви нічого не втрачаєте.

Отже, як запропонував @Heinzi, ви можете просто розділити ці два кроки.

Те , що я хотів би також зробити вивантаження створення HashMapна allInvoicesAllClientsоб'єкт, тому getметод не може повернутисяnull .

Це також зменшує можливість перегонів між окремими потоками, які можуть обидва отримати nullвказівники, getа потім вирішити putнове HashMapз одним записом - другий put, мабуть, відкине перший, втративши Invoiceоб'єкт.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.