ConcurrentHashMapがnullのキーと値を防止するのはなぜですか?


141

のJavaDoc ConcurrentHashMapはこれを言っています:

Hashtableは異なりHashMap、とは異なり、このクラスはキーまたは値として使用できませnull

私の質問:なぜですか?

2番目の質問:Hashtablenullを許可しないのはなぜですか?

私はデータを格納するために多くのHashMapを使用しました。しかし、変更するConcurrentHashMapとNullPointerExceptionsが原因で何度か問題が発生しました。


1
それは非常に厄介な矛盾だと思います。EnumMapもnullを許可しません。nullキーを許可しない技術的な制限は明らかにありません。Map <K、V>の場合、Vタイプのフィールドがnullキーのサポートを提供します(null値と値なしを区別したい場合は、おそらく別のブールフィールド)。
RAY

6
より良い質問は、「HashMapがnullキーとnull値を許可するのはなぜですか?」です。あるいは、「なぜJavaはnullがすべての型に存在することを許可するのですか?」、または「Javaがnullをまったく持っているのはなぜですか?」
Jed Wesley-Smith、

回答:


220

作者ConcurrentHashMap(ダグ・リー)の作者から

ConcurrentMaps(ConcurrentHashMaps、ConcurrentSkipListMaps)でnullが許可されない主な理由は、非並行マップではほとんど許容できないあいまい性に対応できないためです。主なものは、をmap.get(key)返す場合null、キーが明示的にマッピングされているかnull、キーがマッピングされていないかを検出できないことです。非並行マップでは、を使用してこれを確認できますが、並行マップでは、 map.contains(key)呼び出し間でマップが変更されている可能性があります。


7
ありがとうございますが、nullをキーとして持つのはどうですか?
AmitW '25

2
Optional内部で値としてsを使用しない理由
benez

2
@benez OptionalはJava 8の機能であり、当時は利用できませんでした(Java 5)。Optional確かに、今はsを使用できます。
Bruno

@ AmitW、ansは同じ、つまりあいまいさだと思います。たとえば、1つのスレッドがキーをnullとして作成し、それに対して値を格納するとします。次に、別のスレッドが別のキーをnullに変更しました。2番目のスレッドが新しい値を追加しようとすると、置き換えられます。2番目のスレッドが値を取得しようとすると、別のキー(最初のキーで変更されたキー)の値が取得されます。このような状況は回避する必要があります。
デクスター

44

私は、少なくとも部分的に、1つの通話にまとめて1つの通話にできるようにすることだcontainsKeyと思いますget。マップがnullを保持できる場合、getその値のキーがないため、または単に値がnullだったためにnullが返されているかどうかを判断する方法はありません。

なぜそれが問題なのですか?自分でそれを行う安全な方法がないからです。次のコードを見てください。

if (m.containsKey(k)) {
   return m.get(k);
} else {
   throw new KeyNotPresentException();
}

以来m同時マップで、キーkが間に削除されてもよいcontainsKeyし、getなく、所望よりも、テーブルではなかったヌルを戻すには、このスニペットを引き起こし、呼KeyNotPresentException

通常は同期することで解決しますが、並行マップではもちろん機能しません。したがって、の署名はget変更する必要があり、下位互換性のある方法でこれを行う唯一の方法は、ユーザーが最初にnull値を挿入しないようにし、それを「キーが見つかりません」のプレースホルダーとして引き続き使用することでした。


できますmap.getOrDefault(key, NULL_MARKER)。の場合null、値はでしたnull。を返すNULL_MARKER場合、値は存在しませんでした。
Oliv 2018

@Oliv Java 8以降のみ。また、その型には適切なnullマーカーがない場合があります。
アリスパーセル

@AlicePurcell、「しかし、もちろん並行マップでは機能しません」-なぜ、並行バージョンでも同じように同期できるので、なぜ機能しないのか疑問に思います。これについて詳しく説明してもらえますか?
samshers

@samshers同時マップでの操作は同期しないため、すべての呼び出しを外部で同期する必要があります。この時点で、同時マップを使用することによるパフォーマンス上の利点がすべて失われただけでなく、将来のメンテナにトラップを残しました。当然、同期せずに並行マップに安全にアクセスできることが期待されます。
アリスパーセル

@AlicePurcell、素晴らしい。技術的には可能ですが、それは間違いなくメンテナンスの悪夢になるでしょう。それ以降のユーザーは、同時バージョンで同期する必要があるとは予想していません。
samshers

4

Josh Blochが設計しましたHashMap。ダグ・リーが設計しましたConcurrentHashMap。それが名誉なことではないことを願っています。実際、私は、実際のnullが初期化されていないことを表すことができるように、nullはしばしばラッピングを必要とするという問題があると思います。クライアントコードがnullを必要とする場合、null自体をラップする(確かに小さい)コストを支払うことができます。


2

nullでは同期できません。

編集:これは、この場合の正確な理由ではありません。最初は、同時更新に対して何かをロックしたり、オブジェクトモニターを使用して何かが変更されたかどうかを検出したりするのはおかしいと思っていましたが、ソースコードを調べると間違っていたようです-に基づいた「セグメント」を使用してロックしていますハッシュのビットマスク。

その場合、私は彼らがHashtableをコピーするためにそれをしたのではないかと思います。リレーショナルデータベースの世界ではnull!= nullなので、nullをキーとして使用しても意味がないためです。


えっ?マップのキーと値に対して同期は行われません。それは意味がありません。
トビアス・ミュラー

他の種類のロックが行われます。それが「並行」にする理由です。これを行うには、オブジェクトを待機する必要があります。
ポールトンブリン

2
null値の同期に使用できる特別なオブジェクトが内部にないのはなぜですか?例:「プライベートオブジェクトNULL = new Object();」。私はこれを以前に見たことがあると思います...
マルセル

他にどのような種類のロックを意味しますか?
TobiasMüller

実際に、ソースコードgee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/を確認したところ、私はそれについて深刻な疑問を抱いています。個々のアイテムではなく、セグメントロックを使用しているようです。
ポールトンブリン

0

ConcurrentHashMapはスレッドセーフです。nullキーと値を許可しないことは、スレッドセーフであることを確認するための一部だと思います。


0

APIドキュメントの次のスニペットが良いヒントになると思います:「このクラスは、スレッドの安全性に依存しているが同期の詳細に依存していないプログラムでは、Hashtableと完全に相互運用できます。」

彼らはおそらく、とConcurrentHashMap完全に互換性/交換可能にしたかっただけでしょうHashtable。そしてHashtablenullキーと値を許可しないので。


2
そして、なぜHashtableはnullをサポートしないのですか?
マルセル

そのコードを見ると、Hashtableがnull値を許可しない明白な理由はわかりません。たぶん、クラスが作成されたときのAPIの決定だったのでしょうか。HashMapには、Hashtableが行わないnullケースに対する内部処理がいくつかあります。(それは、常にNullPointerExceptionがスローされます。)
トビアス・ミュラー

-2

null値を許可しないことが正しいオプションだとは思いません。多くの場合、null値のキーをコンカレントマップに挿入する必要があります。ただし、ConcurrentHashMapを使用すると、それはできません。今後のバージョンのJDKでサポートできることをお勧めします。


1
Javachampionの競争について考えましたか?
BlackBishop

キーでnullのような動作が必要な場合は、Optionalを使用します。
アリスパーセル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.