Item 29 - Consider typesafe heterogeneous containers

From Effective Java 2/e by Joshua Bloch

  • The idea is to parameterize the key instead of the container.
  • When a class literal is passed among methods to communicate both compile-time and runtime type information, it is called a type token

Typesafe heterogeneous container

// Typesafe heterogeneous container pattern - API
public class Favorites {
   public <T> void putFavorite(Class<T> type, T instance);
   public <T> T getFavorite(Class<T> type);
}

// Typesafe heterogeneous container pattern - client
public static void main(String[] args) {
   Favorites f = new Favorites();
   f.putFavorite(String.class, "Java");
   f.putFavorite(Integer.class, 0xcafebabe);
   f.putFavorite(Class.class, Favorites.class);
   String favoriteString = f.getFavorite(String.class);
   int favoriteInteger = f.getFavorite(Integer.class);
   Class<?> favoriteClass = f.getFavorite(Class.class);
   System.out.printf("%s %x %s%n", favoriteString,
   favoriteInteger, favoriteClass.getName());
}

// Typesafe heterogeneous container pattern - implementation
public class Favorites {
   private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();

   public <T> void putFavorite(Class<T> type, T instance) {
      if (type == null)
         throw new NullPointerException("Type is null");
      favorites.put(type, instance);
      // favorites.put(type, type.cast(instance));
   }

   public <T> T getFavorite(Class<T> type) {
      return type.cast(favorites.get(type));
   }
}

Subtle #1 * Each Favorites instance is backed by a private Map<Class<?>, Object> called favorites * Every key can have a different parameterized type

Subtle #2 * Value type of the favorites Map is simply Object * Map does not guarantee the type relationship between keys and values

cast method

The cast method is the dynamic analog of Java’s cast operator public class Class<T> { T cast(Object obj); }

  • The type tokens used by Favorites are unbounded and sometimes you may need to limit the types that can be passed to a method
  • The annotations API makes extensive use of bounded type tokens
public <T extends Annotation> T getAnnotation(Class<T> annotationType);

// Use of asSubclass to safely cast to a bounded type token
static Annotation getAnnotation(AnnotatedElement element, String annotationTypeName) {
   Class<?> annotationType = null; // Unbounded type token
   try {
      annotationType = Class.forName(annotationTypeName);
   } catch (Exception ex) {
      throw new IllegalArgumentException(ex);
   }
   return element.getAnnotation(annotationType.asSubclass(Annotation.class));
}

asSubclass Method

Class provides an instance method that performs this sort of cast safely (and dynamically). The method is called asSubclass, and it casts the Class object on which it’s called to represent a subclass of the class represented by its argument.

Posted by The Finest Artist