web-dev-qa-db-fra.com

Comment utiliser la nouvelle fonction computeIfAbsent?

J'ai très envie d'utiliser Map.computeIfAbsent mais cela fait trop longtemps que lambdas n'est pas encore au premier cycle.

Presque directement à partir de la documentation: il donne un exemple de l'ancienne façon de faire les choses:

Map<String, Boolean> 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 comprends pas tout à fait. Comment pourrais-je transformer le code pour utiliser la nouvelle manière lambda de l'exprimer?

93
Benjamin H

Supposons que vous ayez le code suivant:

import Java.util.Map;
import Java.util.concurrent.ConcurrentHashMap;

public class Test {
    public static void main(String[] s) {
        Map<String, Boolean> whoLetDogsOut = new ConcurrentHashMap<>();
        whoLetDogsOut.computeIfAbsent("snoop", k -> f(k));
        whoLetDogsOut.computeIfAbsent("snoop", k -> f(k));
    }
    static boolean f(String s) {
        System.out.println("creating a value for \""+s+'"');
        return s.isEmpty();
    }
}

Ensuite, vous verrez le message creating a value for "snoop" exactement une fois, comme lors du deuxième appel de computeIfAbsent, il existe déjà une valeur pour cette clé. La k dans l'expression lambda k -> f(k) n'est qu'un détenteur de place (paramètre) pour la clé que la carte transmettra à votre lambda pour le calcul de la valeur. Ainsi, dans l'exemple, la clé est transmise à l'appel de la fonction.

Vous pouvez également écrire: whoLetDogsOut.computeIfAbsent("snoop", k -> k.isEmpty()); pour obtenir le même résultat sans méthode d'assistance (mais vous ne verrez pas la sortie du débogage pour le moment). Et encore plus simple, comme il s’agit d’une simple délégation à une méthode existante, vous pouvez écrire: whoLetDogsOut.computeIfAbsent("snoop", String::isEmpty); Cette délégation n’a besoin d’écrire aucun paramètre.

Pour vous rapprocher de l’exemple de votre question, vous pouvez l’écrire sous la forme whoLetDogsOut.computeIfAbsent("snoop", key -> tryToLetOut(key)); (que vous ayez nommé le paramètre k ou key). Ou écrivez-le sous la forme whoLetDogsOut.computeIfAbsent("snoop", MyClass::tryToLetOut); si tryToLetOut est static ou whoLetDogsOut.computeIfAbsent("snoop", this::tryToLetOut); si tryToLetOut est une méthode d'instance.

86
Holger

Récemment, je jouais aussi avec cette méthode. J'ai écrit un algorithme mémoisé pour calculer les nombres de Fibonacci, qui pourrait servir à illustrer de nouveau l'utilisation de la méthode.

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

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

Et pour le pas inductif, tout ce que nous avons à faire est de redéfinir notre fonction de 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 ce nombre n'est pas présent sur la carte. Cela représente une amélioration significative par rapport à l’algorithme traditionnel récursif.

102
Edwin Dalorzo

Un autre exemple. Lors de la création d'une carte complexe de cartes, la méthode computeIfAbsent () remplace la méthode get () de la carte. En chaînant les appels computeIfAbsent (), les conteneurs manquants sont construits à la volée avec les expressions lambda fournies:

  // Stores regional movie ratings
  Map<String, Map<Integer, Set<String>>> regionalMovieRatings = new TreeMap<>();

  // This will throw NullPointerException!
  regionalMovieRatings.get("New York").get(5).add("Boyhood");

  // This will work
  regionalMovieRatings
    .computeIfAbsent("New York", region -> new TreeMap<>())
    .computeIfAbsent(5, rating -> new TreeSet<>())
    .add("Boyhood");
34
hexabc

Cela est très utile si vous souhaitez créer une carte multimédia sans utiliser la bibliothèque goyave ( https://google.github.io/guava/releases/19.0/api/docs/com/google/common/collect/Multimap. html )

Par exemple: Si vous souhaitez enregistrer une liste d’étudiants inscrits à un sujet particulier. La solution normale pour cela en utilisant la bibliothèque jdk est

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

//continue....

Comme il existe un code de plaque de chaudière, les gens ont tendance à utiliser la goyave Mutltimap.

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

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

Stuart Marks et Brian Goetz ont fait un bon discours à ce sujet https://www.youtube.com/watch?v=9uTVXxJjuco

21
nantitv