The Artima Developer Community
Sponsored Link

Weblogs Forum
Super Type Tokens

1 reply on 1 page. Most recent reply: May 28, 2007 11:12 PM by Howard Lovatt

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 1 reply on 1 page
Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Super Type Tokens (View in Weblogs)
Posted: May 22, 2007 4:49 AM
Reply to this message Reply
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.


Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Super Type Tokens Posted: May 28, 2007 11:12 PM
Reply to this message Reply
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 {
  static final CollectionFactory< Integer > integerCollectionFactory = new CollectionFactory< Integer >() {
    Integer[] newArray( int size ) { return new 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.

Flat View: This topic has 1 reply on 1 page
Topic: Super Type Tokens Previous Topic   Next Topic Topic: Java One, Day Three

Sponsored Links



Google
  Web Artima.com   

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