Clean Code with Google Guava

Probably, any programmer has ever seen a code full of lots of repetitions and the implementation of "low-level" actions right in the middle of business logic. For example, in the middle of a method that prints a report, there may be such a piece of code concatenating strings:

StringBuilder sb = new StringBuilder();
for (Iterator i = debtors.iterator(); i.hasNext();) {
  if (sb.length() != 0) {
    sb.append(", ");
  }
  sb.append(i.next());
}
out.println("Debtors: " + sb.toString());

It is clear that this code could be more straightforward, for example, in Java 8 you can write this:

out.println("Debtors: " + String.join(", ", debtors));

So right away it’s much clearer what is happening. Google Guava is a set of open-source libraries for Java that helps get rid of these common code patterns. Since Guava appeared long before Java 8, Guava also has a way to concatenate strings: Joiner.on (",") .join (debtors).

Very basic utilities


Let's look at a simple class that implements a standard set of basic Java methods. I suggest not particularly delving into the implementation of the hashCode, equals, toString and compareTo methods (the first three of them I just generated in Eclipse) so as not to waste time, but just look at the amount of code.

class Person implements Comparable {
  private String lastName;
  private String middleName;
  private String firstName;
  private int zipCode;
  // constructor, getters and setters are omitted
  @Override
  public int compareTo(Person other) {
    int cmp = lastName.compareTo(other.lastName);
    if (cmp != 0) {
      return cmp;
    }
    cmp = middleName.compareTo(other.middleName);
    if (cmp != 0) {
      return cmp;
    }
    cmp = firstName.compareTo(other.firstName);
    if (cmp != 0) {
      return cmp;
    }
    return Integer.compare(zipCode, other.zipCode);
  }
  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    Person other = (Person) obj;
    if (firstName == null) {
      if (other.firstName != null)
        return false;
    } else if (!firstName.equals(other.firstName))
      return false;
    if (lastName == null) {
      if (other.lastName != null)
        return false;
    } else if (!lastName.equals(other.lastName))
      return false;
    if (middleName == null) {
      if (other.middleName != null)
        return false;
    } else if (!middleName.equals(other.middleName))
      return false;
    if (zipCode != other.zipCode)
      return false;
    return true;
  }
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((firstName == null) ? 0 : firstName.hashCode());
    result = prime * result + ((lastName == null) ? 0 : lastName.hashCode());
    result = prime * result + ((middleName == null) ? 0 : middleName.hashCode());
    result = prime * result + zipCode;
    return result;
  }
  @Override
  public String toString() {
    return "Person [lastName=" + lastName + ", middleName=" + middleName
        + ", firstName=" + firstName + ", zipCode=" + zipCode + "]";
  }
}

Now let's look at similar code using Guava and new methods from Java 8:

class Person implements Comparable {
  private String lastName;
  private String middleName;
  private String firstName;
  private int zipCode;
  // constructor, getters and setters are omitted
  @Override
  public int compareTo(Person other) {
    return ComparisonChain.start()
        .compare(lastName, other.lastName)
        .compare(firstName, other.firstName)
        .compare(middleName, other.middleName, Ordering.natural().nullsLast())
        .compare(zipCode, other.zipCode)
        .result();
  }
  @Override
  public boolean equals(Object obj) {
    if (obj == null || getClass() != obj.getClass()) {
      return false;
    }
    Person other = (Person) obj;
    return Objects.equals(lastName, other.lastName)
        && Objects.equals(middleName, other.middleName)
        && Objects.equals(firstName, other.firstName)
        && zipCode == other.zipCode;
  }
  @Override
  public int hashCode() {
    return Objects.hash(lastName, middleName, firstName, zipCode);
  }
  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
        .omitNullValues()
        .add("lastName", lastName)
        .add("middleName", middleName)
        .add("firstName", firstName)
        .add("zipCode", zipCode)
        .toString();
  }
}

As you can see, the code has become cleaner and more concise. It uses MoreObjects and ComparisonChain from Guava and the Objects class from Java 8. If you are using Java 7 or an older version, you can use the Objects class from Guava - it has hashCode and equal methods similar to the hash and equals methods from the java class. lang.Objects. Previously, toStringHelper was also in the Objects class, but with the advent of Java 8 in Guava 18, the @Deprecated label was hung on the Objects class on all methods, and those methods that have no analogues in Java 8 were transferred to MoreObjects so that there was no name conflict - Guava develops, and its developers are not shy about getting rid of obsolete code.

I note that this version of the class is slightly different from the original: I suggested that the middle name may not be filled, in this case we will not see it as a result of toString, and compareTo will assume that individuals without a middle name should go after those who have a middle name (in this case, ordering occurs first by last name and first name, and only then by middle name).

Another example of very basic utilities is preconditions. For some reason, in Java there is only Objects.requireNotNull (since Java 7).

Briefly about the preconditions:

Method name in the Preconditions classThrown exception
checkArgument (boolean)IllegalArgumentException
checkNotNull (T)Nullpointerinterception
checkState (boolean)IllegalStateException
checkElementIndex (int index, int size)IndexOutOfBoundException
checkPositionIndex (int index, int size)IndexOutOfBoundException

Why they are needed can be found on the Oracle website .

New Collections


It often happens that you can see this kind of code in the middle of business logic:

Map counts = new HashMap<>();
for (String word : words) {
  Integer count = counts.get(word);
  if (count == null) {
    counts.put(word, 1);
  } else {
    counts.put(word, count + 1);
  }
}

Or a code like this:
List values = map.get(key);
if (values == null) {
  values = new ArrayList<>();
  map.put(key, values);
}
values.add(value);

(in the last passage, the input is map, key, and value). These two examples demonstrate working with collections when collections contain modified data (in this case, numbers and lists, respectively). In the first case, the map (map) essentially describes a multiset, i.e. a set with repeating elements, and in the second case, the display is a multi-display. Such abstractions are in Guava. Let's rewrite the examples using these abstractions:

Multiset counts = HashMultiset.create();
for (String word : words) {
  counts.add(word);
}

and
map.put(key, value);

(here map is Multimap) I note that Guava allows you to customize the behavior of such multi-mappings - for example, we can want value sets to be stored as sets, or we may want lists, for the very mapping we might want a linked list, hash or tree - all the necessary implementations in Guava are available. Table - a collection that eliminates duplicate code in a similar way, but already in case of storing mappings inside mappings. Here are examples of new life-saving collections:

MultisetA “set” that may have duplicates
Multimap“Display” that may have duplicates
BimapSupports “reverse mapping”
TableAssociates an ordered key pair with a value
ClassToInstanceMapMaps a type to an instance of this type (eliminates type casts)
RangesetRange set
RangemapA set of mappings of disjoint ranges to nonzero values


Collection Decorators


To create decorators for collections - both those that are already in the Java Collections Framework and those that are defined in Guava - there are corresponding classes, for example ForwardingList, ForwardingMap, ForwardingMiltiset.

Immutable Collections


Guava also has immutable collections; they may not be directly related to clean code, but they greatly simplify debugging and interaction between different parts of the application. They are:
  • safe for use in “unfriendly code”;
  • can save time and memory, because they do not focus on the possibility of change ( analysis showed that all non-unknown collections are more effective than their counterparts);
  • can be used as constants, and it can be expected that they will definitely not be changed.

There are positive differences compared to the Collections.unmodifiable Methods of the Specific Collection that create the wrappers, so you can expect the collection to be unchanged only if there are no more links to it; the collection leaves overhead for the ability to change both in speed and memory.

A couple of simple examples:

public static final ImmutableSet COLOR_NAMES = ImmutableSet.of(
  "red",
  "green",
  "blue");
class Foo {
  final ImmutableSet bars;
  Foo(Set bars) {
    this.bars = ImmutableSet.copyOf(bars); // defensive copy!
  }
}


Iterator Implementation


PeekingIteratorIt simply wraps the iterator, adding the peek () method to it to get the value of the next element. Created by calling Iterators.peekingIterator (Iterator)
AbstractIteratorEliminates the need to implement all iterator methods - just implement protected T computeNext ()
AbstractSequentialIteratorSimilar to the previous one, but calculates the next element based on the previous one: you need to implement the protected T computeNext (T previous) method


Functional and utilities for collections


Guava provides interfaces such as Functionand Predicate, and utility classes Functions, Predicates, FluentIterable, Iterables, Lists, Sets and others. I remind you that Guava appeared long before Java 8, and therefore Optional and Function interfaces inevitably appeared in itand Predicate, which, however, are useful only in limited cases, because without lambdas a functional code with predicates and functions will in most cases be much more cumbersome than a regular imperative, but in some cases it allows you to maintain conciseness. A simple example:

Predicate nonDefault = not(equalTo(DEFAULT_VALUE));
Iterable strings1 = transform(filter(iterable, nonDefault), toStringFunction());
Iterable strings2 = from(iterable).filter(nonDefault).transform(toStringFunction());

Static methods from Functions (toStringFunction), Predicates (not, equalTo), Iterables (transform, filter) and FluentIterable (from) are imported here. In the first case, static Iterable methods are used to construct the result, in the second - FluentIterable.

Input Output


Abstract classes such as ByteSource, ByteSink, CharSoure, and CharSink are defined to abstract byte and character streams. They are usually created using the facades Resources and Files. There is also a considerable set of methods for working with input and output streams, such as conversion, reading, copying, and concatenation (see the CharSource, ByteSource, ByteSink classes). Examples:

// Read the lines of a UTF-8 text file
ImmutableList lines = Files.asCharSource(file, Charsets.UTF_8).readLines();
// Count distinct word occurrences in a file
Multiset wordOccurrences = HashMultiset.create(
  Splitter.on(CharMatcher.WHITESPACE)
    .trimResults()
    .omitEmptyStrings()
    .split(Files.asCharSource(file, Charsets.UTF_8).read()));
// SHA-1 a file
HashCode hash = Files.asByteSource(file).hash(Hashing.sha1());
// Copy the data from a URL to a file
Resources.asByteSource(url).copyTo(Files.asByteSink(file));


Little by little


ListsCreation of various types of lists, including Lists.newCopyOnWriteArrayList (iterable), Lists.reverse (list) / * view! /, Lists.transform (fromList, function) / * lazy view! * /
SetsConvert from Map in Set (view!), work with sets in a mathematical sense (intersection, union, difference)
IterablesSimple methods like any, all, contains, concat, filter, find, limit, isEmpty, size, toArray, transform. For some reason, in Java 8, many of these methods apply only to collections, but not to Iterable in general.
Bytes, Ints, UnsignedInteger etc.Work with unsigned numbers and arrays of primitive types (there are corresponding utility classes for each primitive type).
ObjectArraysIn fact, there are only two types of methods - concatenation of arrays (for some reason it is not in the standard Java library) and creating masses for a given class or class of an array (for some reason, the Java library has only a similar method for copying).
Joiner, SplitterFlexible classes for combining or narezirovany strings from or to Iterable, List or Map.
Strings, MoreObjectsOf the non-mentioned, the most commonly used methods are Strings.emptyToNull (String), Strings.isNullOrEmpty (String), Strings.nullToEmpty (String) and MoreObjects.firstNonNull (T, T)
Closer, ThrowablesEmulate try-with-resources, multi-catch (useful only for Java 6 and older), work with stack tracing and throwing exceptions.
com.google.common.netThe class names speak for themselves: InternetDomainName, InetAddresses, HttpHeaders, MediaType, UrlEscapers
com.google.common.html and com.google.common.xmlHtmlEscapers and XmlEscapers
RangeRange.
EventbusPowerful implementation of the publisher-subscriber pattern. Subscribers are registered in EventBus, the “responsive” methods of which are marked with annotation, and when an event is called, EventBus finds subscribers who are able to perceive this type of event and notifies them of the event.
IntMath, LongMath, BigIntegerMath, DoubleMathMany useful functions for working with numbers.
ClasspathIn Java, there is no cross-platform way to view classes on a classpath. And Guava provides an opportunity to walk through the classes of a package or project.
TypeTokenDue to type erasure, we cannot manipulate generic types during program execution. TypeToken allows you to manipulate these types.


More examples


Hashing:

HashFunction hf = Hashing.md5();
HashCode hc = hf.newHasher()
       .putLong(id)
       .putString(name, Charsets.UTF_8)
       .putObject(person, personFunnel)
       .hash();

Caching:
LoadingCache graphs = CacheBuilder.newBuilder()
       .maximumSize(1000)
       .expireAfterWrite(10, TimeUnit.MINUTES)
       .removalListener(MY_LISTENER)
       .build(
           new CacheLoader() {
             public Graph load(Key key) throws AnyException {
               return createExpensiveGraph(key);
             }
           });

Dynamic proxy:

Foo foo = Reflection.newProxy(Foo.class, invocationHandler)

To create a dynamic proxy without Guava, the following code is usually written:

Foo foo = (Foo) Proxy.newProxyInstance(
    Foo.class.getClassLoader(),
    new Class[] {Foo.class},
    invocationHandler);


That is all the code you are reading.

Also popular now: