Google Guava cache suppliers

Versions: Google Guava 19

Sometimes we need to cache only singular object, without associating it with any key. In this case the use of Guava's LoadingCache is not well adapted. But fortunately, Guava provides another objects to deal with this situation - Suppliers.

The goal is to show another method to generate cache loaders. In the first part of this post we focus on Suppliers API. The second part, still through JUnit tests, shows sample use cases.

Guava Suppliers API

Current Guava's release (19.0 at the time of writing) proposes Suppliers inside com.google.common.base package. Unlike CacheLoader, it's not placed inside cache package because the role of suppliers is more general. In fact, this final class defines several methods that we can use when we need to get an object from a function. However, some additional features make of it a great and simple cache loader.

Basically, to get an object, we need to call a get() method, defined in Supplier interface, implemented inside Suppliers class as objects with different characteristics. Thanks to these objects, we have a possibility to work with:

Guava Suppliers example

After discovering different types of Supplier, we can pass to code part:

public class SuppliersTest {

  @Test
  public void should_correctly_create_and_use_composed_supplier() {
    int inputSupplierSalary = 300;
    Supplier<Integer> composedSupplier = 
      Suppliers.compose(salary -> salary * 2, () -> inputSupplierSalary);

    assertThat(composedSupplier.get()).isEqualTo(600);
  }

  @Test
  public void should_correctly_cache_value_from_supplier() {
    Integer cachedValue = new Integer(89);

    // Simple use case is shown here but cache can avoid to make redundant I/O operations
    // for the values which don't change but must be stored
    // somewhere outside JAR (database, exposed web service etc.)
    Supplier<Integer> cachingSupplier = Suppliers.memoize(() -> cachedValue);

    assertThat(cachingSupplier.get() == cachedValue).isTrue();
  }

  @Test
  public void should_correctly_cache_value_from_supplier_with_expiration_time() throws InterruptedException {
    List<Integer> invocations = new ArrayList<>();
    Supplier<Integer> delegateSupplier = () -> {
      invocations.add(1);
      return 50;
    };

    // Similarly to the test with memoize(), we've simple use case but
    // expiration cache can help to reduce I/O requests loading some
    // objects that not change every second (ex: distinguished news in newspaper's homepage)
    Supplier<Integer> expiringCacheSupplier = 
      Suppliers.memoizeWithExpiration(delegateSupplier, 1, TimeUnit.SECONDS);
    Integer cachedValue = expiringCacheSupplier.get();
    assertThat(cachedValue).isEqualTo(50);
    assertThat(invocations).hasSize(1);

    // wait 1 second, should re-call get() method
    // instead of returning cached value
    Thread.sleep(1000L);
    cachedValue = expiringCacheSupplier.get();
    assertThat(cachedValue).isEqualTo(50);
    assertThat(invocations).hasSize(2);
    // invoke get() immediately after, cached instance should be returned
    cachedValue = expiringCacheSupplier.get();
    assertThat(cachedValue).isEqualTo(50);
    assertThat(invocations).hasSize(2);
  }

  @Test
  public void should_always_supply_the_same_object_instance() {
    Integer age = new Integer(19);
    Integer age2 = new Integer(19);

    Supplier<Integer> ageSupplier = Suppliers.ofInstance(age);

    assertThat(ageSupplier.get() == age).isTrue();
    assertThat(ageSupplier.get() == age2).isFalse();
  }

  @Test
  public void should_correctly_wrap_supplier_to_a_function() {
    Function<Supplier<Integer>, Integer> supplierFunction =  Suppliers.supplierFunction();
    List<Supplier<Integer>> suppliers = Lists.newArrayList(() -> 10,
      () -> 20, () -> 30);

    List<Integer> transformedFromSuppliers = FluentIterable.from(suppliers)
      .transform(supplierFunction).toList();

    assertThat(transformedFromSuppliers).hasSize(3);
    assertThat(transformedFromSuppliers).containsOnly(10, 20, 30);
  }

}

This post shows that CacheLoader is not the single solution generating cacheable objects in Guava. A very interesting alternative are Supplier implementations, defined inside Suppliers final class. As we can see, simple caching not needing a key-value pairs, can be easily achieved by creating appropriate Supplier and passing it into one of 2 memoize* methods. In additional, Suppliers provides other interesting Supplier which can either be used in iterables as Function or composed with a Function.

If you liked it, you should read:

The comments are moderated. I publish them when I answer, so don't worry if you don't see yours immediately :)

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!