J'ai ci-dessous l'implémentation existante
Map<String, CompletableFuture<Employee>[]> employeeCache = new HashMap<>(); .......... CompletableFuture<Employee>[] employeeDetails = empIds.stream().map(empId -> //Here I need to check in HashMap that empId is present or not if present then fetch from Map instead of calling service. employeeService.employeeDetails(Integer.valueOf(empId))) .filter(Objects::nonNull) .toArray(CompletableFuture[]::new);
Dans ce code, je dois vérifier dans HashMap que empId est déjà présent ou non comme ci-dessous - si empId n'est pas présent dans HashMap, appelez le service et mettez dans HashMap à des fins futures.
CompletableFuture<Employee>[] employeeDetails = empIds.stream().map(empId -> employeeService.employeeDetails(Integer.valueOf(empId))) .filter(Objects::nonNull) .toArray(CompletableFuture[]::new);
Comment puis-je ajouter si
archiver stream ()
api comme indiqué dans ci-dessus et le cas échéant, procurez-vous sur Map.
3 Réponses :
Vous pouvez utiliser Mappez # computeIfAbsent
pour réaliser ce que vous essayez de faire:
Map<String, CompletableFuture<Employee>[]> employeeCache = new HashMap<>(); CompletableFuture<Employee>[] employeeDetails = empIds.stream() .map(empId -> employeeCache .computeIfAbsent(empId, k -> employeeService.employeeDetails(Integer.valueOf(empId)))) .filter(Objects::nonNull) .toArray(CompletableFuture[]::new);
Merci Minar. J'oublie de mentionner que si la clé n'est pas présente HashMap, récupérez le service et mettez-le dans hashmap. Comment je peux mettre de la valeur dans HashMap dans ce cas. Ici, la valeur par défaut est toujours l'appel au service, mais je dois également mettre cette valeur dans HashMap après l'appel de service.
@ppb J'ai mis à jour la réponse. Veuillez voir si cela aide.
Donc, ici, computeIfAbsent () mettra de la valeur dans la carte s'il n'est pas présent?
Selon la documentation, le la fonction de mappage fournie à map
doit être sans interférence et sans état.
Merci Minar. J'essaie de corriger Junit pour cela, mais je ne sais pas comment je peux me moquer de la carte EmployeeCache et assertEqual ou assertThat pour la carte.
La fonction passée à computeIfAbsent
reçoit la clé comme paramètre, vous pouvez donc utiliser k
au lieu de capturer empId
ici. Le Integer.valueOf
semble également redondant, vous pouvez donc même utiliser empId -> employeeCache .computeIfAbsent (empId, employeeService :: employeeDetails)…
ici. En plus de cela, je recommande de corriger l'indentation trompeuse.
Vous pouvez éventuellement utiliser Optional.ofNullable
(car l'absence de clé dans une Map
renverrait null
) avec Optional.orElseGet
(pour effectuer l'appel de service lorsque la valeur n'est pas présente dans la Carte
) dans votre Opération Stream.map
comme:
List<CompletableFuture<Employee>[]> list = new ArrayList<>(); for (String empId : empIds) { CompletableFuture<Employee>[] completableFutures = employeeCache.putIfAbsent(employeeService.employeeDetails(Integer.valueOf(empId))); if (completableFutures != null) { list.add(completableFutures); } } CompletableFuture<Employee>[] employeeDetails = list.toArray(new CompletableFuture[0]);
ou comme suggéré par Aomine dans les commentaires, vous pouvez simplement utiliser getOrDefault
également:
CompletableFuture<Employee>[] employeeDetails = empIds.stream() .map(empId -> employeeCache.putIfAbsent(empiId, employeeService.employeeDetails(Integer.valueOf(empId)))) .filter(Objects::nonNull) .toArray(CompletableFuture[]::new);
Si vous souhaitez également mettre dans le cache, vous pouvez simplement utiliser Map.putIfAbsent
à mettre s'il n'est pas présent comme : st rike>
CompletableFuture<Employee>[] employeeDetails = empIds.stream() .map(empId -> employeeCache.getOrDefault(empId, employeeService.employeeDetails(Integer.valueOf(empId)))) .filter(Objects::nonNull) .toArray(CompletableFuture[]::new);
Si vous souhaitez également mettre à jour le cache tout en fecting depuis le service, vous êtes probablement mieux sans flux ici:
CompletableFuture<Employee>[] employeeDetails = empIds.stream() .map(empId -> Optional.ofNullable(employeeCache.get(empId)) .orElseGet(() -> employeeService.employeeDetails(Integer.valueOf(empId)))) .filter(Objects::nonNull) .toArray(CompletableFuture[]::new);
De plus, tout ça (si j'étais vous) j'aurais utilisé Guava's LoadingCache
qui offre des avantages similaires sous les couvertures avec même une personnalisation Implémentation de CacheLoader
.
Pour plus de détails, vous pouvez lire leur wiki - CachesExplained .
@ppb voulez-vous l'ajouter également à la carte lors de la récupération du service?
Oui. Je veux également l'ajouter à la carte lors de la récupération du service, donc à l'avenir, je n'ai plus besoin d'appeler le service pour cet empId.
@ppb mis à jour. puis utilisez putIfAbsent
, il renverrait la valeur si elle est déjà présente, ou bien la mettrait dans la carte puis la renverrait.
Si vous n'avez plus besoin de la carte employeeCache
, c'est-à-dire si vous ne l'utilisez que comme cache local dans l'opération Stream.map
, vous pouvez la résumer loin via une méthode utilitaire générique:
CompletableFuture<Employee>[] employeeDetails = empIds .stream() .map(memoize(empId -> employeeService.employeeDetails(Integer.valueOf(empId)))) .filter(Objects::nonNull) .toArray(CompletableFuture[]::new);
Cela s'appelle la mémorisation et c'est une technique bien connue, en particulier en programmation fonctionnelle.
Ensuite, vous l'utiliseriez dans votre exemple comme suit:
public static <T, U> Function<T, U> memoize(Function<T, U> f) { Map<T, U> m = new HashMap<>(); return t -> m.computeIfAbsent(t, f); }
Comme vous le voyez, cela est totalement transparent pour le code de l'appelant et il utilisera effectivement la carte comme cache.
MODIFIER: Veuillez noter que je J'ai simplement utilisé un HashMap
commun, car j'ai remarqué que le flux était exécuté de manière séquentielle. Si vous devez exécuter le flux en parallèle, vous devez changer le cache en ConcurrentHashMap
.
Avez-vous besoin de la carte plus loin, c'est-à-dire plus tard dans le code, pour autre chose? Ou en avez-vous besoin uniquement dans la fonction
Stream.map
, pour l'utiliser comme cache?