The Guava library, for me, is hands down the best Java utility library out there. It is a shining example of clean code and API design, both in its implementation and documentation. If you’re not already using it in your Java projects – you absolutely should be! Check out the wiki here to get started.
In this series I want to highlight different pieces of Guava that I have found particularly useful. Often there’s a Guava utility that fits your need perfectly, so becoming familiar with the different features of the library can be extremely helpful!
Preconditions
By definition, a precondition is "a condition that must be fulfilled before other things can happen or be done". Software developers are intimately familiar with this concept, knowing that defining and checking preconditions can help in detecting errors as soon as possible and ultimately preventing bugs (EJ 49).
Most methods and constructors will have restrictions on their parameters, but enforcing those restrictions can be tedious and quickly lead to excessive boilerplate code. Consider a constructor with two object reference parameters that are required to be non-null:
public final class Person
{
private final String name;
private final LocalDate birthday;
public Person(String name, LocalDate birthday)
{
if (name == null)
{
throw new NullPointerException("Name cannot be null");
}
if (birthday == null)
{
throw new NullPointerException("Birthday cannot be null");
}
this.name = name;
this.birthday = birthday;
}
}
To me, it feels like a lot of code for such a simple setup. This approach certainly works, but it will clutter your code considerably as you add more parameters or validity checks.
The static utilities in the Preconditions class are designed to cut down on this type of clutter and make your life easier. Let’s take a look at the available methods.
checkNotNull
The most common method from Preconditions that I use is checkNotNull
. Exactly as it states, this method will verify that the value you give it is not null. It will also return the value directly so you can inline your calls!
Using our previous example, here’s what it would look like using Preconditions
:
import static com.google.common.base.Preconditions.checkNotNull;
public final class Person
{
private final String name;
private final LocalDate birthday;
public Person(String name, LocalDate birthday)
{
this.name = checkNotNull(name);
this.birthday = checkNotNull(birthday);
}
}
You still have the null checks you need, but the boilerplate is gone! Under the hood, checkNotNull
is doing the exact same thing we were previously doing manually:
public static <T extends @NonNull Object> T checkNotNull(T reference) {
if (reference == null) {
throw new NullPointerException();
}
return reference;
}
Having it wrapped up in a well-named method that you can statically import is incredibly convenient.
Let’s say you still want to have helpful error messages with your NullPointerException
s. Guava has you covered with an overload!
public Person(String name, LocalDate birthday)
{
this.name = checkNotNull(name, "Name cannot be null");
this.birthday = checkNotNull(birthday, "Birthday cannot be null");
}
Just as smooth and slick as before.
But what about Objects.requireNonNull()?!?
Yes, since Java 7 the JDK does provide us an almost identical method in Objects.requireNotNull(). So why use one over the other? I recommend the Guava version for two reasons:
- Consistency with usage of other
Preconditions
methods in your code. - Available overload with printf-style exception messages (discussed later).
checkArgument
For verifying more general argument constraints, there’s the checkArgument
method. Imagine a setter method in our Person
class that can only accept values of zero or greater:
/**
* Sets the age of this person.
*
* @param age the new age in years
* @throws IllegalArgumentException if age is negative
*/
public void setAge(int age)
{
if (age < 0)
{
throw new IllegalArgumentException("Age cannot be negative: " + age);
}
this.age = age;
}
While we can’t inline our calls to checkArgument
like we could with checkNotNull
, it can still simplify our code while maintaining the functionality we desire:
public void setAge(int age)
{
checkArgument(age >= 0, "Age cannot be negative: %s", age);
this.age = age;
}
There are a few things to note about this example:
- The first argument to
checkArgument
, the condition, should express the condition that should be true. This is the inverse of the condition in our previousif
statement. I find that writing the conditions forcheckArgument
tends to result in code that is easier to read and interpret. - All
Preconditions
methods have printf-style overloads for their error message creation. Anything after the second argument is treated as a variable to be printed using the formatting in the error message. Note that only%s
is supported for efficiency instead of the full range ofFormatter
specifiers.
checkState
As you might imagine, checkState
behaves much like checkArgument
except that it throws an IllegalStateException
instead of an IllegalArgumentException
. Any time you need to verify the state or capabilities of some object before allowing an action to be taken, the checkState
method will provide exactly what you need.
Others
There are additional methods in the Preconditions
class that deal with indices: checkElementIndex
, checkPositionIndex
, and checkPositionIndexes
. I rarely end up using these methods, but you can read more about them in the wiki or the Javadocs.
Final Notes
The Preconditions
class has to be one of the most useful APIs in the entire Guava library. After static imports, the methods are clearly named and indicate which exception will be thrown if the condition fails. They can cut down on boilerplate validation checking in your code or perhaps provide a quick and easy way to beef up your error checking if you have some holes in your precondition verification.
One thing to note about all these methods is that they throw unchecked runtime exceptions if they fail. As such, only use Preconditions
methods to detect programming errors like null values. If clients can and should realistically recover from the failure, you’d want to use a checked exception type and avoid the Preconditions
class.
The Preconditions
class also doesn’t cover every type of precondition failure imaginable. Methods that throw exceptions like UnsupportedOperationException
or NoSuchElementException
should not be migrated just for the sake of using the Preconditions
utilities.
For further reading on when to use the Preconditions
class, see the Conditional Failures Explained article in the Guava User Guide.