Apakah menggunakan java Map.containsKey () redundan saat menggunakan map.get ()

93

Saya telah bertanya-tanya selama beberapa waktu apakah diperbolehkan dalam praktik terbaik untuk menahan diri dari menggunakan containsKey()metode ini java.util.Mapdan sebaliknya melakukan pemeriksaan nol pada hasil dari get().

Alasan saya adalah bahwa tampaknya mubazir untuk melakukan pencarian nilai dua kali - pertama untuk yang containsKey()dan kemudian untuk get().

Di sisi lain, mungkin sebagian besar implementasi standar Mapcache pencarian terakhir atau yang sebaliknya dapat dilakukan oleh compiler dengan redundansi, dan agar kode dapat dibaca, lebih disukai untuk mempertahankan containsKey()bagian tersebut.

Saya sangat menghargai komentar Anda.

Erik Madsen
sumber

Jawaban:

112

Beberapa implementasi Peta diizinkan untuk memiliki nilai nol, misalnya HashMap, dalam hal ini jika get(key)dikembalikan nulltidak menjamin bahwa tidak ada entri di peta yang terkait dengan kunci ini.

Jadi jika Anda ingin tahu apakah peta berisi penggunaan kunci Map.containsKey. Jika Anda hanya membutuhkan nilai yang dipetakan ke penggunaan kunci Map.get(key). Jika peta ini mengizinkan nilai null, maka nilai kembali null tidak selalu menunjukkan bahwa peta tidak berisi pemetaan untuk kunci tersebut; Dalam kasus seperti Map.containsKeyitu tidak ada gunanya dan akan mempengaruhi kinerja. Selain itu, dalam kasus akses bersamaan ke peta (misalnya ConcurrentHashMap), setelah Anda mengujinya, Map.containsKey(key)ada kemungkinan bahwa entri tersebut akan dihapus oleh utas lain sebelum Anda memanggil Map.get(key).

Evgeniy Dorofeev
sumber
8
Meskipun nilainya disetel ke null, apakah Anda ingin memperlakukannya secara berbeda dengan kunci / nilai yang tidak disetel? Jika Anda tidak secara khusus perlu memperlakukannya secara berbeda, Anda dapat menggunakanget()
Peter Lawrey
1
Jika Anda Mapadalah private, kelas Anda mungkin bisa menjamin nulltidak pernah dimasukkan ke dalam peta. Dalam hal ini, Anda dapat menggunakan get()diikuti dengan pemeriksaan null, bukan containsKey(). Melakukannya bisa lebih jelas, dan mungkin sedikit lebih efisien, dalam beberapa kasus.
Raedwald
44

Menurut saya cukup standar untuk menulis:

Object value = map.get(key);
if (value != null) {
    //do something with value
}

dari pada

if (map.containsKey(key)) {
    Object value = map.get(key);
    //do something with value
}

Ini tidak kurang mudah dibaca dan sedikit lebih efisien jadi saya tidak melihat alasan untuk tidak melakukannya. Jelas jika peta Anda dapat berisi null, kedua opsi tersebut tidak memiliki semantik yang sama .

assylias
sumber
8

Seperti yang ditunjukkan oleh assylias, ini adalah pertanyaan semantik. Umumnya, Map.get (x) == null adalah yang Anda inginkan, tetapi ada kasus di mana penting untuk menggunakan containsKey.

Salah satu kasus tersebut adalah cache. Saya pernah menangani masalah kinerja di aplikasi web yang sering menanyakan database-nya mencari entitas yang tidak ada. Ketika saya mempelajari kode caching untuk komponen itu, saya menyadari itu menanyakan database jika cache.get (key) == null. Jika database mengembalikan null (entitas tidak ditemukan), kami akan menyimpan kunci itu ke cache -> pemetaan null.

Beralih ke containsKey memecahkan masalah karena pemetaan ke nilai null sebenarnya berarti sesuatu. Pemetaan kunci ke nol memiliki arti semantik yang berbeda dengan kunci yang tidak ada.

Brandon
sumber
Menarik. Mengapa Anda tidak menambahkan pemeriksaan null sebelum menyimpan nilai?
Saket
Itu tidak akan mengubah apapun. Intinya adalah bahwa pemetaan kunci untuk nol berarti "kami sudah melakukan ini. Itu cache. Nilai adalah nol". Vs. tidak mengandung kunci yang diberikan sama sekali, yang berarti "Tidak tahu, tidak di cache, kami mungkin perlu memeriksa DB."
Brandon
5
  • containsKeydiikuti oleh sebuah getredundan hanya jika kita tahu apriori bahwa nilai null tidak akan pernah diizinkan. Jika nilai null tidak valid, pemanggilan containsKeymemiliki penalti performa non-sepele dan hanya overhead seperti yang ditunjukkan pada tolok ukur di bawah ini.

  • OptionalIdiom Java 8 - Optional.ofNullable(map.get(key)).ifPresentatau Optional.ofNullable(map.get(key)).ifPresent- menimbulkan overhead yang tidak sepele dibandingkan dengan pemeriksaan nol vanilla saja.

  • A HashMapmenggunakan O(1)pencarian tabel konstan sedangkan a TreeMapmenggunakan O(log(n))pencarian. The containsKeydiikuti dengan getidiom jauh lebih lambat ketika dijalankan pada TreeMap.

Tolak ukur

Lihat https://github.com/vkarun/enum-reverse-lookup-table-jmh

// t1
static Type lookupTreeMapNotContainsKeyThrowGet(int t) {
  if (!lookupT.containsKey(t))
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return lookupT.get(t);
}
// t2
static Type lookupTreeMapGetThrowIfNull(int t) {
  Type type = lookupT.get(t);
  if (type == null)
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return type;
}
// t3
static Type lookupTreeMapGetOptionalOrElseThrow(int t) {
  return Optional.ofNullable(lookupT.get(t)).orElseThrow(() -> new 
      IllegalStateException("Unknown Multihash type: " + t));
}
// h1
static Type lookupHashMapNotContainsKeyThrowGet(int t) {
  if (!lookupH.containsKey(t))
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return lookupH.get(t);
}
// h2
static Type lookupHashMapGetThrowIfNull(int t) {
  Type type = lookupH.get(t);
  if (type == null)
    throw new IllegalStateException("Unknown Multihash type: " + t);
  return type;
}
// h3
static Type lookupHashMapGetOptionalOrElseThrow(int t) {
  return Optional.ofNullable(lookupH.get(t)).orElseThrow(() -> new 
    IllegalStateException("Unknown Multihash type: " + t));
}
Benchmark (iterations) (lookupApproach) Mode Cnt Score Unit Kesalahan

MultihashTypeLookupBenchmark.testLookup 1000 t1 rata-rata 9 33,438 ± 4,514 us / op
MultihashTypeLookupBenchmark.testLookup 1000 t2 rata-rata 9 26.986 ± 0.405 us / op
MultihashTypeLookupBenchmark.testLookup 1000 t3 rata-rata 9 39,259 ± 1,306 us / op
MultihashTypeLookupBenchmark.testLookup 1000 h1 rata-rata 9 18,954 ± 0,414 us / op
MultihashTypeLookupBenchmark.testLookup 1000 h2 rata-rata 9 15.486 ± 0.395 us / op
MultihashTypeLookupBenchmark.testLookup 1000 h3 rata-rata 9 16,780 ± 0,719 us / op

Referensi sumber TreeMap

https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/TreeMap.java

Referensi sumber HashMap

https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/HashMap.java

Venkat Karun Venugopalan
sumber
3

Kami dapat membuat jawaban @assylias lebih mudah dibaca dengan Java8 Opsional,

Optional.ofNullable(map.get(key)).ifPresent(value -> {
     //do something with value
};)
Raja
sumber
2

Di Java jika Anda memeriksa implementasinya

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

keduanya menggunakan getNode untuk mengambil pencocokan, tempat pekerjaan utama diselesaikan.

redundansi bersifat kontekstual, misalnya jika Anda memiliki kamus yang disimpan dalam peta hash. Ketika Anda ingin mendapatkan kembali arti sebuah kata

perbuatan...

if(dictionary.containsKey(word)) {
   return dictionary.get(word);
}

berlebihan.

tetapi jika Anda ingin memeriksa kata itu valid atau tidak berdasarkan kamus. perbuatan...

 return dictionary.get(word) != null;

lebih...

 return dictionary.containsKey(word);

berlebihan.

Jika Anda memeriksa implementasi HashSet , yang menggunakan HashMap secara internal, gunakan metode 'containsKey' dalam 'berisi'.

    public boolean contains(Object o) {
        return map.containsKey(o);
    }
asela38
sumber