String Utility Operations On Google Guava

Google Guava library supports various String Operations to speed up common operations in day to day programming. Below is a list that can help.

ASCII Operationscom.google.common.base.Ascii

  • ASCII constants including various types of white spaces like carriage return, new line, space etc, Signals like escape, delete etc, other communication signals and more.
  • equalsIgnoreCase – Much faster than implementation in java.lang.String. Works well for ASCII strings. Read the documentation and analyze your use case before using this.
  • Other common String operations like toUpperCase, toLowerCase, truncate etc.

Continue reading “String Utility Operations On Google Guava”

Common Utilities Classes and Methods In Google Guava

Google Guava library has multiple common utility methods that can be used in various applications. Below is a glimpse of the same.

com.google.common.base.Preconditions has various methods like checkArgument and checkState which are used to check the truth evaluation of the boolean parameter passed. It also has checkNotNull which evaluates whether the parameter passed is a non-null value and throws NullPointerException if it is null.

com.google.common.base.StandardSystemProperty has a list of common system environment properties defined.

com.google.common.base.Stopwatch can be used to measure the time difference between a set of operations. This can be consumed in simple ways by initiating it started and stopping the same once done and obtain the elapsed duration in a well formed readable format or converted into a desired unit for computational purposes. This simplifies the performance logging operation.

Stopwatch watch = Stopwatch.createStarted();
Thread.sleep(1000);
watch.stop();
System.out.println("Elapsed: " + watch); // prints 1.001 s
System.out.println("Elapsed: " + watch.elapsed(MILLISECONDS)); // prints 1000
System.out.println("Elapsed: " + watch.elapsed(MICROSECONDS)); // prints 1000500

Stopwatch can be enhanced by providing a com.google.common.base.Ticker which is a source which can be used to tweak the behavior of Stopwatch as below.

Ticker t = new Ticker() {
    @Override
    public long read() {
        return System.nanoTime() * 2;//Speed up by two fold
    }
};
Stopwatch watch = Stopwatch.createStarted(t);
Thread.sleep(1000);
watch.stop();
System.out.println("Elapsed: " + watch.elapsed(MILLISECONDS)); // prints 2000

com.google.common.base.Throwables has some nice utility methods to get the StackTrace as String. Also it implements getRootCause which can be used to obtain the root cause of an exception which has been nested in wrappers.

Collection Enhancements in Google Guava

Guava library introduces many utility methods for collections as well as new types of collections to be used in various programming scenarios.

Immutable Collections:

Even though JDK provides immutable collections through Collections.unmodifiableList, unmodifiableSet etc, they are not very efficient as they go through all the operations which exist in a normal collection.

Initializing Immutables is done with of method where individual elements are passed as parameters or copyOf method where an Iterable can be passed as a parameter. It can even be initialized using a Builder where multiple elements can be added on the fly to build an Immutable collection.

ImmutableSet<String> set = ImmutableSet.of("a", "b", "c", "b");
ImmutableSet<String> set2 = ImmutableSet.copyOf(set);

ImmutableList<String> list = set.asList();

ImmutableMap<String, Integer> map = ImmutableMap.of("a", 1, "b", 2, "c", 3);

ImmutableList<String> list2 = ImmutableList.<String>builder().add("a", "b", "c").build();

New Collection Types:

  • MultiSet – This is a collection which¬† accepts the same value multiple times and stores it as the element against the number of occurrences it is being added to the collection. The number of occurrences for a given element can be obtained using the count() method.
Multiset<String> multiset = HashMultiset.create();
multiset.add("A");
multiset.add("A"); // accepted, count of "A"'s occurrence increases by 1.
multiset.count("A");// returns 2
  • MultiMap – Like MultiSet, this accepts multiple values for a single key. The data is stored as a single key with a List of values. A real life example of its application can be with HTTP headers where same header name can be associated with multiple values.
    Multimap<String, String> multimap = ArrayListMultimap.create();
    multimap.put("A", "one");
    multimap.put("A", "two");//accept, does not replace "one", stored as one more mapping.
    System.out.println(multimap.get("A")); //prints ["one", "two"]

    Caution should be exercised while using this as multimap.get() will never return a null, if the value does not exist, the return is an empty List to which a value can be added. In order to check existence of a key, it can be done in two ways.

    multimap.get("B").size==0;// key does not exist as no mapping exists
    multimap.asMap().get("B")==null; // asMap gives the equivalent representation of java.util.Map

keys() method can be used to get the keys as a MultiSet with key as the entry and number of values in the map representing the value of MultiSet.

multimap.keys().count("A"); // returns 2

entries() method returns each of the key-value pairs.

multimap.entries(); // returns A:1, A:2 as separate entries
multimap.asMap().entries(); // returns A:[1, 2] as single entry

clear() method can be used to evict a key from the MultiMap. As MultiMap does not allow an entry without values, the key is naturally out of the collection when all the values are cleared.

  • BiMapBiMap is a special collection which finds its use in multiple programming scenarios. BiMap accepts entries where both keys and values are restricted to be unique. With such unique association, it provides an inverse() method which can be invoked to have all the values as keys and keys as values, thus supporting a reverse lookup functionality. With this collection, it is always easy to maintain two way mapping, without having to manually maintain two separate maps which can lead to data inconsistency.
BiMap<String, Integer> biMap = HashBiMap.create();
biMap.put("a", "1");
biMap.get("a"); // returns "1"
biMap.inverse().get("1"); // returns "a"
  • TableTable is a collection which is used to maintain a tabular mapping of the information. With this, it is easy to maintain a mapping of data where a row and a column can contain a specific value. Real life examples would include a school time table, sudoku chart etc.
    Table<String, Integer, String> table = HashBasedTable.create();
    table.put("Monday", 1, "Language1");
    table.put("Monday", 2, "Maths");
    table.put("Tuesday", 1, "Science");
    
    System.out.println(table); //{Monday={1=Language1, 2=Maths}, Tuesday={1=Science}}
    System.out.println(table.row("Monday")); //{1=Language1, 2=Maths}
    System.out.println(table.column(1)); //{Monday=Language1, Tuesday=Science}
    System.out.println(table.columnKeySet()); // [1, 2]
    System.out.println(table.get("Monday", 2)); // Maths

    ClassToInstanceMap – This can be used in factories that provide singleton objects for a given class. A class instance can be initialized once and stored in this collection and be used readily. Primarily this is a Map, but it has getInstance and putInstance methods which can reduce type casting effort.

    ClassToInstanceMap instanceMap = MutableClassToInstanceMap.create();
    List<String> list = new ArrayList<>();
    instanceMap.putInstance(List.class, list);
    System.out.println(list == instanceMap.getInstance(List.class));

    Apart from these, few new collections related to numerical ranges exist, whose real life application seems to be limited.

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.