Mengapa ConcurrentHashMap mencegah kunci dan nilai null?

141

JavaDoc dari ConcurrentHashMapmengatakan ini:

Suka Hashtabletapi tidak seperti HashMap, kelas ini tidak memungkinkan nulluntuk digunakan sebagai kunci atau nilai.

Pertanyaan saya: Kenapa?

Pertanyaan 2: Mengapa tidak Hashtablemengizinkan nol?

Saya telah menggunakan banyak HashMaps untuk menyimpan data. Tetapi ketika berganti ke ConcurrentHashMapsaya beberapa kali mengalami masalah karena NullPointerExceptions.

Marcel
sumber
1
Saya pikir ini adalah inkonsistensi yang sangat menjengkelkan. EnumMap juga tidak mengizinkan null. Jelas tidak ada batasan teknis yang melarang kunci nol. untuk Peta <K, V>, cukup bidang bertipe V akan memberikan dukungan untuk kunci null (mungkin bidang boolean lain jika Anda ingin membedakan antara nilai nol dan tidak ada nilai).
RAY
6
Pertanyaan yang lebih baik adalah "mengapa HashMap mengizinkan kunci nol dan nilai nol?". Atau mungkin, "mengapa Java mengizinkan null untuk menghuni semua tipe?", Atau bahkan "mengapa Java memiliki nulls sama sekali?".
Jed Wesley-Smith

Jawaban:

220

Dari penulis ConcurrentHashMapdirinya sendiri (Doug Lea) :

Alasan utama bahwa nulls tidak diizinkan di ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) adalah bahwa ambiguitas yang mungkin hanya bisa ditoleransi di peta non-konkuren tidak dapat diakomodasi. Yang utama adalah bahwa jika map.get(key)kembali null, Anda tidak dapat mendeteksi apakah kunci secara eksplisit memetakan ke nullvs kunci tidak dipetakan. Dalam peta non-konkuren, Anda dapat memeriksanya melalui map.contains(key), tetapi pada peta konkuren, peta mungkin telah berubah di antara panggilan.

Bruno
sumber
7
Terima kasih, tetapi bagaimana dengan null sebagai kuncinya?
AmitW
2
mengapa tidak menggunakan Optionalnilai s sebagai internal
benez
2
@ Benez Optionaladalah fitur Java 8, yang saat itu tidak tersedia (Java 5). Anda bisa menggunakan Optionalsekarang, memang.
Bruno
@AmitW, saya pikir ans yaitu sama, ambiguitas. Misalnya anggap satu utas membuat kunci sebagai nol dan menyimpan nilai terhadapnya. Kemudian utas lainnya mengubah kunci lain menjadi nol. Ketika utas kedua mencoba menambahkan nilai baru, itu akan diganti. Jika utas kedua mencoba untuk mendapatkan nilai, itu akan mendapatkan nilai untuk kunci yang berbeda, yang dimodifikasi oleh yang pertama. Situasi seperti itu harus dihindari.
Dexter
44

Saya percaya itu adalah, setidaknya sebagian, untuk memungkinkan Anda menggabungkan containsKeydan getmenjadi satu panggilan. Jika peta dapat menyimpan nol, tidak ada cara untuk mengetahui apakah getmengembalikan nol karena tidak ada kunci untuk nilai itu, atau hanya karena nilainya nol.

Mengapa itu menjadi masalah? Karena tidak ada cara aman untuk melakukannya sendiri. Ambil kode berikut:

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

Karena mini adalah peta konkuren, kunci k dapat dihapus di antaracontainsKey dan getpanggilan, menyebabkan potongan ini mengembalikan nol yang tidak pernah ada dalam tabel, daripada yang diinginkan KeyNotPresentException.

Biasanya Anda akan menyelesaikannya dengan menyinkronkan, tetapi dengan peta bersamaan yang tentu saja tidak akan berfungsi. Karenanya tanda tangan untuk getharus berubah, dan satu-satunya cara untuk melakukan itu dengan cara yang kompatibel-mundur adalah mencegah pengguna memasukkan nilai nol di tempat pertama, dan terus menggunakannya sebagai pengganti untuk "kunci tidak ditemukan".

Alice Purcell
sumber
Anda bisa melakukannya map.getOrDefault(key, NULL_MARKER). Jika itu null, nilainya null. Jika kembali NULL_MARKER, nilainya tidak ada.
Oliv
@Oliv Hanya pada Java 8. Selain itu, mungkin tidak ada penanda nol yang masuk akal untuk jenis itu.
Alice Purcell
@AlicePurcell, "tetapi dengan peta konkuren yang tentu saja tidak akan berfungsi" - mengapa, saya dapat menyinkronkan pada versi bersamaan juga - jadi bertanya-tanya mengapa itu tidak akan berfungsi. dapatkah Anda menjelaskan hal ini?
samshers
@samshers Tidak ada operasi pada peta bersamaan yang disinkronkan, jadi Anda harus menyinkronkan semua panggilan secara eksternal, di mana Anda tidak hanya kehilangan semua manfaat kinerja dari memiliki peta yang dilakukan secara bersamaan, Anda juga telah meninggalkan perangkap untuk pengelola masa depan yang secara alami berharap untuk dapat mengakses peta konkuren secara aman tanpa sinkronisasi.
Alice Purcell
@AlicePurcell, bagus. Meskipun secara teknis mungkin, itu pasti akan menjadi mimpi buruk pemeliharaan dan itu tidak akan diharapkan oleh pengguna kemudian bahwa mereka harus melakukan sinkronisasi pada versi bersamaan.
samshers
4

Josh Bloch dirancang HashMap; Doug Lea dirancang ConcurrentHashMap. Saya harap itu tidak memfitnah. Sebenarnya saya pikir masalahnya adalah bahwa nulls sering membutuhkan pembungkus sehingga null yang sebenarnya dapat berdiri untuk diinisialisasi. Jika kode klien membutuhkan nol maka dapat membayar biaya pembatalan nol itu sendiri.

Tom Hawtin - tackline
sumber
2

Anda tidak dapat menyinkronkan pada nol.

Sunting: Ini tidak persis mengapa dalam hal ini. Saya awalnya berpikir ada sesuatu yang mewah terjadi dengan mengunci hal-hal terhadap pembaruan bersamaan atau menggunakan monitor Obyek untuk mendeteksi jika ada sesuatu yang dimodifikasi, tetapi setelah memeriksa kode sumber tampaknya saya salah - mereka mengunci menggunakan "segmen" berdasarkan pada bitmask hash.

Dalam hal itu, saya curiga mereka melakukannya untuk menyalin Hashtable, dan saya curiga Hashtable melakukannya karena di dunia basis data relasional, null! = Null, jadi menggunakan null sebagai kunci tidak ada artinya.

Paul Tomblin
sumber
Hah? Tidak ada sinkronisasi yang dilakukan pada kunci dan nilai-nilai Peta. Itu tidak masuk akal.
Tobias Müller
Ada beberapa jenis penguncian yang dilakukan. Itulah yang membuatnya "Bersamaan". Untuk melakukan itu, diperlukan Objek untuk bertahan.
Paul Tomblin
2
Mengapa tidak ada Obyek khusus secara internal yang dapat digunakan untuk menyinkronkan nilai nol? misalnya "objek pribadi NULL = Objek baru ();". Saya pikir saya pernah melihat ini sebelumnya ...
Marcel
Apa jenis penguncian yang Anda maksud?
Tobias Müller
Sebenarnya, sekarang saya melihat kode sumber gee.cs.oswosatedu/dl/classes/EDU/oswego/cs/dl/util/concurrent/... Saya memiliki keraguan serius tentang hal itu. Tampaknya menggunakan penguncian segmen, tidak mengunci pada item individual.
Paul Tomblin
0

ConcurrentHashMap aman digunakan. Saya percaya bahwa tidak mengizinkan kunci dan nilai null adalah bagian dari memastikan bahwa itu adalah thread-safe.

Kevin Crowell
sumber
0

Saya kira cuplikan dari dokumentasi API berikut ini memberikan petunjuk yang baik: "Kelas ini sepenuhnya dapat dioperasikan dengan Hashtable dalam program yang mengandalkan keamanan utangnya tetapi tidak pada detail sinkronisasi."

Mereka mungkin hanya ingin membuat ConcurrentHashMapsepenuhnya kompatibel / dipertukarkan ke Hashtable. Dan karena Hashtabletidak memungkinkan kunci dan nilai null ..

Tobias Müller
sumber
2
Dan mengapa Hashtable tidak mendukung null?
Marcel
Dari melihat kodenya, saya tidak melihat alasan yang jelas mengapa Hashtable tidak mengizinkan nilai nol. Mungkin itu hanya keputusan API dari belakang ketika kelas dibuat ?! HashMap memiliki beberapa penanganan khusus untuk kasus null secara internal yang tidak dimiliki Hashtable. (Selalu melempar NullPointerException.)
Tobias Müller
-2

Saya tidak berpikir menolak nilai nol adalah pilihan yang benar. Dalam banyak kasus, kami ingin memasukkan kunci dengan nilai nol ke dalam peta saat ini. Namun, dengan menggunakan ConcurrentHashMap, kami tidak dapat melakukannya. Saya menyarankan agar versi JDK yang akan datang dapat mendukungnya.

Yinhaomin
sumber
1
Pernahkah Anda berpikir untuk bersaing untuk Javachampion?
BlackBishop
Gunakan Opsional jika Anda ingin perilaku seperti nol di kunci Anda.
Alice Purcell