ppb
ppb

Reputation: 2613

conditional check in java 8 stream apis

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

Answers (3)

fps
fps

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

Naman
Naman

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

Minar Mahmud
Minar Mahmud

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

Related Questions