Item 29: Consider typesafe heterogeneous containers

1.  Generic containers limit you to a fixed number of type parameters per container. In some cases we may parameterize the key instead of the container. Then present the parameterized key to the container to insert or retrieve a value. The generic type system is used to guarantee that the type of the value agrees with its key:

// 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);
    }
    
    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type));
    }
}

 

A Favorites instance is typesafe: it will never return an Integer when you ask it for a String. It is also heterogeneous: all the keys are of different types. We call Favorites a typesafe heterogeneous container. When a class literal is passed among methods to communicate both compile-time and runtime type information, it is called a type token.

 

2.  The type of a class literal is no longer simply Class, but Class<T>. For example, String.class is of type Class<String>, and Integer.class is of type Class<Integer>.

 

3.  The Class.cast method is the dynamic analog of Java’s cast operator. It simply checks that its argument is an instance of the type represented by the Class object. If so, it returns the argument; otherwise it throws a ClassCastException.

 

4.  A malicious client could easily corrupt the type safety of a Favorites instance, simply by using a Class object in its raw form. But the resulting client code would generate an unchecked warning when it was compiled. This is no different from putting a String into a HashSet<Integer> by using the raw type HashSet. To resolve this issue:

// Achieving runtime type safety with a dynamic cast
public <T> void putFavorite(Class<T> type, T instance) {
    favorites.put(type, type.cast(instance));
}

 

5.  There are collection wrappers in java.util.Collections that play the same trick. They are called checkedSet, checkedList, checkedMap, and so forth. Their static factories take a Class object (or two) in addition to a collection (or map). The static factories are generic methods, ensuring that the compile-time types of the Class object and the collection match. The wrappers add reification to the collections they wrap. (The wrapper throws a ClassCastException at runtime if someone tries to put Coin into your Collection<Stamp>.)

 

6.  An AnnotatedElement is a typesafe heterogeneous container whose keys are annotation types, it contains the following method:

public <T extends Annotation> T getAnnotation(Class<T> annotationType);

 

7.  You could cast the object of Class<?> to Class<? extends Annotation>, but this cast is unchecked. The class 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. If the cast succeeds, the method returns its argument; if it fails, it throws a ClassCastException.

 

你可能感兴趣的:(cast)