Core Java

Google Guava Libraries Essentials

I want code to be simple-n-short, on-point and easy to read. Unnecessary complexity distract and obscure understanding of what is really going on and can be a real killer for productivity.

You know, tangled for-loops and indexes to track, if/else and switch cases, null/validation checks, converting/copying/deleting/sorting collections, exception handling … the list goes on along with ever-increasing line numbers and maintenance burden.

An excellent quote by Tony Hoare comes to mind.

There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies.

In other words: The Devil is in the details.

Apache Commons have some of most wonderful libraries complementing the JDK APIs, but this post is not about Commons. It is about Google Guava which is similar to Commons in many regards. It provide a single library for commonly used day-to-day tasks, such as collection handling, string manipulation, concurrency, IO, primitives, exceptions etc.

There is so much nice stuff in Guava and I wont have time to go through the complete library, but here at least some examples of what it can do.

Objects
Objects makes it easy to implement hashcode/equals without cluttering your classes too much (Eclipse auto-generation tends to be a bit verbose for my taste).

Classes that implement toString are really pleasant to use when doing debugging and logging, but can be a real pain implement. Objects.toStringHelper makes this really easy and also help maintaining a consistent format for printing objects.

public class Item {
  private String id;
  private String name;
 
  public Item(String id, String name) {
    this.id = id;
    this.id = name;
  }
 
  public String getId() {
    return id;
  }
 
  public String getName() {
    return name;
  }
 
  @Override
  public int hashCode() {
    return Objects.hashCode(getId(), getName());
  }
 
  @Override
  public String toString() {
    return Objects.toStringHelper(this).add("id", getId()).add("name", getName()).toString();
  }
 
  @Override
  public boolean equals(Object o) {
    if (!(o instanceof Item)) {
      return false;
    }
    Item other = (Item) o;
    return Objects.equal(getId(), other.getId()) && Objects.equal(getName(), other.getName());
  }
}

Printing this class outputs something like this.

Item{id=1, name=Book}

Throwables
Wrapping the original exception object is not always appropriate, because it can cause ClassNotFoundException in the client code if communication occur between unrelated class loaders or if they are serialized on the wire. Throwables can decouple this dependency, still allowing remote clients to see the stack trace by converting it to a string.

try {
  // throws implementation specific exception
} catch (InternalException e) {
  throw new ApiException("reason", Throwables.getStackTraceAsString(e));
}

Iterables
Concatenating two separate collections and performing operations on the result can cause a quite a lot of clutter. Iterables to the rescue. Take a minute and think how code might look without Iterables.concat.

for (Item item : Iterables.concat(books, electronics)) {
  // do something useful
}

Multimaps
Multimap is like a Map, but allow multiple values to be stored for every key. The following example is a a variant of a typesafe hetereogeneous container using multimap to realize a product catalogue of items.

public class ProductCatalogue {
  private Multimap<Class,? extends Item>, Item> catalogue = ArrayListMultimap.create();
 
  public void add(Item item) {
    catalogue.put(item.getClass(), item);
  }
 
  public <T extends Item> Collection<Item> list(Class<T> clazz) {
    return catalogue.get(clazz);
  }
}
 
ProductCatalogue catalogue = new ProductCatalogue();
catalogue.add(new Book("1", "Book1"));
catalogue.add(new Movie("2", "Movie1"));
// only get books
System.out.println("Books " + catalogue.list(Book.class));
// only get movies
System.out.println("Movies " + catalogue.list(Movie.class));

BiMap
BiMap implement a one-to-one bidirectional relationship between key and value of the Map. Here is an example using language code to get the language and vice versa.

BiMap<String, String> languageCodes = HashBiMap.create();
languageCodes.put("en", "English");
languageCodes.put("fr", "French");
languageCodes.put("zh", "Chinese");
assert "English" == languageCodes.get("en");
assert "en" == languageCodes.inverse().get("English");

Preconditions
Most classes have restrictions on values given them in constructor and methods. Invalid values should be escalated as soon as possible by doing explicit validity checks before execution. It is a lot better to fail-fast than to fail later with an unexpected exception or worse, silently compute the wrong result.

public Item(String id, String name) {
  this.id = Preconditions.checkNotNull(id, "id must not be null");
  this.name = Preconditions.checkNotNull(name, "name must not be null");
  Preconditions.checkArgument(name.length() > 6, "name must be longer than 6 chars");
}

Constraints
Constraints are similar to preconditions in a way that they can restrict what values are added to a collection. This makes collections much easier to use and code a lot cleaner, since constraints are separated from business code.

public class Voyage {
  private Country targetcountry;
  private int capacity;
  private List<Cargo> items = Constraints.constrainedList(new ArrayList<Cargo>(), new Constraint<Cargo>() {
    @Override
    public Cargo checkElement(Cargo cargo) {
      Preconditions.checkNotNull(cargo);
      Preconditions.checkArgument(targetcountry.allows(cargo));
      Preconditions.checkArgument(cargo.getUnits() gt; 0);
      return cargo;
    }
  });
 
  public void load(List<Cargo> cargos) {
    items.addAll(cargos);
  }
}

Predicates and Functions
Predicates evaluate if something is true or false but can also be combined into more complex evaluations using “and”, “or”, “not” and “in”.

What normally would require a for-loop and bunch of if statements can now be reduced to a one-liner. How sweet is that?

Predicate<Item> heavyItemPolicy = new Predicate<Item>() {
  @Override
  public boolean apply(Item item) {
    if(item.getWeight() > 1000){
      return true;
    }
    return false;
  }
};
Collection<Item> heavyItems = Collections2.filter(order, heavyItemPolicy);

You can also use Maps.filterKeys or Iterables.filter in a similar way. But keep in mind that removal from modification is bidirectional. e.g. removal from the origin affect result and vice versa.

Functions on the other hand, is a way of transforming one object to another. For example, convert concurrency on a order of items.

Function currencyConverter = new Function<Double, Item>() {
  @Override
  public Double apply(Item item) {
   return item.getPrice() * ANOTHER_CURRENCY;
  }
}
Collection<Double> prices = Collections2.transform(order, currencyConverter);

You can also use Maps.transformValues or Iterables.transform in a similar way.

A Query API
I have been think about how to create simple but powerful Fake Objects for some time now. But I dont want fakes themselves to turn into a maintenance burden, so they must be easy to implement. My intuition tells me i need a general purpose state management framework for this to work. And so using predicates, I created a small fluent query interface interacting with an in memory storage.

InMemoryStorage storage = new InMemoryStorage();
// add a few Item.class objects to storage
Criteria middleprice = field("price").is(largerThan(100)).and(lessThan(200));
Criteria expired = field("expires").is(after(currentDate));
Collection<Item> result = storage.select(middleprice.and(not(expired))).from(Item.class);

I feel quite satisfied with the result actually – short, compact, understandable and typesafe.

Im not going to go into the details here, but please do inspect the implementation of Criteria and InMemoryStorage, as well as the tests.

I hope these examples will trigger you to explore Guava further and use it to make your code more readable, robust and maintainable.

And lastly, I really do hope many of these facilities reach standard Java some day soon.

Reference: The Devil is in the details from our JCG partner Kristoffer Sjögren at the Deep Hacks blog.

Related Articles :

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
rami outa
rami outa
12 years ago

Hello,
I need to create a java library for my project. I am creating a small language and I want to write this language in the java code normally so I have to import a library that I will create in order to do so. Can anyone help me in creating the library?

Thanks
my email is rami.outa@gmail.com

Back to top button