HashMap, um den Standardwert für nicht gefundene Schlüssel zurückzugeben?

151

Ist es möglich, HashMapfür alle Schlüssel, die nicht im Set enthalten sind, einen Standardwert zurückzugeben?

Larry
quelle
Sie können die Existenz des Schlüssels überprüfen und die Standardeinstellung zurückgeben. Oder erweitern Sie die Klasse und ändern Sie das Verhalten. oder Sie können sogar null verwenden - und ein Häkchen setzen, wo immer Sie es verwenden möchten.
SudhirJ
2
Dies ist verwandt / Duplikat von stackoverflow.com/questions/4833336/… einige andere Optionen werden dort diskutiert.
Mark Butler
3
Schauen Sie sich die Java 8-Lösung für Map API getOrDefault() Link
Trey Jonn

Antworten:

136

[Aktualisieren]

Wie von anderen Antworten und Kommentatoren angemerkt, können Sie ab Java 8 einfach aufrufen Map#getOrDefault(...).

[Original]

Es gibt keine Map-Implementierung, die dies genau tut, aber es wäre trivial, eine eigene zu implementieren, indem Sie HashMap erweitern:

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
quelle
20
Um genau zu sein, können Sie die Bedingung von (v == null)bis anpassen , (v == null && !this.containsKey(k))falls sie absichtlich einen nullWert hinzufügen . Ich weiß, dies ist nur ein Eckfall, aber der Autor kann darauf stoßen.
Adam Paynter
@maerics: Mir ist aufgefallen, dass du benutzt hast !this.containsValue(null). Dies unterscheidet sich geringfügig von !this.containsKey(k). Die containsValueLösung schlägt fehl, wenn einem anderen Schlüssel explizit der Wert "zugewiesen" wurde null. Zum Beispiel: Wird map = new HashMap(); map.put(k1, null); V v = map.get(k2);in diesem Fall vnoch nullrichtig sein?
Adam Paynter
21
Im Allgemeinen halte ich dies für eine schlechte Idee - ich würde das Standardverhalten auf den Client oder einen Delegaten übertragen, der nicht behauptet, eine Map zu sein. Insbesondere das Fehlen eines gültigen keySet () oder entrySet () führt zu Problemen mit allem, was erwartet, dass der Map-Vertrag eingehalten wird. Und der unendliche Satz gültiger Schlüssel, der Schlüssel () enthält, führt wahrscheinlich zu einer schlechten Leistung, die schwer zu diagnostizieren ist. Um jedoch nicht zu sagen, dass es möglicherweise keinem bestimmten Zweck dient.
Ed Staub
Ein Problem bei diesem Ansatz besteht darin, dass der Wert ein kompliziertes Objekt ist. Map <String, List> #put funktioniert nicht wie erwartet.
Eyal
Funktioniert nicht mit ConcurrentHashMap. Dort sollten Sie das Ergebnis von get auf null überprüfen.
dveim
162

Verwenden Sie in Java 8 Map.getOrDefault . Der Schlüssel und der Wert müssen zurückgegeben werden, wenn kein passender Schlüssel gefunden wird.

Spycho
quelle
14
getOrDefaultist sehr schön, erfordert aber bei jedem Zugriff auf die Karte die Standarddefinition. Das einmalige Definieren eines Standardwerts hätte auch Lesbarkeitsvorteile beim Erstellen einer statischen Wertekarte.
Ach
3
Dies ist trivial, um sich selbst zu implementieren. private static String get(Map map, String s) { return map.getOrDefault(s, "Error"); }
Jack Satriano
@ JackSatriano Ja, aber Sie müssten den Standardwert hart codieren oder eine statische Variable dafür haben.
Blrp
1
Siehe unten die Antwort mit computeIfAbsent, besser, wenn der Standardwert teuer ist oder jedes Mal anders sein sollte.
Hectorpal
Dies ist jedoch schlechter für den Speicher und spart nur dann Rechenzeit, wenn der Standardwert für die Erstellung / Berechnung teuer ist. Wenn es billig ist, werden Sie wahrscheinlich feststellen, dass es schlechter abschneidet, da es in die Karte eingefügt werden muss, anstatt nur einen Standardwert zurückzugeben. Sicherlich eine andere Option.
Spycho
73

Verwenden Sie die DefaultedMap von Commons, wenn Sie das Rad nicht neu erfinden möchten , z.

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

Sie können auch eine vorhandene Karte übergeben, wenn Sie nicht erst für die Erstellung der Karte verantwortlich sind.

Dave Newton
quelle
26
+1, obwohl es manchmal einfacher ist, das Rad neu zu erfinden, als große Abhängigkeiten für winzige Teile einfacher Funktionalität einzuführen.
Maerics
3
und lustig ist, dass viele Projekte, mit denen ich arbeite, bereits so etwas im Klassenpfad haben (entweder Apache Commons oder Google Guava)
bartosz.r
@ bartosz.r, definitiv nicht auf dem Handy
Pacerier
44

Java 8 hat eine nette computeIfAbsent- Standardmethode für die MapSchnittstelle eingeführt, die faul berechneten Wert speichert und so den Kartenvertrag nicht bricht:

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

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

Disclamer: Diese Antwort stimmt nicht genau mit dem überein, was OP gefragt hat, kann jedoch in einigen Fällen nützlich sein, wenn der Schlüsseltitel begrenzt ist und das Zwischenspeichern verschiedener Werte rentabel wäre. Es sollte nicht im umgekehrten Fall mit vielen Schlüsseln und demselben Standardwert verwendet werden, da dies unnötig Speicherplatz verschwenden würde.

Vadzim
quelle
Nicht das, was OP gefragt hat: Er möchte keine Nebenwirkungen auf der Karte. Das Speichern des Standardwerts für jeden fehlenden Schlüssel ist außerdem ein nutzloser Speicherplatzverlust.
numéro6
@ numéro6, ja, das stimmt nicht genau mit dem überein, was OP gefragt hat, aber einige googelnde Leute finden diese Nebenantwort immer noch nützlich. Wie in anderen Antworten erwähnt, ist es unmöglich, genau das zu erreichen, was OP verlangt hat, ohne den Kartenvertrag zu brechen. Eine andere hier nicht erwähnte Problemumgehung besteht darin , anstelle von Map eine andere Abstraktion zu verwenden .
Vadzim
Es ist möglich, genau das zu erreichen, was OP verlangt, ohne den Kartenvertrag zu brechen. Es ist keine Problemumgehung erforderlich. Die Verwendung von getOrDefault ist der richtige (am meisten aktualisierte) Weg. ComputeIfAbsent ist der falsche Weg: Sie verlieren Zeit beim Aufrufen der Mapping-Funktion und des Speichers, indem Sie das Ergebnis speichern (beide für jeden fehlenden Schlüssel). Ich kann keinen guten Grund dafür sehen, anstatt getOrDefault. Was ich beschreibe, ist der genaue Grund, warum der Kartenvertrag zwei unterschiedliche Methoden enthält: Es gibt zwei unterschiedliche Anwendungsfälle, die nicht verwechselt werden sollten (ich musste einige bei der Arbeit beheben). Diese Antwort verbreitete die Verwirrung.
numéro6
14

Können Sie nicht einfach eine statische Methode erstellen, die genau dies tut?

private static <K, V> V getOrDefault(Map<K,V> map, K key, V defaultValue) {
    return map.containsKey(key) ? map.get(key) : defaultValue;
}
Shervin Asgari
quelle
Wo soll die statische Aufladung aufbewahrt werden?
Pacerier
10

Sie können einfach eine neue Klasse erstellen, die HashMap erbt, und die Methode getDefault hinzufügen. Hier ist ein Beispielcode:

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

        return defaultValue;
    }
}

Ich denke, dass Sie die Methode get (K key) in Ihrer Implementierung nicht überschreiben sollten, da Ed Staub in seinem Kommentar Gründe angegeben hat und Sie den Vertrag über die Map-Schnittstelle brechen werden (dies kann möglicherweise zu schwer zu findenden Gründen führen Bugs).

Ivan Mushketyk
quelle
4
Sie haben einen Sinn darin, die getMethode nicht zu überschreiben . Auf der anderen Seite erlaubt Ihre Lösung nicht, die Klasse über eine Schnittstelle zu verwenden, was häufig der Fall ist.
Krzysztof Jabłoński
5

Verwenden:

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

Dies geschieht standardmäßig. Es kehrt zurück null.

mrkhrts
quelle
@ Larry, scheint mir ein wenig albern, HashMapnur für diese Funktionalität eine Unterklasse zu bilden, wenn dies nullvollkommen in Ordnung ist.
Mrkhrts
15
Es ist jedoch nicht in Ordnung, wenn Sie ein NullObjectMuster verwenden oder keine Nullprüfungen in Ihrem Code verteilen möchten - ein Wunsch, den ich vollständig verstehe.
Dave Newton
3

Auf Java 8+

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

Ich fand die LazyMap sehr hilfreich.

Wenn die Methode get (Object) mit einem Schlüssel aufgerufen wird, der in der Map nicht vorhanden ist, wird das Objekt in der Factory erstellt. Das erstellte Objekt wird mit dem angeforderten Schlüssel zur Karte hinzugefügt.

Auf diese Weise können Sie Folgendes tun:

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

Der Aufruf zum getErstellen eines Standardwerts für den angegebenen Schlüssel. Sie geben an, wie der Standardwert mit dem Factory-Argument an erstellt werden soll LazyMap.lazyMap(map, factory). Im obigen Beispiel wird die Karte AtomicIntegermit dem Wert 0 auf eine neue Karte initialisiert .

Benedikt Köppel
quelle
Dies hat gegenüber der akzeptierten Antwort den Vorteil, dass der Standardwert von einer Factory erstellt wird. Was ist, wenn mein Standardwert lautet List<String>: Wenn ich die akzeptierte Antwort verwende, riskiere ich, für jeden neuen Schlüssel dieselbe Liste zu verwenden, anstatt (sagen wir) eine new ArrayList<String>()aus einer Fabrik.
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);
}

Anwendungsbeispiel:

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
quelle
0

Ich musste die Ergebnisse lesen, die von einem Server in JSON zurückgegeben wurden, auf dem ich nicht garantieren konnte, dass die Felder vorhanden sind. Ich verwende die Klasse org.json.simple.JSONObject, die von HashMap abgeleitet ist. Hier sind einige Hilfsfunktionen, die ich verwendet habe:

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; }
}   
BuvinJ
quelle
-1

Berücksichtigen Sie in gemischten Java / Kotlin-Projekten auch Kotlins Map.withDefault .

Vadzim
quelle