Unlocking Guava: How 7 Design Patterns Power Its Core
This article dissects the Guava library to reveal how seven classic design patterns—Builder, Proxy, Immutable, Singleton, Decorator, Adapter, and Observer—are implemented in its core classes, providing clear code examples, step‑by‑step construction processes, and practical insights for Java developers.
Builder Pattern (CacheBuilder)
Guava’s CacheBuilder implements the Builder pattern. It provides a fluent API to configure cache parameters such as initialCapacity, maximumSize, expireAfterWrite, and removalListener. Each setter returns this to allow method chaining. The build() method validates the configuration (e.g., concurrencyLevel limits) and creates a LocalCache instance.
public class CacheBuilder<K,V> {
private long expireAfterWriteNanos = -1;
private int initialCapacity = 16;
private long maximumSize = Long.MAX_VALUE;
private RemovalListener<? super K,? super V> removalListener;
// … other fields …
public CacheBuilder<K,V> initialCapacity(int initialCapacity) {
this.initialCapacity = initialCapacity;
return this;
}
public CacheBuilder<K,V> maximumSize(long maxSize) {
this.maximumSize = maxSize;
return this;
}
public CacheBuilder<K,V> expireAfterWrite(long duration, TimeUnit unit) {
this.expireAfterWriteNanos = unit.toNanos(duration);
return this;
}
public <K1 extends K, V1 extends V> Cache<K1,V1> build() {
if (concurrencyLevel > MAX_SEGMENTS) {
throw new IllegalArgumentException("concurrencyLevel cannot be greater than " + MAX_SEGMENTS);
}
return new LocalCache<K,V>(this);
}
}Proxy Pattern (ForwardingCollection)
The abstract class ForwardingCollection<E> acts as a proxy for a Collection. Subclasses implement the abstract delegate() method to return the wrapped collection. All Collection methods are forwarded to delegate(), allowing selective overriding.
public abstract class ForwardingCollection<E>
extends ForwardingObject implements Collection<E> {
protected abstract Collection<E> delegate();
@Override public boolean add(E e) { return delegate().add(e); }
@Override public boolean remove(Object o) { return delegate().remove(o); }
@Override public int size() { return delegate().size(); }
// … other methods …
}Immutable Pattern (ImmutableList, ImmutableSet, ImmutableMap)
Guava’s immutable collection classes guarantee that the contents cannot change after construction. They achieve this by:
Storing elements in final fields (e.g., an array for ImmutableList).
Providing only static factory methods ( of, copyOf) that create new instances.
Omitting mutating methods such as add, remove, clear, which throw UnsupportedOperationException.
Example of ImmutableList:
public final class ImmutableList<E>
extends ImmutableCollection<E> implements List<E> {
private final transient Object[] array;
private ImmutableList(Object[] array) { this.array = array; }
public static <E> ImmutableList<E> of() {
return new ImmutableList<E>(new Object[0]);
}
public static <E> ImmutableList<E> of(E... elements) {
return new ImmutableList<E>(copyOf(elements));
}
private static <E> Object[] copyOf(E[] elements) {
Object[] array = new Object[elements.length];
System.arraycopy(elements, 0, array, 0, elements.length);
return array;
}
@Override public E get(int index) { return (E) array[index]; }
// … other List methods without mutators …
}Example of ImmutableSet (simplified):
public abstract class ImmutableSet<E>
extends ImmutableCollection<E> implements Set<E> {
private final transient ImmutableMap<E, Boolean> map;
protected ImmutableSet(ImmutableMap<E, Boolean> map) { this.map = map; }
public static <E> ImmutableSet<E> of() {
return new RegularSet<>(ImmutableMap.of());
}
public static <E> ImmutableSet<E> of(E element) {
return new SingletonSet<>(element);
}
public static <E> ImmutableSet<E> copyOf(Collection<? extends E> elements) {
return new RegularSet<>(ImmutableMap.copyOf(elements));
}
@Override public boolean contains(Object o) { return map.containsKey(o); }
// mutating methods throw UnsupportedOperationException
}ImmutableMap follows the same principles, storing entries in a final array or map and exposing only read‑only operations.
public abstract class ImmutableMap<K,V> implements Map<K,V> {
private final transient Entry<K,V>[] entries;
protected ImmutableMap(Entry<K,V>[] entries) { this.entries = entries; }
public static <K,V> ImmutableMap<K,V> of() { return new RegularImmutableMap<>(EMPTY); }
public static <K,V> ImmutableMap<K,V> of(K k, V v) {
return new SingletonImmutableMap<>(k, v);
}
public static <K,V> ImmutableMap<K,V> copyOf(Map<? extends K,? extends V> map) {
return new RegularImmutableMap<>(copyEntries(map));
}
@Override public V put(K k, V v) {
throw new UnsupportedOperationException();
}
// read‑only methods such as get, entrySet, keySet, values are provided
}Singleton Pattern (MoreExecutors.listeningDecorator)
The utility class MoreExecutors has a private constructor and a static method listeningDecorator(ExecutorService). The method returns a singleton ListeningDecorator for a given executor, creating the decorator only once per executor.
public final class MoreExecutors {
private MoreExecutors() {}
public static ListeningExecutorService listeningDecorator(ExecutorService executor) {
if (executor instanceof ListeningExecutorService) {
return (ListeningExecutorService) executor;
}
return new ListeningDecorator(executor);
}
private static class ListeningDecorator extends AbstractListeningExecutorService {
// implementation details
}
}Decorator Pattern (ForwardingObject)
ForwardingObjectprovides a base decorator that forwards core Object methods ( equals, hashCode, toString) to a wrapped delegate.
public abstract class ForwardingObject extends Forwarding {
final Object delegate;
protected ForwardingObject(Object delegate) { this.delegate = delegate; }
@Override protected Object delegate() { return delegate; }
@Override public boolean equals(Object obj) { return delegate.equals(obj); }
@Override public int hashCode() { return delegate.hashCode(); }
@Override public String toString() { return delegate.toString(); }
// subclasses can add extra behavior
}Adapter Pattern (Forwarding)
The abstract class Forwarding defines an abstract delegate() method and supplies default forwarding implementations for equals, hashCode, and toString. Subclasses implement delegate() to return the adaptee and can expose any target interface.
public abstract class Forwarding {
protected abstract Object delegate();
@Override public boolean equals(Object obj) { return delegate().equals(obj); }
@Override public int hashCode() { return delegate().hashCode(); }
@Override public String toString() { return delegate().toString(); }
// subclasses add specific forwarding logic
}Observer Pattern (RemovalListener)
Guava’s cache eviction notifications use the observer pattern. Implementations of RemovalListener<K,V> receive a RemovalNotification<K,V> and can react (e.g., logging, cleanup) without modifying the cache core.
public class MyCustomMap<K,V> implements Map<K,V> {
private final Map<K,V> delegate;
private final RemovalListener<K,V> listener = new MyRemovalListener();
public MyCustomMap(Map<K,V> delegate) { this.delegate = delegate; }
@Override public V put(K key, V value) { return delegate.put(key, value); }
// … other Map methods delegated …
private class MyRemovalListener implements RemovalListener<K,V> {
@Override
public void onRemoval(RemovalNotification<K,V> notification) {
if (notification.getCause() == RemovalCause.REPLACED) {
// custom handling for replaced entries
}
// additional logic
}
}
}Java Architecture Stack
Dedicated to original, practical tech insights—from skill advancement to architecture, front‑end to back‑end, the full‑stack path, with Wei Ge guiding you.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
