Bulunmayan anahtarlar için varsayılan değer döndürmek üzere HashMap?


151

Kümede bulunmayan HashMaptüm anahtarlar için varsayılan değer döndürmek mümkün müdür ?


Anahtar varlığını kontrol edebilir ve varsayılana dönebilirsiniz. Ya da sınıfı genişletip davranışı değiştirin. hatta null kullanabilirsiniz - ve kullanmak istediğiniz yere biraz kontrol koyun.
SudhirJ

2
Bu, stackoverflow.com/questions/4833336/… ile ilişkili / yinelenmiştir orada diğer bazı seçenekler ele alınmıştır.
Mark Butler

3
Harita API'sı getOrDefault() bağlantısı
Trey Jonn

Yanıtlar:


136

[Güncelleme]

Diğer cevaplar ve yorumcular tarafından belirtildiği gibi, Java 8'den itibaren arayabilirsiniz Map#getOrDefault(...).

[Orijinal]

Bunu tam olarak yapan bir Harita uygulaması yoktur, ancak HashMap'i genişleterek kendi uygulamanızı uygulamak önemsiz olacaktır:

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;
  }
}

20
Kesin olmak gerekirse, bilerek bir değer eklemeleri durumunda durumu ' (v == null)den (v == null && !this.containsKey(k))' nulldeğerine ayarlamak isteyebilirsiniz . Biliyorum, bu sadece bir köşe davası, ancak yazar bunun içine girebilir.
Adam Paynter

@maerics: Kullandığını fark ettim !this.containsValue(null). Bu çok farklı !this.containsKey(k). containsValueBazı eğer çözüm başarısız olur diğer anahtarına açıkça değeri atanmıştır null. Örneğin: map = new HashMap(); map.put(k1, null); V v = map.get(k2);Bu durumda, vyine de nulldoğru olacak mı?
Adam Paynter

21
Genel olarak , bunun kötü bir fikir olduğunu düşünüyorum - varsayılan davranışı müşteriye veya Harita olduğunu iddia etmeyen bir delege iterdim. Özellikle, geçerli keySet () veya entrySet () eksikliği, Harita sözleşmesine uyulmasını bekleyen herhangi bir şeyle ilgili sorunlara neden olur. VeKey () ifadesini içeren sonsuz sayıda geçerli anahtar kümesi, teşhis edilmesi zor olan kötü performansa neden olabilir. Bununla birlikte, bunun belirli bir amaca hizmet edemeyeceğini söylememek.
Ed Staub

Bu yaklaşımla ilgili bir sorun, değerin karmaşık bir nesne olup olmadığıdır. <Dize, Liste> #put eşlemesi beklendiği gibi çalışmaz.
Eyal

ConcurrentHashMap üzerinde çalışmaz. Orada get'in sonucunu null olarak kontrol etmelisiniz.
dveim

162

Java 8'de Map.getOrDefault kullanın . Anahtarı alır ve eşleşen anahtar bulunmazsa döndürülecek değeri alır.


14
getOrDefaultçok güzel, ancak haritaya her erişildiğinde varsayılan tanım gerektirir. Bir kez varsayılan bir değer tanımlamak, statik bir değerler haritası oluştururken okunabilirlik avantajlarına da sahip olacaktır.
ach

3
Bu, kendinizi uygulamak için önemsizdir. private static String get(Map map, String s) { return map.getOrDefault(s, "Error"); }
Jack Satriano

@JackSatriano Evet, ancak varsayılan değeri sabit olarak kodlamanız veya statik bir değişkeniniz olması gerekir.
Blrp

1
ComputeIfAbsent kullanarak cevabın altına bakın, varsayılan değer pahalı olduğunda veya her seferinde farklı olması gerektiğinde daha iyidir.
hectorpal

Bellek için daha kötü olmasına rağmen ve yalnızca varsayılan değerin oluşturulması / hesaplanması pahalıysa hesaplama süresinden tasarruf sağlar. Ucuzsa, muhtemelen varsayılan bir değer döndürmek yerine haritaya eklemek zorunda olduğu için daha kötü performans gösterdiğini göreceksiniz. Kesinlikle başka bir seçenek olsa.
Spycho

73

Tekerleği yeniden icat etmek istemiyorsanız Commons ' DefaultedMap kullanın , örn.

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

Haritayı ilk etapta oluşturmaktan sorumlu değilseniz, mevcut bir haritayı da aktarabilirsiniz.


26
+1 olsa da, tekerleği yeniden icat etmek, basit işlevlerin küçük dilimleri için büyük bağımlılıklar sağlamaktan daha kolaydır.
maerics

3
ve komik olan şey, birlikte çalıştığım birçok projenin sınıf yolunda zaten böyle bir şeye sahip olması (Apache Commons veya Google Guava)
bartosz.r

@ bartosz.r, kesinlikle mobilde değil
Pacerier

44

Java 8, tembel hesaplanmış değeri depolayan ve böylece harita sözleşmesini bozmayan arayüze hoş bir computeIfAbsent varsayılan yöntemi getirdiMap :

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

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

Feragatname: Bu cevap OP'nin istediği ile tam olarak eşleşmemektedir, ancak bazı durumlarda anahtar sayısı sınırlı olduğunda ve farklı değerlerin önbelleğe alınması karlı olduğunda soru başlığının eşleşmesi yararlı olabilir. Belleğin gereksiz yere boşa harcanacağı için bol miktarda anahtar ve aynı varsayılan değerle aksi durumda kullanılmamalıdır.


OP'nin sorduğu şey değil: harita üzerinde hiçbir yan etki istemiyor. Ayrıca, mevcut olmayan her anahtar için varsayılan değerin saklanması gereksiz bir bellek alanı kaybıdır.
numéro6

@ numéro6, evet, bu OP'nin istediği ile tam olarak eşleşmiyor, ancak googling yapan bazı insanlar hala bu yan yanıtı yararlı buluyor. Belirtilen diğer cevaplar gibi, OP'nin harita sözleşmesini bozmadan tam olarak sorduğu şeylere ulaşmak imkansız. Burada bahsedilmeyen başka bir geçici çözüm Harita yerine başka bir soyutlama kullanmaktır .
Vadzim

Harita sözleşmesini bozmadan OP'nin istediklerini tam olarak elde etmek mümkündür. Herhangi bir çözüm gerekmez, sadece getOrDefault kullanmak doğru (en güncel) yoldur, computeIfAbsent yanlış yoldur: sonucu saklayarak mappingFunction ve hafızayı çağırırken zaman kaybedersiniz (her iki kayıp anahtar için de). Bunu yapmak için getOrDefault yerine iyi bir neden göremiyorum. Tarif ettiğim şey, Harita sözleşmesinde iki farklı yöntem olmasının kesin sebebidir: karıştırılmaması gereken iki farklı kullanım durumu vardır (bazılarını iş yerinde düzeltmek zorunda kaldım). Bu cevap karışıklığı yaydı.
numéro6

14

Tam olarak bunu yapan statik bir yöntem oluşturamaz mısınız?

private static <K, V> V getOrDefault(Map<K,V> map, K key, V defaultValue) {
    return map.containsKey(key) ? map.get(key) : defaultValue;
}

statik nerede saklanır?
Pacerier

10

HashMap'i devralan yeni bir sınıf oluşturabilir ve getDefault yöntemini ekleyebilirsiniz. İşte bir örnek kod:

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

        return defaultValue;
    }
}

Ed Staub'un yorumunda belirttiği nedenlerden dolayı ve Harita arabiriminin sözleşmesini kıracağınızdan (bu potansiyel olarak bulunması zor olabilir) böcek).


4
getMetodu geçersiz kılmamanın bir anlamı var . Öte yandan - çözümünüz sınıfın arabirim yoluyla kullanılmasına izin vermez, bu genellikle durumdur.
Krzysztof Jabłoński


3

Bunu varsayılan olarak yapar. Geri döner null.


@Larry, mükemmel bir şekilde HashMapsadece bu işlevsellik için alt sınıfa geçmek bana biraz aptalca geliyor null.
mrkhrts

15
Bir NullObjectdesen kullanıyorsanız veya kodunuz boyunca boş denetimleri dağıtmak istemiyorsanız iyi değil - tamamen anladığım bir arzu.
Dave Newton

3

Java 8+ üzerinde

Map.getOrDefault(Object key,V defaultValue)

1

LazyMap'i oldukça faydalı buldum .

Haritada bulunmayan bir anahtarla get (Object) yöntemi çağrıldığında, fabrika nesneyi oluşturmak için kullanılır. Oluşturulan nesne, istenen anahtar kullanılarak haritaya eklenir.

Bu, böyle bir şey yapmanıza izin verir:

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

To çağrısı get, verilen anahtar için varsayılan bir değer oluşturur. Varsayılan bağımsız değişkenin nasıl oluşturulacağını fabrika bağımsız değişkeniyle belirtirsiniz LazyMap.lazyMap(map, factory). Yukarıdaki örnekte, harita AtomicInteger0 değerine sahip yeni bir değere başlatılır .


Bu, varsayılan değerin bir fabrika tarafından oluşturulması bakımından kabul edilen cevaba göre bir avantaja sahiptir. Varsayılan değerim ise List<String>- kabul edilen yanıtı kullanarak, bir fabrikadan (diyelim) ziyade her yeni anahtar için aynı listeyi kullanma riskini alırım new ArrayList<String>().


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);
}

Örnek Kullanım:

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));

0

Alanların mevcut olacağını garanti edemedim JSON bir sunucudan döndürülen sonuçları okumak gerekiyordu. HashMap türetilmiş org.json.simple.JSONObject sınıfı kullanıyorum. İşte kullandığım bazı yardımcı fonksiyonlar:

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; }
}   

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.