Introduction to Google Guava

External libraries are always helpful. They can help to resolve many "hot" programming problems, but also learn us the good rules of coding. One of this type of libraries in Java world is Google Guava.

This article will introduce us into Google Guava world. The first part will describe the main idea of this library and in which situations it can be used. The next part will present two key concepts implemented in Google Guava: avoiding nulls and fail fast approach.

What is Google Guava ?

As we can read in the Google Guava code page, this is a library that contains the core libraries used in Google's Java-based projects.

The project is maintained by Google's programmers and, according to official source, is written to be used directly without any optimization or test effort. Released in 2009, Google Guava is actually in its 17 version and it covers different subjects, as:
- cache
- collections
- concurrency
- input/output operations
- hashing
- math operations
- primitive types
- ranges
- reflection
- Strings

Google Guava avoids nulls to fail fast

One of key philosophy concepts implemented in Google Guava is the avoiding the operations with nulls. Google Guava code page explains that null is confusing value and we can't really know if it represents success or failure state, or if its presence is justified or not... For example when Map.get(key) returns null, we, as library users, can't know if this null represents entry not set in the Map or entry set but with null value.

You can take a simple look on some of Google Guava classes to learn that it'll be hard to introduce null values. For example, open the code source of com.google.common.collect.ImmutableMultimap class and analyze putAll method:

/**
 * Stores a collection of values with the same key in the built multimap.
 *
 * @throws NullPointerException if {@code key}, {@code values}, or any
 *     element in {@code values} is null. The builder is left in an invalid
 *     state.
 */
public Builder<K, V> putAll(K key, Iterable<? extends V> values) {
  if (key == null) {
	throw new NullPointerException(
		"null key in entry: null=" + Iterables.toString(values));
  }
  Collection<V> valueList = builderMultimap.get(key);
  for (V value : values) {
	checkEntryNotNull(key, value);
	valueList.add(value);
  }
  return this;
}

As you can see, if you try to add entry with null as a key, a NullPointerException will be thrown. Let's confirm that with simple JUnit test case:

@Test
public void testNullAvoiding() {
	ImmutableMultimap<String, String> notNullableMap = null;
	try {
		notNullableMap = ImmutableMultimap.<String, String>builder().put("Not_null_key", "OK").put(null, "Null key's value").build();
		fail("ImmutableMultimap shouldn't be created with null key");
	} catch (Exception e) {
		assertTrue("Exception should be NullPointerException but is "+e.getClass(), e.getClass() == NullPointerException.class);
	}
	assertTrue("notNullableMap should be null but is "+notNullableMap, notNullableMap == null);
}

This test will pass, ie. NullPointerException will be thrown at map construction. This case illustrates also another philosophic concept of Google Guava - fail-fast. This approach blocks code execution as soon as possible when given object is considered as invalid. We saw that with ImmutableMultimap which doesn't construct a map when one key is null.

We can see this approach also with the part responsible for math operations, and more precisely, rounding. This part can throw ArithmeticException when mandatory rounding strategy must be specified and it's not. By doing so, the code is executed only until the moment when working data is correct. Otherwise, the program could fail later in the code and execute some of supplementary operations (for example: operations consuming a lot of memory which could be penalizing for whole system). Take a look at given method to note that:

// com.google.common.math.MathPreconditions
static void checkRoundingUnnecessary(boolean condition) {
	if (!condition) {
	  throw new ArithmeticException("mode was UNNECESSARY, but rounding was necessary");
	}
}

// com.google.common.math.IntMath
@SuppressWarnings("fallthrough")
// TODO(kevinb): remove after this warning is disabled globally
public static int log2(int x, RoundingMode mode) {
	checkPositive("x", x);
	switch (mode) {
	  case UNNECESSARY:
		checkRoundingUnnecessary(isPowerOfTwo(x));
		// fall through
	  case DOWN:
	  case FLOOR:
		return (Integer.SIZE - 1) - Integer.numberOfLeadingZeros(x);

	  case UP:
	  case CEILING:
		return Integer.SIZE - Integer.numberOfLeadingZeros(x - 1);

	  case HALF_DOWN:
	  case HALF_UP:
	  case HALF_EVEN:
		// Since sqrt(2) is irrational, log2(x) - logFloor cannot be exactly 0.5
		int leadingZeros = Integer.numberOfLeadingZeros(x);
		int cmp = MAX_POWER_OF_SQRT2_UNSIGNED >>> leadingZeros;
		  // floor(2^(logFloor + 0.5))
		int logFloor = (Integer.SIZE - 1) - leadingZeros;
		return logFloor + lessThanBranchFree(cmp, x);

	  default:
		throw new AssertionError();
	}
}

In this situation, the log2 throws an exception if, for example, we try to return base-2 logarithm for a number which aren't the power of 2 and which hasn't rounding mode specified. Below test case illustrates that:

@Test
public void testFailFastApproach() {
	try {
		int logarithm = IntMath.log2(3, RoundingMode.UNNECESSARY);
		fail("We can't get the logarithm of 2 with unnecessary rounding mode");
	} catch (Exception e) {
		assertTrue("Exception should be ArithmeticException but was "+e.getClass(), e.getClass() == ArithmeticException.class);
	}
}

Another way to implement fail-fast approach are preconditions. They are static methods that can be imported from com.google.common.base.Preconditions class. They wrap control operations as not null checking, and launch the exceptions if tested condition isn't true. An example of precondition is checkArgument method which verifies if expression passed in parameter is true:

public static void checkArgument(boolean expression) {
	if (!expression) {
		throw new IllegalArgumentException();
	}
}

An example of use of this precondition could be:

@Test
public void testPreconditionFail() {
	try {
		String name = null;
		checkArgument(name != null, "Name can't be null");
		fail("Precondition should throw an IllegaArgumentException here");
	} catch (Exception e) {
		assertTrue("Exception should be IllegalArgumentException but was "+e.getClass(), e.getClass() == IllegalArgumentException.class);
	}
}

This article presents Google Guava. We learned that the project is maintained and daily used by Google. After that, we discovered two main ideas implemented in the project: avoiding null and fail-fast approach. The first one favors the work with not null objects while the second one guarantees that the code fails since some of objects is in invalid state.


If you liked it, you should read:

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