157 votes

Comment puis-je utiliser la nouvelle fonction computeIfAbsent?

Je tiens vraiment à utiliser Map.computeIfAbsent mais cela fait trop longtemps depuis les lambdas durant mes études universitaires.

Presque directement depuis la documentation: cela donne un exemple de l'ancienne façon de faire les choses :

Map whoLetDogsOut = new ConcurrentHashMap<>();
String key = "snoop";
if (whoLetDogsOut.get(key) == null) {
  Boolean isLetOut = tryToLetOut(key);
  if (isLetOut != null)
    map.putIfAbsent(key, isLetOut);
}

Et la nouvelle façon :

map.computeIfAbsent(key, k -> new Value(f(k)));

Mais dans leur exemple, je pense que je ne suis pas tout à fait en train de vraiment le comprendre. Comment pourrais-je transformer le code pour utiliser la nouvelle façon lambda d'exprimer ceci ?

142voto

Edwin Dalorzo Points 19899

Récemment, j'ai aussi joué avec cette méthode. J'ai écrit un algorithme mémorisé pour calculer les nombres de Fibonacci qui pourrait servir comme une autre illustration sur comment utiliser la méthode.

Nous pouvons commencer par définir une carte et mettre les valeurs dedans pour les cas de base, à savoir, fibonnaci(0) et fibonacci(1):

private static Map memo = new HashMap<>();
static {
   memo.put(0,0L); //fibonacci(0)
   memo.put(1,1L); //fibonacci(1)
}

Et pour l'étape inductive, tout ce que nous avons à faire est de redéfinir notre fonction Fibonacci comme suit:

public static long fibonacci(int x) {
   return memo.computeIfAbsent(x, n -> fibonacci(n-2) + fibonacci(n-1));
}

Comme vous pouvez le constater, la méthode computeIfAbsent utilisera l'expression lambda fournie pour calculer le nombre de Fibonacci lorsque le nombre n'est pas présent dans la carte. Cela représente une amélioration significative par rapport à l'algorithme récursif traditionnel.

120voto

Holger Points 13789

Supposons que vous avez le code suivant:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Test {
    public static void main(String[] s) {
        Map whoLetDogsOut = new ConcurrentHashMap<>();
        whoLetDogsOut.computeIfAbsent("snoop", k -> f(k));
        whoLetDogsOut.computeIfAbsent("snoop", k -> f(k));
    }
    static boolean f(String s) {
        System.out.println("créer une valeur pour \""+s+'"');
        return s.isEmpty();
    }
}

Alors vous verrez le message créer une valeur pour "snoop" exactement une fois car lors de la deuxième invocation de computeIfAbsent, il y a déjà une valeur pour cette clé. Le k dans l'expression lambda k -> f(k) est juste un espace réservé (paramètre) pour la clé que la carte transmettra à votre lambda pour calculer la valeur. Ainsi dans l'exemple, la clé est transmise à l'invocation de la fonction.

Alternativement, vous pourriez écrire: whoLetDogsOut.computeIfAbsent("snoop", k -> k.isEmpty()); pour obtenir le même résultat sans méthode auxiliaire (mais vous ne verrez pas la sortie de débogage alors). Et encore plus simple, comme c'est une simple délégation à une méthode existante, vous pourriez écrire: whoLetDogsOut.computeIfAbsent("snoop", String::isEmpty); Cette délégation ne nécessite pas d'écrire des paramètres.

Pour être plus proche de l'exemple dans votre question, vous pourriez l'écrire comme ceci: whoLetDogsOut.computeIfAbsent("snoop", clé -> tryToLetOut(clé)); (peu importe que vous nomiez le paramètre k ou clé). Ou l'écrire comme whoLetDogsOut.computeIfAbsent("snoop", MyClass::tryToLetOut); si tryToLetOut est static ou whoLetDogsOut.computeIfAbsent("snoop", this::tryToLetOut); si tryToLetOut est une méthode d'instance.

56voto

hexabc Points 558

Un autre exemple. Lors de la construction d'une carte complexe de cartes, la méthode computeIfAbsent() est un remplacement de la méthode get() de la carte. En enchaînant les appels de computeIfAbsent() ensemble, les conteneurs manquants sont construits à la volée par les expressions lambda fournies :

// Stocke les évaluations des films régionaux
Map>> évaluationsFilmsRégionaux = new TreeMap<>();

// Cela lancera une NullPointerException !
évaluationsFilmsRégionaux.get("New York").get(5).add("Boyhood");

// Cela fonctionnera
évaluationsFilmsRégionaux
  .computeIfAbsent("New York", région -> new TreeMap<>())
  .computeIfAbsent(5, évaluation -> new TreeSet<>())
  .add("Boyhood");

40voto

nantitv Points 185

Multi-map

Ceci est vraiment utile si vous voulez créer une multimap sans recourir à la bibliothèque Google Guava pour son implémentation de MultiMap.

Par exemple, supposez que vous voulez stocker une liste d'étudiants qui se sont inscrits à une matière particulière.

La solution classique pour cela en utilisant la bibliothèque JDK est :

Map> studentListSubjectWise = new TreeMap<>();
Listlis = studentListSubjectWise.get("a");
if(lis == null) {
    lis = new ArrayList<>();
}
lis.add("John");

//continuer....

Étant donné qu'il contient un peu de code redondant, les gens ont tendance à utiliser Guava Mutltimap.

En utilisant Map.computeIfAbsent, nous pouvons écrire en une seule ligne sans Guava Multimap comme suit.

studentListSubjectWise.computeIfAbsent("a", (x -> new ArrayList<>())).add("John");

Stuart Marks & Brian Goetz ont fait une bonne présentation à ce sujet https://www.youtube.com/watch?v=9uTVXxJjuco

0voto

Nrj Points 2788

Voici un exemple de comparaison (ancien vs nouveau) qui démontre les deux approches :

static Map> playerSkills = new HashMap<>();
public static void main(String[] args) {
    //résultat souhaité
    //player1, cricket, baseball
    //player2, swimming

    //ancienne méthode
    add("Joueur1","cricket");
    add("Joueur2","natation");
    add("Joueur1","baseball");

    System.out.println(playerSkills);

    //vider
    playerSkills.clear();

    //nouvelle méthode
    addNew("Joueur1","cricket");
    addNew("Joueur2","natation");
    addNew("Joueur1","baseball");
    System.out.println(playerSkills);

}

private static void add(String name, String skill) {
    Set skills = playerSkills.get(name);
    if(skills==null) {
        skills= new HashSet<>();
        playerSkills.put(name, skills);
    }
    skills.add(skill);
}

private static void addNew(String name, String skill) {
    playerSkills
            .computeIfAbsent(name, set -> new HashSet<>())
            .add(skill);
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X