Summary
Neil Gafter has proposed Super Type Tokens and also noted their limitations. Alternatives include reify generics and providing generic factories. Super Type Tokens are a means of remembering the erased type using a token, which is an instance of TypeRef in the example.
Advertisement
Super Type Tokens
Neil Gafter has proposed Super
Type Tokens and also noted their limitations.
Alternatives include
reify
generics and providing generic factories. Super
Type Tokens are a means of remembering the erased type using a token, which is
an instance of TypeRef in the example. Neil gives an example of its use:
The code for TypeRef is a little obscure and the Javadoc for Type etc. are
of no use! Since TypeRef is generic and abstract the supertype of an instance
of type TypeRef must be generic and hence the cast to ParameterizedType won't
fail (line 10). One you have a ParameterizedType you can ask for its type parmeters,
in this case it has one. Now you can use an instance of TypeRef as a genric
token because its equals and hashCode methods look at the generic type.
The usage example is a store of favourites that are accessed via generic type
tokens. However the cast on line 32 that is ignored (line 30) circumvents the
type checking and if two layers of generics are used, e.g. a generic list
is the generic parameter to TypeRef, it fails, as shown below.
6class Oops {
7static Favorites f = newFavorites();
8 9static <T> List<T> favoriteList() {
10 TypeRef<List<T>> ref = new TypeRef<List<T>>(){};
11 List<T> result = f.getFavorite(ref);
12if (result == null) {
13 result = new ArrayList<T>();
14 f.setFavorite(ref, result);
15 }
16return result;
17 }
1819publicstaticvoidmain(String[] args) {
20 List<String> ls = favoriteList();
21 List<Integer> li = favoriteList();
22 li.add(1);
23for (String s : ls) System.out.println(s);
24 }
25 }
The problem is that when the token is created (line 10) for both List<String>
(line 20) and List<Integer> (line 21) the same token is created, one
for a java.util.List<T>, in both cases. Thus the call to favoriteList
on both lines 20 and 21 returns the same list and therefore when run you get
a type error when you attempt the read this list as a String (line 23), because
you have already put an Integer in (line 22). The type system did detect a
potential problem but the warning was supressed and hence a runtime error.
One solution is to reify generics,
i.e.“erase
erasure”, this is my prefered option, via adding a source statement
so that backward compatibility isn't broken. A source statment identifies the
version of Java in use, e.g. source 7;, states that it is Java 7. This way generics
can be reified in 7 and erased in pre-7. Note the idea of a source statement
was my suggestion.
Another possibility is to use factories instead of tokens, e.g.:
The code is straightforward to anyone familiar with the factory design pattern.
Factories are typically singletons and in this case an enum is used to make
a singleton factory. A feature of using a factory instead of a token is that
you would not have an expectation that something made by a different factory
was the same sort of object even if the structural type of the object was the
same. I.E. two List<String> would be different types if made by different
factories, i.e. no structural matching of types just matching based on factories.
The resulting example of using favorites that cause a problem before is now
OK:
7enum FactoryListInteger implements Factory< List< Integer > > {
8 INSTANCE {
9public List< Integer > create() { returnnew ArrayList< Integer >(); }
10 }
11 }
1213publicclass Oops2 {
14finalstatic Favorites2 f = newFavorites2();
1516static < T > List< T > favoriteList( final Factory< List< T > > factory ) {
17 List< T > result = f.getFavorite( factory );
18if ( result == null ) {
19 result = new ArrayList< T >();
20 f.setFavorite( factory, result );
21 }
22return result;
23 }
2425publicstaticvoidmain( final String[] notUsed ) {
26final List< String > ls = favoriteList( FactoryListString.INSTANCE );
27final List< Integer > li = favoriteList( FactoryListInteger.INSTANCE );
28 li.add( 1 );
29for ( final String s : ls ) {
30 out.println( s );
31 }
32for ( final Integer i : li ) {
33 out.println( i );
34 }
35 }
36 }
The above code works as expected because the factories are singletons and
are therefore passed to favoriteList rather than being new objects with an
erased second level type. As noted above the TypeRef version
in both cases is of type List<T>, whereas with the factories the types are
List< String > and List< Integer >.
In the case of Neil's example neither the
type token nor the factory are used for anything other than undoing the effects
of erasure. However both techniques can do much more, in particular they
can make instances of generic objects.
Another example of factories would be for generic collections that are correctly typed internally, i.e. their internal array was correctly typed and they could return a correcttly typed copy array.
IE imagine that ArrayList had a constructor that accepted a factory that could create for it the correctly typed array it needs. EG
interface CollectionFactory< T > {
T[] newArray( int size );
// other object constructors needed by other types of collections
}
class CollectionFactories {
staticfinal CollectionFactory< Integer > integerCollectionFactory = new CollectionFactory< Integer >() {
Integer[] newArray( int size ) { returnnew Integer[ size ]; }
// other integer object constructors
};
// other factories, e.g. for String
}
interface ListPlus< T > extends List< T > {
CollectionFactory< T > getFactory();
T[] toTypedArray();
}
...
ListPlus< Integer > l = new ArrayList< Integer >( integerCollectionFactory );
In the above example a shadow collection set of interfaces called collectionPlus, in the example, is added that has extra methods that are correctly typed and which return the factory which can be used as a type token. The abstract collections are modified to have a factory field and to have constructors that accept factories and to implement the plus interfaces.