Guava: Elegant Caching Mechanisms

There are many different ways to implement a caching mechanism. Google’s Guava provides very simple and elegant solutions to do so.

OPTION 1: Caching using Supplier

If you want to cache just one thing, Supplier should satisfy that requirement.

private Supplier<Collection<Person>> allEmployeesCache = Suppliers.memoizeWithExpiration(
        new Supplier<Collection<Person>>() {
            public Collection<Person> get() {
                return makeExpensiveCallToGetAllEmployees();
            }
        }, 1, TimeUnit.DAYS);

public Collection<Person> getAllEmployees() {
    return allEmployeesCache.get();
}

In the above example, we cache all employees for a day.

OPTION 2: Caching using LoadingCache

If you want to cache multiple things (ex: think Hashmap-like), LoadingCache should satisfy that requirement.

private LoadingCache<String, Collection<Person>> employeeRoleLoadingCache = CacheBuilder.newBuilder()
        .expireAfterWrite(1, TimeUnit.DAYS)
        .maximumSize(1000)
        .build(new CacheLoader<String, Collection<Person>>() {
            @Override
            public Collection<Person> load(String roleName) throws Exception {
                return makeExpensiveCallToGetAllEmployeesByRoleName(roleName);
            }
        });

public Collection<Person> getAllEmployeesByRole(String roleName) {
    return employeeRoleLoadingCache.getUnchecked(roleName);
}

In the above example, we cache all employees for each given role for a day. Further, we configure the cache to store up to 1000 elements before evicting the older cached element.

Guava: Elegant Collection Ordering

PROBLEM

Java provides a built-in API to sort a list, but it is flat out ugly and prone to error especially when dealing with Comparator. Here’s an example:-

List<Request> allRequests = ...;

Collections.sort(allRequests, new Comparator<Request>() {
    @Override
    public int compare(Request lhs, Request rhs) {
        if (lhs.getSubmissionDatetime() == null) {
            return -1;
        }
        else if (rhs.getSubmissionDatetime() == null) {
            return 1;
        }

        return lhs.getSubmissionDatetime().compareTo(rhs.getSubmissionDatetime());
    }
});

Number of puppies killed = 3 … from the if-else statement.

SOLUTION

A better way to do this is to use Ordering from Guava:-

List<Request> allRequests = ...;

List<Request> sortedRequests = Ordering.natural()
        .nullsFirst()
        .onResultOf(new Function<Request, LocalDateTime>() {
            @Override
            public LocalDateTime apply(Request request) {
                return request.getSubmissionDatetime();
            }
        })
        .immutableSortedCopy(allRequests);

In this example, we want all the null elements to appear on top of the list. We can also use nullsLast() if we want them to be at the bottom of the list.

Number of puppies killed = 0.

Java: Performing 2-Key Lookup using HashBasedTable

PROBLEM

HashMap is great to perform a simple lookup, for example:-

Map<String, String> map = new HashMap<String, String>();
map.put("key", "mike");

String value = map.get("key"); // "mike"

However, what if we need 2 keys to lookup a value? For example, we need “key1” + “key2” in order to lookup “mike”.

SOLUTION 1: Create combo key

You can combine the keys to create a unique key:-

Map<String, String> map = new HashMap<String, String>();
map.put("key1|key2", "mike");

String value = map.get("key1|key2"); // "mike"

Assuming if the keys are just characters, this works fine. However, this solution might not work too well if the keys are objects, like Person object, etc.

SOLUTION 2: Create key as an object

In this solution, we leverage Apache Commons’ MultiKey to create the combo key:-

Map<MultiKey, String> map = new HashMap<MultiKey, String>();
map.put(new MultiKey("key1", "key2"), "mike");

String value = map.get(new MultiKey("key1", "key2")); // "mike"

This solution works a little better than the first solution, but the code looks just plain ugly to read.

SOLUTION 3: Using Map of Map

In this solution, we use Map of Map to accomplish the 2-key lookup:-

Map<String, Map<String, String>> map = new HashMap<String, Map<String, String>>();

Map<String, String> innnerMap = new HashMap<String, String>();
innnerMap.put("key2", "mike");

map.put("key1", innnerMap);

String value = map.get("key1").get("key2"); // "mike"

This solution obviously offers tons of flexibility, but it is shit difficult to read… and sadly, I myself implemented something like this in the past. In my opinion, this solution is too complicated for what we are trying to achieve here. We also have to worry about whether map.get(firstKey) will return null, which may cause NullPointerException if we chain the call to lookup the second key.

SOLUTION 4: Using HashBasedTable

The most elegant solution in my opinion is to utilize Google Guava’s HashBasedTable:-

HashBasedTable<String, String, String> hashBasedTable = HashBasedTable.create();
hashBasedTable.put("key1", "key2", "mike");

String value = hashBasedTable.get("key1", "key2"); // "mike"

This is probably the cleanest solution to perform 2-key lookup. Based on the documentation, HashBasedTable itself is using Map of Map under the cover.