HashMap untuk mengembalikan nilai default untuk kunci yang tidak ditemukan?

151

Apakah mungkin untuk HashMapmengembalikan nilai default untuk semua kunci yang tidak ditemukan di set?

Larry
sumber
Anda dapat memeriksa keberadaan kunci dan mengembalikan default. Atau perluas kelas dan modifikasi perilaku. atau bahkan Anda dapat menggunakan null - dan beri tanda centang di mana pun Anda ingin menggunakannya.
SudhirJ
2
Ini terkait / duplikat dari stackoverflow.com/questions/4833336/... beberapa opsi lain dibahas di sana.
Mark Butler
3
Lihatlah solusi Java 8 untuk getOrDefault() tautan
Trey Jonn

Jawaban:

136

[Memperbarui]

Seperti dicatat oleh jawaban dan komentator lain, pada Java 8 Anda cukup menelepon Map#getOrDefault(...).

[Asli]

Tidak ada implementasi Peta yang melakukan hal ini dengan tepat tetapi akan sepele untuk mengimplementasikannya sendiri dengan memperluas HashMap:

public class DefaultHashMap<K,V> extends HashMap<K,V> {
  protected V defaultValue;
  public DefaultHashMap(V defaultValue) {
    this.defaultValue = defaultValue;
  }
  @Override
  public V get(Object k) {
    return containsKey(k) ? super.get(k) : defaultValue;
  }
}
maerics
sumber
20
Hanya untuk menjadi tepat, Anda mungkin ingin menyesuaikan keadaan dari (v == null)ke (v == null && !this.containsKey(k))dalam kasus mereka sengaja menambahkan nullnilai. Saya tahu, ini hanya kasus sudut, tetapi penulis mungkin mengalami itu.
Adam Paynter
@maerics: Saya perhatikan Anda menggunakan !this.containsValue(null). Ini agak berbeda dari !this.containsKey(k). The containsValuesolusi akan gagal jika beberapa lainnya kunci telah secara eksplisit diberi nilai null. Misalnya: map = new HashMap(); map.put(k1, null); V v = map.get(k2);Dalam hal ini, vapakah masih akan null, benar?
Adam Paynter
21
Secara umum , saya pikir ini adalah ide yang buruk - saya akan mendorong perilaku default ke klien, atau delegasi yang tidak mengklaim sebagai Peta. Secara khusus, kurangnya keySet () atau entrySet () yang valid akan menyebabkan masalah dengan apa pun yang mengharapkan kontrak Peta dihormati. Dan kumpulan kunci valid tak terbatas yang mengandungKey () menyiratkan kemungkinan menyebabkan kinerja buruk yang sulit didiagnosis. Namun, tidak untuk mengatakan bahwa itu mungkin tidak melayani tujuan tertentu.
Ed Staub
Satu masalah dengan pendekatan ini adalah jika nilainya adalah objek yang rumit. Peta <String, List> #put tidak akan berfungsi seperti yang diharapkan.
Eyal
Tidak berfungsi di ConcurrentHashMap. Di sana, Anda harus memeriksa hasil get untuk null.
dveim
162

Di Java 8, gunakan Map.getOrDefault . Dibutuhkan kunci, dan nilai untuk kembali jika tidak ada kunci yang cocok ditemukan.

Spycho
sumber
14
getOrDefaultsangat bagus, tetapi membutuhkan definisi default setiap kali peta diakses. Menentukan nilai default sekali juga akan memiliki manfaat keterbacaan saat membuat peta nilai statis.
ach
3
Ini sepele untuk diterapkan sendiri. private static String get(Map map, String s) { return map.getOrDefault(s, "Error"); }
Jack Satriano
@JackSatriano Ya tetapi Anda harus membuat hard-code nilai default, atau memiliki variabel statis untuk itu.
Blrp
1
Lihat di bawah jawaban menggunakan computeIfAbsent, lebih baik ketika nilai default mahal atau harus berbeda setiap kali.
hectorpal
Meskipun lebih buruk untuk memori, dan hanya akan menghemat waktu komputasi jika nilai defaultnya mahal untuk dibangun / dihitung. Jika murah, Anda mungkin merasa kinerjanya lebih buruk, karena harus dimasukkan ke dalam peta, bukan hanya mengembalikan nilai default. Tentu saja pilihan lain.
Spycho
73

Gunakan Commed ' DefaultedMap jika Anda merasa tidak ingin menciptakan kembali roda, misalnya,

Map<String, String> map = new DefaultedMap<>("[NO ENTRY FOUND]");
String surname = map.get("Surname"); 
// surname == "[NO ENTRY FOUND]"

Anda juga dapat meneruskan di peta yang ada jika Anda tidak bertanggung jawab untuk membuat peta di tempat pertama.

Dave Newton
sumber
26
+1 meskipun terkadang lebih mudah untuk menemukan kembali roda daripada memperkenalkan dependensi besar untuk irisan kecil fungsi sederhana.
maerics
3
dan lucunya, banyak proyek yang saya kerjakan telah memiliki sesuatu seperti ini di classpath (baik Apache Commons atau Google Guava)
bartosz.r
@ bartosz.r, jelas tidak di ponsel
Pacerier
44

Java 8 memperkenalkan metode standar computeIfAbsent yang bagus untuk Mapantarmuka yang menyimpan nilai yang dikomputasi dengan malas sehingga tidak merusak kontrak peta:

Map<Key, Graph> map = new HashMap<>();
map.computeIfAbsent(aKey, key -> createExpensiveGraph(key));

Asal: http://blog.javabien.net/2014/02/20/loadingcache-in-java-8-without-guava/

Disclamer: Jawaban ini tidak cocok dengan apa yang ditanyakan OP tetapi mungkin berguna dalam beberapa kasus mencocokkan judul pertanyaan ketika nomor kunci terbatas dan caching nilai yang berbeda akan menguntungkan. Seharusnya tidak digunakan dalam kasus yang berlawanan dengan banyak kunci dan nilai default yang sama karena ini akan membuang-buang memori.

Vadzim
sumber
Bukan yang ditanyakan OP: dia tidak menginginkan efek samping pada peta. Selain itu, menyimpan nilai default untuk setiap kunci yang tidak ada adalah hilangnya ruang memori yang tidak berguna.
numéro6
@ numéro6, ya, ini tidak cocok dengan apa yang diminta OP tetapi beberapa orang Google masih menemukan jawaban sisi ini berguna. Seperti jawaban lain yang disebutkan, tidak mungkin untuk mencapai apa yang diminta OP tanpa memutus kontrak peta. Solusi lain yang tidak disebutkan di sini adalah dengan menggunakan abstraksi lain alih-alih Peta .
Vadzim
Adalah mungkin untuk mencapai apa yang diminta OP tanpa melanggar kontrak peta. Tidak ada solusi yang diperlukan, hanya menggunakan getOrDefault adalah cara yang benar (paling baru), computeIfAbsent adalah cara yang salah: Anda akan kehilangan waktu memanggil fungsi pemetaan dan memori dengan menyimpan hasilnya (baik untuk setiap kunci yang hilang). Saya tidak dapat melihat alasan bagus untuk melakukan itu alih-alih getOrDefault. Apa yang saya jelaskan adalah alasan tepat mengapa ada dua metode berbeda dalam kontrak Peta: ada dua kasus penggunaan yang berbeda yang tidak boleh bingung (saya harus memperbaiki beberapa di tempat kerja). Jawaban ini menyebarkan kebingungan.
numéro6
14

Tidak bisakah Anda membuat metode statis yang melakukan ini?

private static <K, V> V getOrDefault(Map<K,V> map, K key, V defaultValue) {
    return map.containsKey(key) ? map.get(key) : defaultValue;
}
Shervin Asgari
sumber
di mana menyimpan statis?
Pacerier
10

Anda cukup membuat kelas baru yang mewarisi HashMap dan menambahkan metode getDefault. Berikut ini contoh kode:

public class DefaultHashMap<K,V> extends HashMap<K,V> {
    public V getDefault(K key, V defaultValue) {
        if (containsKey(key)) {
            return get(key);
        }

        return defaultValue;
    }
}

Saya pikir Anda tidak boleh mengesampingkan metode get (K key) dalam implementasi Anda, karena alasan yang ditentukan oleh Ed Staub dalam komentarnya dan karena Anda akan memutus kontrak antarmuka Peta (ini berpotensi menyebabkan beberapa sulit ditemukan) bug).

Ivan Mushketyk
sumber
4
Anda punya titik untuk tidak mengganti getmetode. Di sisi lain - solusi Anda tidak memungkinkan menggunakan kelas melalui antarmuka, yang mungkin sering terjadi.
Krzysztof Jabłoński
5

Menggunakan:

myHashMap.getOrDefault(key, defaultValue);
Diego Alejandro
sumber
3

Ini dilakukan secara default. Ia kembali null.

mrkhrts
sumber
@Larry, Agak konyol bagi saya untuk subkelas HashMaphanya untuk fungsi ini ketika nullbaik-baik saja.
mrkhrts
15
Namun, tidak masalah jika Anda menggunakan NullObjectpola, atau tidak ingin menyebarkan cek-nol di seluruh kode Anda - keinginan yang sepenuhnya saya mengerti.
Dave Newton
3

Di java 8+

Map.getOrDefault(Object key,V defaultValue)
Eduardo
sumber
1

Saya menemukan LazyMap cukup membantu.

Ketika metode get (Object) dipanggil dengan kunci yang tidak ada di peta, pabrik digunakan untuk membuat objek. Objek yang dibuat akan ditambahkan ke peta menggunakan kunci yang diminta.

Ini memungkinkan Anda melakukan sesuatu seperti ini:

    Map<String, AtomicInteger> map = LazyMap.lazyMap(new HashMap<>(), ()->new AtomicInteger(0));
    map.get(notExistingKey).incrementAndGet();

Panggilan untuk getmenciptakan nilai default untuk kunci yang diberikan. Anda menentukan cara membuat nilai default dengan argumen pabrik untuk LazyMap.lazyMap(map, factory). Pada contoh di atas, peta diinisialisasi ke yang baru AtomicIntegerdengan nilai 0.

Benedikt Köppel
sumber
Ini memiliki keunggulan dibandingkan jawaban yang diterima karena nilai default dibuat oleh pabrik. Bagaimana jika nilai default saya List<String>- menggunakan jawaban yang diterima saya akan mengambil risiko menggunakan daftar yang sama untuk setiap kunci baru, daripada (katakanlah) new ArrayList<String>()dari pabrik.
0
/**
 * Extension of TreeMap to provide default value getter/creator.
 * 
 * NOTE: This class performs no null key or value checking.
 * 
 * @author N David Brown
 *
 * @param <K>   Key type
 * @param <V>   Value type
 */
public abstract class Hash<K, V> extends TreeMap<K, V> {

    private static final long serialVersionUID = 1905150272531272505L;

    /**
     * Same as {@link #get(Object)} but first stores result of
     * {@link #create(Object)} under given key if key doesn't exist.
     * 
     * @param k
     * @return
     */
    public V getOrCreate(final K k) {
        V v = get(k);
        if (v == null) {
            v = create(k);
            put(k, v);
        }
        return v;
    }

    /**
     * Same as {@link #get(Object)} but returns specified default value
     * if key doesn't exist. Note that default value isn't automatically
     * stored under the given key.
     * 
     * @param k
     * @param _default
     * @return
     */
    public V getDefault(final K k, final V _default) {
        V v = get(k);
        return v == null ? _default : v;
    }

    /**
     * Creates a default value for the specified key.
     * 
     * @param k
     * @return
     */
    abstract protected V create(final K k);
}

Contoh penggunaan:

protected class HashList extends Hash<String, ArrayList<String>> {
    private static final long serialVersionUID = 6658900478219817746L;

    @Override
        public ArrayList<Short> create(Short key) {
            return new ArrayList<Short>();
        }
}

final HashList haystack = new HashList();
final String needle = "hide and";
haystack.getOrCreate(needle).add("seek")
System.out.println(haystack.get(needle).get(0));
KomodoDave
sumber
0

Saya perlu membaca hasil yang dikembalikan dari server di JSON di mana saya tidak bisa menjamin bidang akan ada. Saya menggunakan kelas org.json.simple.JSONObject yang berasal dari HashMap. Berikut adalah beberapa fungsi pembantu yang saya gunakan:

public static String getString( final JSONObject response, 
                                final String key ) 
{ return getString( response, key, "" ); }  
public static String getString( final JSONObject response, 
                                final String key, final String defVal ) 
{ return response.containsKey( key ) ? (String)response.get( key ) : defVal; }

public static long getLong( final JSONObject response, 
                            final String key ) 
{ return getLong( response, key, 0 ); } 
public static long getLong( final JSONObject response, 
                            final String key, final long defVal ) 
{ return response.containsKey( key ) ? (long)response.get( key ) : defVal; }

public static float getFloat( final JSONObject response, 
                              final String key ) 
{ return getFloat( response, key, 0.0f ); } 
public static float getFloat( final JSONObject response, 
                              final String key, final float defVal ) 
{ return response.containsKey( key ) ? (float)response.get( key ) : defVal; }

public static List<JSONObject> getList( final JSONObject response, 
                                        final String key ) 
{ return getList( response, key, new ArrayList<JSONObject>() ); }   
public static List<JSONObject> getList( final JSONObject response, 
                                        final String key, final List<JSONObject> defVal ) { 
    try { return response.containsKey( key ) ? (List<JSONObject>) response.get( key ) : defVal; }
    catch( ClassCastException e ) { return defVal; }
}   
Terima kasih
sumber
-1

Dalam proyek Jawa / Kotlin campuran juga mempertimbangkan Map.withDefault Kotlin .

Vadzim
sumber