ハッシュマップのハッシュマップを初期化するコードの繰り返しを回避するにはどうすればよいですか?


27

すべてのクライアントにはIDと多くの請求書があり、日付ごとに請求書のハッシュマップの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がnullでない場合でも、put(date、invoice)を実行したいので、「allInvoicesAllClients」にデータを追加する必要があります。だからそれはあまり役に立たないようです。


キーの一意性を保証できない場合は、セカンダリマップにInvoiceだけでなく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)(またはputの後にget(id)が続く)を実行するため、次のputは、アイテムput(date)を修正するために行われ、正しい答えです。
エルナンEche

allInvoicesAllClients.computeIfAbsent(id, key -> Map.of(date, invoice))
アレクサンダー-モニカを

1
@ Alexander-ReinstateMonica Map.ofが変更Map不可のを作成しますが、OPが望んでいるかどうかはわかりません。
Jacob G.

このコードは、OPが元々持っていたものよりも効率が悪いでしょうか?Javaがラムダ関数を処理する方法に慣れていないので、これを尋ねます。
Zecong Hu

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()メソッドとともに登場する前の何十年にもわたってこれを実現した方法です。
Neil Bartlett

1
現在も、この実装は、マップ実装が単一のget-or-put-and-return-if-absentメソッドを提供しない言語で使用されています。これはまだ他の言語で最善の解決策は、この問題を具体的にJavaの8のためにタグ付けされていても言及する価値があることも可能であること
クインモーティマー

11

「二重ブレース」マップの初期化はほとんど使用しないでください。

{{  put(date, invoice); }}

この場合、使用する必要があります computeIfAbsent

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

このIDのマップがない場合は、マップを挿入します。結果は、既存のマップまたは計算されたマップになります。次にput、そのマップ内のアイテムをnullにならないように保証できます。


1
私は、ない私は、あなたの代わりに日付のIDを使用しているため、おそらく単一のラインコードを混乱さallInvoicesAllClientsが、入れて、私はそれを編集しますdownvoteか分からない
エルナンEche

1
@HernánEcheああ。私の間違い。ありがとう。はい、プットフォーidも行われます。computeIfAbsent必要に応じて、条件付きプットと考えることができます。また、値も返します
Michael

" ほとんどの場合、"ダブルブレース "マップの初期化は使用しないでください。 "なぜですか?(私はあなたが正しいことを疑いません;私は本物の好奇心から求めています。)
Heinzi

1
@Heinzi匿名の内部クラスを作成するため。これは、それを宣言したクラスへの参照を保持します。これにより、マップを公開すると(たとえば、getterを介して)、外側のクラスがガベージコレクションされるのを防ぎます。また、Javaにあまり詳しくない人にとっては混乱を招く可能性があることもわかりました。初期化子ブロックはほとんど使用されず、このように書くと{{ }}特別な意味を持つように見えますが、そうではありません。
マイケル

1
@マイケル:理にかなっている、ありがとう。匿名の内部クラスは常に(静的である必要がない場合でも)非静的であることを完全に忘れていました。
ハインツィ

5

これは他の回答よりも長いですが、imhoははるかに読みやすくなっています。

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

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

3
これはHashMapでは機能しますが、一般的なアプローチは最適ではありません。これらがConcurrentHashMapの場合、これらの操作はアトミックではありません。そのような場合、チェックザアクトは競合状態につながります。とにかく嫌いな人は賛成です。
マイケル

0

ここでは2つの別々のことを行っています。 HashMap存在するそれに新しいエントリを追加します。

既存のコードでは、ハッシュマップを登録する前に必ず新しい要素を挿入する必要がありますが、これは必要ありません HashMapここでは順序を気にしないはません。どちらのバリアントもスレッドセーフではないため、何も失うことはありません。

したがって、@ Heinziが提案したように、これら2つのステップを分割することができます。

私はまたどうなることの創造オフロードでHashMapallInvoicesAllClientsオブジェクトなので、getこの方法は返すことはできませんnull

これにより、nullポインタを取得し、単一のエントリで新しいスレッドにget決定する可能性のある別々のスレッド間の競合の可能性も減少します。putHashMapputInvoice

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.