Reputation: 2613
I have below existing implementation
CompletableFuture<Employee>[] employeeDetails =
empIds.stream().map(empId ->
employeeService.employeeDetails(Integer.valueOf(empId)))
.filter(Objects::nonNull)
.toArray(CompletableFuture[]::new);
In this code I need to check in HashMap that empId is already present or not as below - if empId is not present in HashMap then call service and put it into HashMap for future purpose.
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);
How I can add if
check in stream()
api as shown in above and if present then get from Map.
Upvotes: 4
Views: 917
Reputation: 34460
If you don't further need the employeeCache
map, i.e. if you are only using it as a local cache within the Stream.map
operation, you could abstract it away via a generic utility method:
public static <T, U> Function<T, U> memoize(Function<T, U> f) {
Map<T, U> m = new HashMap<>();
return t -> m.computeIfAbsent(t, f);
}
This is called memoization and is a well-known technique, especially in functional programming.
Then, you'd use it in your example as follows:
CompletableFuture<Employee>[] employeeDetails = empIds
.stream()
.map(memoize(empId -> employeeService.employeeDetails(Integer.valueOf(empId))))
.filter(Objects::nonNull)
.toArray(CompletableFuture[]::new);
As you see, this is totally transparent to the caller code and it will indeed use the map as a cache.
EDIT: Please note that I've used just a common HashMap
, because I've noticed the stream was run sequentially. If you are to ever run the stream in parallel, you should change the cache to ConcurrentHashMap
.
Upvotes: 0
Reputation: 31868
You could possibly use Optional.ofNullable
(since lack of key in a Map
would return null
) along with Optional.orElseGet
(to make the service call when the value isn't present in the Map
) in your Stream.map
operation as:
CompletableFuture<Employee>[] employeeDetails = empIds.stream()
.map(empId -> Optional.ofNullable(employeeCache.get(empId))
.orElseGet(() -> employeeService.employeeDetails(Integer.valueOf(empId))))
.filter(Objects::nonNull)
.toArray(CompletableFuture[]::new);
or as suggested by Aomine in comments, you could simply use getOrDefault
as well:
CompletableFuture<Employee>[] employeeDetails = empIds.stream()
.map(empId -> employeeCache.getOrDefault(empId,
employeeService.employeeDetails(Integer.valueOf(empId))))
.filter(Objects::nonNull)
.toArray(CompletableFuture[]::new);
If you want to put in the cache as well, you could simply use Map.putIfAbsent
to put if not present as:
CompletableFuture<Employee>[] employeeDetails = empIds.stream()
.map(empId -> employeeCache.putIfAbsent(empiId, employeeService.employeeDetails(Integer.valueOf(empId))))
.filter(Objects::nonNull)
.toArray(CompletableFuture[]::new);
If you want to update the cache as well while fecthing from the service, you're probably better of without streams here:
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]);
Besides, all of that (if I were you) I would have used Guava's LoadingCache
which provides similar benefits under the covers with even a custom CacheLoader
implementation.
For further details over it you can read their wiki - CachesExplained.
Upvotes: 1
Reputation: 2665
You can use Map#computeIfAbsent
to achieve what you are trying to do:
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);
Upvotes: 3