Atom Feed SITE FEED   ADD TO GOOGLE READER

Coding in the small with Google Collections: ClassToInstanceMap

Part 14 in a Series.

ClassToInstanceMap is a specialized Map whose keys are class literals like PizzaPromotion.class or RestockingInformation.class and whose values are instances of those types. It provides a convenient balance between type safety and model flexibility.

In some code I wrote recently, I needed to attach a RestockingInformation object to a Product without explicitly mentioning (and depending on) RestockingInformation in the Product class. I also wanted to allow other objects to be attached as necessary in the future:

Before:

public class Product {

...

private final Map<Class<?>, Optional> optionalValuesByType
= new HashMap<Class<?>, Optional>();

public <T extends Optional> void putOptional(Class<T> type, T value) {
type.cast(value); /* ensure the value is assignable to T */
optionalValuesByType.put(type, value);
}

@SuppressWarnings({"unchecked"})
public <T extends Optional> T getOptional(Class<T> type) {
return Objects.nonNull((T)optionalValuesByType.get(type));
}

@SuppressWarnings({"unchecked"})
public <T extends Optional> T getOptional(Class<T> type, T defaultValue) {
T result = (T)optionalValuesByType.get(type);
return result != null
? result
: defaultValue;
}

private interface Optional {
/* marker interface */
}
}

After:

public class Product {

...

private final ClassToInstanceMap<Optional> optionalValuesByType
= Maps.newClassToInstanceMap();

public <T extends Optional> void putOptional(Class<T> type, T value) {
optionalValuesByType.putInstance(type, value);
}

public <T extends Optional> T getOptional(Class<T> type) {
return Objects.nonNull(optionalValuesByType.getInstance(type));
}

public <T extends Optional> T getOptional(Class<T> type, T defaultValue) {
T result = optionalValuesByType.getInstance(type);
return result != null
? result
: defaultValue;
}

private interface Optional {
/* marker interface */
}
}

Class literals are ideal map keys because they already have a global namespace - Java class names are distinct! Anyone can introduce their own Optional implementation without fear of clobbering someone else's value.

Suppose the inventory team added an optional type called com.publicobject.pizza.inventory.DeliveryInfo to describe how a product is transported from the warehouse to the storefront. This doesn't prevent the customer service team from tracking a 30-minute guarantee in their own com.publicobject.pizza.customers.DeliveryInfo class. Both keys can be added to a map without clobbering each other. Using Strings for keys is not nearly as safe!

The idea of using class literals as map keys is useful everywhere, but Guice made it famous. ClassToInstanceMap makes it easy to do everywhere else.