Как я могу избежать повторения кода, инициализирующего hashmap hashmap? - PullRequest
27 голосов
/ 19 марта 2020

У каждого клиента есть идентификатор, и многие счета-фактуры с датами хранятся в виде 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); }}
);

Но если get не равен null, я все же хочу, чтобы put (дата, счет-фактура) выполнялся, и добавление данных в "allInvoicesAllClients" по-прежнему необходимо. Так что это не очень помогает.

Ответы [ 5 ]

39 голосов
/ 19 марта 2020

Это отличный вариант использования для Map#computeIfAbsent. Ваш фрагмент по сути эквивалентен:

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

Если id не указан в качестве ключа в allInvoicesAllClients, то он создаст отображение из id в новый HashMap и вернет новый HashMap. Если id присутствует в качестве ключа, он вернет существующий HashMap.

16 голосов
/ 20 марта 2020

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
11 голосов
/ 19 марта 2020

Вы почти никогда не должны использовать инициализацию карты с «двойной скобкой».

{{  put(date, invoice); }}

В этом случае вам следует использовать computeIfAbsent

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

Если для этот идентификатор, вы вставите один. Результатом будет существующая или вычисленная карта. Затем вы можете put элементов на этой карте, гарантируя, что она не будет нулевой.

5 голосов
/ 19 марта 2020

Это длиннее других ответов, но imho гораздо более читабельно:

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

allInvoicesAllClients.get(id).put(date, invoice);
0 голосов
/ 20 марта 2020

Здесь вы делаете две разные вещи: убедитесь, что HashMap существует, и добавляете в него новую запись.

Существующий код гарантирует, что сначала нужно вставить новый элемент, прежде чем зарегистрировать ha * 1022. * карта, но это не обязательно, потому что HashMap не заботится о порядке здесь. Ни один из этих вариантов не является поточно-ориентированным, поэтому вы ничего не теряете.

Итак, как и предложил @Heinzi, вы можете просто разделить эти два шага.

Я бы также разгрузил создание HashMap к объекту allInvoicesAllClients, поэтому метод get не может вернуть null.

Это также уменьшает возможность для гонок между отдельными потоками, которые могут одновременно получить указатели null от get и затем решите put новый HashMap с одной записью - второй put, вероятно, отбросит первый, потеряв объект Invoice.

...