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.