HashMap pour renvoyer la valeur par défaut pour les clés non trouvées?

151

Est-il possible d'avoir un HashMapretour d'une valeur par défaut pour toutes les clés qui ne sont pas trouvées dans l'ensemble?

Larry
la source
Vous pouvez vérifier l'existence de la clé et renvoyer la valeur par défaut. Ou étendez la classe et modifiez le comportement. ou même vous pouvez utiliser null - et mettre un peu de contrôle là où vous voulez l'utiliser.
SudhirJ du
2
Ceci est lié / dupliqué à stackoverflow.com/questions/4833336/ ... certaines autres options sont discutées ici.
Mark Butler
3
Découvrez la solution Java 8 pour le getOrDefault() lien
Trey Jonn

Réponses:

136

[Mettre à jour]

Comme indiqué par d'autres réponses et commentateurs, à partir de Java 8, vous pouvez simplement appeler Map#getOrDefault(...).

[Original]

Il n'y a pas d'implémentation de Map qui fait exactement cela, mais il serait trivial d'implémenter la vôtre en étendant 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
la source
20
Pour être précis, vous voudrez peut-être ajuster la condition de (v == null)à (v == null && !this.containsKey(k))au cas où ils ajoutaient volontairement une nullvaleur. Je sais, ce n'est qu'un cas secondaire, mais l'auteur peut y tomber.
Adam Paynter le
@maerics: J'ai remarqué que vous utilisiez !this.containsValue(null). C'est subtilement différent de !this.containsKey(k). La containsValuesolution échouera si une autre clé a été explicitement affectée à la valeur null. Par exemple: map = new HashMap(); map.put(k1, null); V v = map.get(k2);dans ce cas, vsera toujours null, correct?
Adam Paynter le
21
En général , je pense que c'est une mauvaise idée - je pousserais le comportement par défaut dans le client, ou un délégué qui ne prétend pas être une carte. En particulier, l'absence de keySet () ou entrySet () valide causera des problèmes avec tout ce qui s'attend à ce que le contrat Map soit respecté. Et l'ensemble infini de clés valides qu'implique contientKey () est susceptible de provoquer de mauvaises performances difficiles à diagnostiquer. Cela ne veut pas dire, cependant, que cela pourrait ne pas servir un objectif particulier.
Ed Staub le
Un problème avec cette approche est si la valeur est un objet compliqué. Map <String, List> #put ne fonctionnera pas comme prévu.
Eyal
Ne fonctionne pas sur ConcurrentHashMap. Là, vous devriez vérifier le résultat de get pour null.
dveim
162

Dans Java 8, utilisez Map.getOrDefault . Il prend la clé et la valeur à renvoyer si aucune clé correspondante n'est trouvée.

Spycho
la source
14
getOrDefaultest très agréable, mais nécessite la définition par défaut à chaque fois que la carte est accédée. La définition unique d'une valeur par défaut aurait également des avantages de lisibilité lors de la création d'une carte statique de valeurs.
ach
3
C'est facile à mettre en œuvre vous-même. private static String get(Map map, String s) { return map.getOrDefault(s, "Error"); }
Jack Satriano
@JackSatriano Ouais, mais vous devrez coder en dur la valeur par défaut ou avoir une variable statique pour cela.
Blrp
1
Voir ci-dessous la réponse utilisant computeIfAbsent, mieux lorsque la valeur par défaut est chère ou doit être différente à chaque fois.
hectorpal
Bien que ce soit pire pour la mémoire, et ne fera gagner du temps de calcul que si la valeur par défaut est coûteuse à construire / calculer. S'il est bon marché, vous trouverez probablement qu'il fonctionne moins bien, car il doit s'insérer dans la carte, plutôt que de simplement renvoyer une valeur par défaut. Certainement une autre option cependant.
Spycho
73

Utilisez DefaultedMap de Commons si vous n'avez pas envie de réinventer la roue, par exemple,

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

Vous pouvez également transmettre une carte existante si vous n'êtes pas en charge de la création de la carte en premier lieu.

Dave Newton
la source
26
+1 bien qu'il soit parfois plus facile de réinventer la roue que d'introduire de grandes dépendances pour de minuscules tranches de fonctionnalités simples.
maerics
3
et ce qui est drôle, c'est que de nombreux projets avec lesquels je travaille ont déjà quelque chose comme ça dans classpath (Apache Commons ou Google Guava)
bartosz.r
@ bartosz.r, certainement pas sur mobile
Pacerier
44

Java 8 a introduit une belle méthode par défaut computeIfAbsent pour l' Mapinterface qui stocke la valeur calculée paresseusement et ne rompt donc pas le contrat de carte:

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

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

Disclamer: Cette réponse ne correspond pas exactement à ce que OP a demandé, mais peut être pratique dans certains cas, correspondre au titre de la question lorsque le nombre de clés est limité et que la mise en cache de différentes valeurs serait rentable. Il ne devrait pas être utilisé dans le cas contraire avec beaucoup de clés et la même valeur par défaut car cela gaspillerait inutilement de la mémoire.

Vadzim
la source
Pas ce que OP a demandé: il ne veut aucun effet secondaire sur la carte. En outre, le stockage de la valeur par défaut pour chaque clé absente est une perte inutile d'espace mémoire.
numéro6
@ numéro6, oui, cela ne correspond pas exactement à ce que OP a demandé, mais certaines personnes qui recherchent sur Google trouvent toujours cette réponse secondaire utile. Comme d'autres réponses l'ont mentionné, il est impossible d'atteindre exactement ce que OP a demandé sans rompre le contrat de la carte. Une autre solution de contournement non mentionnée ici consiste à utiliser une autre abstraction au lieu de Map .
Vadzim le
Il est possible de réaliser exactement ce que OP a demandé sans rompre le contrat de carte. Aucune solution de contournement n'est nécessaire, utiliser simplement getOrDefault est la bonne méthode (la plus mise à jour), computeIfAbsent est la mauvaise méthode: vous perdrez du temps à appeler la fonction mappingFunction et la mémoire en stockant le résultat (les deux pour chaque clé manquante). Je ne vois aucune bonne raison de faire cela au lieu de getOrDefault. Ce que je décris est la raison exacte pour laquelle il existe deux méthodes distinctes sur le contrat Map: il y a deux cas d'utilisation distincts qui ne doivent pas être confondus (j'ai dû en corriger certains au travail). Cette réponse a semé la confusion.
numéro6
14

Ne pouvez-vous pas simplement créer une méthode statique qui fait exactement cela?

private static <K, V> V getOrDefault(Map<K,V> map, K key, V defaultValue) {
    return map.containsKey(key) ? map.get(key) : defaultValue;
}
Shervin Asgari
la source
où stocker la statique?
Pacerier
10

Vous pouvez simplement créer une nouvelle classe qui hérite de HashMap et ajouter la méthode getDefault. Voici un exemple de code:

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

        return defaultValue;
    }
}

Je pense que vous ne devriez pas surcharger la méthode get (K key) dans votre implémentation, à cause des raisons spécifiées par Ed Staub dans son commentaire et parce que vous allez rompre le contrat de l'interface Map (cela peut potentiellement conduire à des problèmes difficiles à trouver Bugs).

Ivan Mushketyk
la source
4
Vous avez raison de ne pas remplacer la getméthode. D'autre part, votre solution ne permet pas d'utiliser la classe via l'interface, ce qui peut souvent être le cas.
Krzysztof Jabłoński
5

Utilisation:

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

Il le fait par défaut. Il revient null.

mrkhrts
la source
@Larry, me semble un peu ridicule de sous HashMap- classer juste pour cette fonctionnalité quand nullc'est parfaitement bien.
mrkhrts
15
Ce n'est pas bien si vous utilisez un NullObjectmodèle, cependant, ou si vous ne voulez pas disperser des vérifications nulles dans votre code - un désir que je comprends parfaitement.
Dave Newton
3

Sur java 8+

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

J'ai trouvé la LazyMap très utile.

Lorsque la méthode get (Object) est appelée avec une clé qui n'existe pas dans la carte, la fabrique est utilisée pour créer l'objet. L'objet créé sera ajouté à la carte à l'aide de la clé demandée.

Cela vous permet de faire quelque chose comme ceci:

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

L'appel à getcrée une valeur par défaut pour la clé donnée. Vous spécifiez comment créer la valeur par défaut avec l'argument de fabrique à LazyMap.lazyMap(map, factory). Dans l'exemple ci-dessus, la carte est initialisée à un nouveau AtomicIntegeravec la valeur 0.

Benedikt Köppel
la source
Cela présente un avantage par rapport à la réponse acceptée en ce que la valeur par défaut est créée par une usine. Et si ma valeur par défaut est List<String>- en utilisant la réponse acceptée, je risquerais d'utiliser la même liste pour chaque nouvelle clé, plutôt que (disons) une new ArrayList<String>()d'une usine.
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);
}

Exemple d'utilisation:

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
la source
0

J'avais besoin de lire les résultats renvoyés par un serveur en JSON où je ne pouvais pas garantir que les champs seraient présents. J'utilise la classe org.json.simple.JSONObject qui est dérivée de HashMap. Voici quelques fonctions d'aide que j'ai employées:

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
la source
-1

Dans les projets mixtes Java / Kotlin, considérez également Map.withDefault de Kotlin .

Vadzim
la source