Collection Utilities in Google Guava

There are many utility classes in Guava which can help achieve collections based operations quicker. Here is a glimpse of the major ones. Note that Convenience Factory Methods for Collections are now a part of Java 9, but these work great if you are still on older versions of Java.

  1. It has utility classes like Lists, Sets, Iterables, Iterators, FluentIterable, Maps, MultiMaps, MultiSets, ObjectArrays, Queues, Streams, Tables etc. which support various utility operations that can be performed on the collections like merging, splitting, converting to different collection type, checking if empty, reversing the sequence etc. as applicable for the type of collection.
  2. Lists
    Provides convenient constructors like newArrayList, newLinkedList etc.
    Provides charactersOf – a shortcut method to get characters of String into List<Character>
    Provides partition and reverse methods to split the List based on size and reverse the List contents. Also has a cartesianProduct implementation to operate between two Lists.
    Provides transform method to convert a List from one Generic Type to other based on a computation performed by a given Function .

    @Test
    public void testLists() {
        List<String> list = Lists.newArrayList("a", "b", "c");
        Assert.assertTrue(list instanceof ArrayList);
    
        System.out.println(Lists.reverse(list));
    
        System.out.println(Lists.transform(list, s -> s.toUpperCase()));
    
        System.out.println(Lists.charactersOf("hello"));
    
        System.out.println(Lists.partition(Lists.charactersOf("elephant"), 3));
    
        System.out.println(Lists.cartesianProduct(Lists.newArrayList("a", "b"), Lists.newArrayList("1", "2")));
    }
  3. Sets
    Like Lists, provides static constructors like newTreeSet, newHashSet etc.
    Provides union, intersection and difference methods to perform between two Set objects.
    Provides implementation for combinations (nCr) and powerSet (all possible subsets for a given set).
    Provides filter method similar to java.util.Stream.

    @Test
    public void testSets() {
        Set<String> set1 = Sets.newHashSet("a", "b", "c");
        Set<String> set2 = Sets.newHashSet("c", "d", "e");
    
        System.out.println(Sets.cartesianProduct(set1, set2));
        Sets.combinations(set1, 2).forEach(s -> System.out.print(s + "; "));
        System.out.println();
        System.out.println(Sets.difference(set1, set2));
        // a, c, e, g etc
        System.out.println(Sets.filter(Sets.union(set1, set2), v -> (v.toCharArray()[0] - 'a') % 2 == 0));
        System.out.println(Sets.union(set1, set2));
        System.out.println(Sets.intersection(set1, set2));
        Sets.powerSet(set1).forEach(s -> System.out.print(s + "; "));
    }
  4. Maps
    Provides static constructors like newHashMap, newTreeMap etc. Even can be constructed using fromProperties method.
    Implements asMap and toMap methods which can be used to generate a Map using a Set / Iterable and a Function to compute a value based on the key provided.
    Implements difference method which gives exhaustive result of difference between two maps with result indicating equality, common entries and different entries represented as com.google.common.collect.MapDifference object.
    Provides exhaustive filtering capabilities like filterKeys, filterEntries and filterValues.
    Provides exhaustive transforming capabilities with transformEntries and transformValues method.

    @Test
    public void testMaps() {
        Set<String> set1 = Sets.newHashSet("a", "b");
        Map<String, String> map1 = Maps.asMap(set1, s -> "A" + s);
        System.out.println(map1);
    
        Map<String, String> map2 = Maps.asMap(set1, s -> "B" + s);
        System.out.println(Maps.difference(map1, map2).entriesInCommon());
        System.out.println(Maps.difference(map1, map2).entriesDiffering());
    
        System.out.println(Maps.filterKeys(map1, s -> s.contains("a")));
    
    }
  5. Iterables
    Provides various utility methods for filtering, finding elements on an Iterable. Also has methods to perform merging, partitioning of different Iterables and many more utility methods.

Cache functionality on Google Guava

Google Guava has a precise support to implement minor caching needs of any Java application. Guava’s implementation supports caching on the local memory and thus it should be cautiously used keeping in mind a few limitations.

This cannot be used for requirements where the cache grows to a very high size as it might impact the application performance due to an overloaded memory consumption. This also does not have network based shared cache and thus cannot support if you require any clustered caching. If the application needs a simple caching implementation which does not grow to a high size and required only for quicker access without persisting / network sharing requirements, Guava is an easy choice due to its simplicity of implementation.

Guava supports two types of cache implementation based on how the data is loaded into the cache –

LoadingCache – where a Loader is provided upfront and as and when a request arrives to obtain some entry from cache, the appropriate value is computed, added into cache and served back as response. Further calls would be served faster as the value for a given key is already present in the cache. All required values can be computed upfront by invoking a call to getAll method which accepts an Iterable as the input.

@Test
public void testLoadingCache() throws ExecutionException {
    LoadingCache cache = CacheBuilder.newBuilder().build(new CacheLoader() {
        @Override
        public String load(String key) throws Exception {
            sleep(1000); // dummy delay
            return cacheMap.get(key);
        }
    });

    Stopwatch stopwatch = Stopwatch.createStarted();
    assertEquals("one", cache.get("1"));
    stopwatch.stop();
    assertTrue(stopwatch.elapsed(MILLISECONDS) > 1000);

    stopwatch.reset();
    stopwatch.start();
    assertEquals("one", cache.get("1"));
    stopwatch.stop();
    assertTrue(stopwatch.elapsed(MILLISECONDS) < 100);
}

Cache – where the value for a given key is explicitly added. Initialization returns an empty Cache object which can be used to store the key-value pairs that would be accessed in the future.

@Test
public void testCache() throws ExecutionException {
    Cache cache = CacheBuilder.newBuilder().build();
    cache.putAll(cacheMap);

    Stopwatch stopwatch = Stopwatch.createStarted();
    assertEquals("one", cache.getIfPresent("1"));
    stopwatch.stop();
    assertTrue(stopwatch.elapsed(MILLISECONDS) < 100); stopwatch.reset(); stopwatch.start(); assertEquals("four", cache.get("4", () -> "four"));// add to cache and get
    stopwatch.stop();
    long t1 = stopwatch.elapsed(MILLISECONDS);

    stopwatch.reset();
    stopwatch.start();
    assertEquals("four", cache.get("4", () -> "four"));// already in cache, just get
    stopwatch.stop();
    long t2 = stopwatch.elapsed(MILLISECONDS);
    assertTrue(t1 > t2);
}

Any existing entry can be retrieved using getIfPresent method with the key as input. Both the implementations support a get method which accepts a key and a Callable implementation which can be invoked if the key does not exist in the cache.

Cache entries can be evicted explicitly by invoking invalidate method or also by setting few policies for automatic eviction based on cache size or time duration after write /access. Note that the eviction based on policies does not happen at the exact expiry of the key, but it happens along with simultaneous read or write operations. This ensures a dedicated thread is not consumed just to housekeep the cache entries, if required, an explicit cleanUp method can be called to clear expired entries.

// time based
Cache<String, String> cache = CacheBuilder.newBuilder().expireAfterWrite(1, SECONDS).build();
cache = CacheBuilder.newBuilder().expireAfterAccess(1, SECONDS).build();

// max size
cache = CacheBuilder.newBuilder().maximumSize(5).build();

//invalidation
cache.invalidate("1");

Caching framework also supports registering Removal Listeners and stats for advanced usage to perform any post processing or to run analytics.