The Artima Developer Community
Sponsored Link

Pattern Centric Blog
Super Type Tokens
by Howard Lovatt
May 22, 2007
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:

 7 abstract class TypeRef<T> {
 8     private final Type type;
 9     protected TypeRef() {
10         ParameterizedType superclass = (ParameterizedType)
11             getClass().getGenericSuperclass();
12         type = superclass.getActualTypeArguments()[0];
13         System.out.println("type = "+type);
14     }
15     @Override public boolean equals (Object o) {
16         return o instanceof TypeRef &&
17             ((TypeRef)o).type.equals(type);
18     }
19     @Override public int hashCode() {
20         return type.hashCode();
21     }
22 }
23 
24 public class Favorites {
25     private Map<TypeRef<?>, Object> favorites =
26         new HashMap< TypeRef<?> , Object>();
27     public <T> void setFavorite(TypeRef<T> type, T thing) {
28         favorites.put(type, thing);
29     }
30     @SuppressWarnings("unchecked")
31     public <T> T getFavorite(TypeRef<T> type) {
32         return (T) favorites.get(type);
33     }
34     public static void main(String[] args) {
35         Favorites f = new Favorites();
36         List<String> stooges = Arrays.asList(
37             "Larry", "Moe", "Curly");
38         f.setFavorite(new TypeRef<List<String>>(){}, stooges);
39         List<String> ls = f.getFavorite(
40             new TypeRef<List<String>>(){});
41     }
42 }

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.

 6 class Oops {
 7     static Favorites f = new Favorites();
 8 
 9     static <T> List<T> favoriteList() {
10         TypeRef<List<T>> ref = new TypeRef<List<T>>(){};
11         List<T> result = f.getFavorite(ref);
12         if (result == null) {
13             result = new ArrayList<T>();
14             f.setFavorite(ref, result);
15         }
16         return result;
17     }
18 
19     public static void main(String[] args) {
20         List<String> ls = favoriteList();
21         List<Integer> li = favoriteList();
22         li.add(1);
23         for (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.:

 8 interface Factory< T > {
 9   T create();
10 }
11 
12 enum FactoryListString implements Factory< List< String > > {
13   INSTANCE {
14     public List< String > create() { return new ArrayList< String >(); }
15   }
16 }
17 
18 public class Favorites2 {
19   private final Map< Factory< ? >, Object > favorites =
20     new HashMap< Factory< ? >, Object >();
21   
22   public < T > void setFavorite( Factory< T > type, T thing ) {
23     favorites.put( type, thing );
24   }
25 
26   @SuppressWarnings( "unchecked" )
27   public < T > T getFavorite( Factory< T > type ) {
28     return (T) favorites.get( type );
29   }
30 
31   public static void main( final String[] notUsed ) {
32     final Favorites2 f = new Favorites2();
33     f.setFavorite( FactoryListString.INSTANCE,
34                     asList( "Larry", "Moe", "Curly" ) );
35     out.println( f.getFavorite( FactoryListString.INSTANCE ) );
36   }
37 }

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:

 7 enum FactoryListInteger implements Factory< List< Integer > > {
 8   INSTANCE {
 9     public List< Integer > create() { return new ArrayList< Integer >(); }
10   }
11 }
12 
13 public class Oops2 {
14   final static Favorites2 f = new Favorites2();
15 
16   static < T > List< T > favoriteList( final Factory< List< T > > factory ) {
17     List< T > result = f.getFavorite( factory );
18     if ( result == null ) {
19       result = new ArrayList< T >();
20       f.setFavorite( factory, result );
21     }
22     return result;
23   }
24 
25   public static void main( final String[] notUsed ) {
26     final List< String > ls = favoriteList( FactoryListString.INSTANCE );
27     final List< Integer > li = favoriteList( FactoryListInteger.INSTANCE );
28     li.add( 1 );
29     for ( final String s : ls ) {
30       out.println( s );
31     }
32     for ( 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.

Talk Back!

Have an opinion? Readers have already posted 1 comment about this weblog entry. Why not add yours?

RSS Feed

If you'd like to be notified whenever Howard Lovatt adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

Dr. Howard Lovatt is a senior scientist with CSIRO, an Australian government owned research organization, and is the creator of the Pattern Enforcing Compiler (PEC) for Java. PEC is an extended Java compiler that allows Software Design Patterns to be declared and hence checked by the compiler. PEC forms the basis of Howard's 2nd PhD, his first concerned the design of Switched Reluctance Motors.

This weblog entry is Copyright © 2007 Howard Lovatt. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use