The Artima Developer Community
Sponsored Link

Weblogs Forum
Reflecting generics

26 replies on 2 pages. Most recent reply: Aug 22, 2013 1:53 PM by sathya sayee

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 26 replies on 2 pages [ 1 2 | » ]
Ian Robertson

Posts: 68
Nickname: ianr
Registered: Apr, 2007

Reflecting generics (View in Weblogs)
Posted: Jun 22, 2007 10:16 PM
Reply to this message Reply
Summary
Type arguments to generic classes are not available for reflection at runtime - or are they? The type arguments for statically declared types can be discovered at runtime. A look at how to do this, and why you might want to.
Advertisement

Probably the most common complaint about generics in Java is that they are not reified - there is not a way to know at runtime that a List<String> is any different from a List<Long>. I've gotten so used to this that I was quite surprised to run across Neil Gafter's work on Super Type Tokens. It turns out that while the JVM will not track the actual type arguments for instances of a generic class, it does track the actual type arguments for subclasses of generic classes. In other words, while a new ArrayList<String>() is really just a new ArrayList() at runtime, if a class extends ArrayList<String>, then the JVM knows that String is the actual type argument for List's type parameter.

Super type tokens are a nice trick to distinguish between types which have the same raw type, but different type arguments. What they don't easily provide, however, is an easy means of discovering what the type argument for a generic type parameter is. I recently ran across a situation where I wanted to do exactly that while trying to write an abstract base class to take some of the work out of implementing Hibernate's UserType interface:

  public interface UserType {
    /**
     * The class returned by <tt>nullSafeGet()</tt>.
     */
    public Class returnedClass();

    /**
     * Retrieve an instance of the mapped class from a JDBC resultset.
     */
    public Object nullSafeGet(ResultSet rs, String[] names, Object ownerthrows SQLException;

    /**
     * Write an instance of the mapped class to a prepared statement.
     */
    public void nullSafeSet(PreparedStatement st, Object value, int indexthrows SQLException;

    ...
  }

UserType is used to provide custom mappings between database values and Java objects. For example, one might need a UserType implementation to map a textual representation of dates (stored as VARCHARs) to the Date class. Because Hibernate needs to support Java 1.4, the UserType interface is not itself generic, but it makes plenty of sense for a base class which implements it to take advantage of generics. In a reified world I might have something like:

  public abstract class AbstractUserType<T> implements UserType {

    public Class returnedClass() {
      return T.class//not remotely legal in Java 5 or 6.
    }

    abstract public T nullSafeGet(ResultSet rs, String[] names, Object ownerthrows SQLException;

    abstract protected void set(PreparedStatement st, T value, int indexthrows SQLException;

    public void nullSafeSet(PreparedStatement st, Object value, int indexthrows SQLException {
      set(st, (Tvalue, index);
    }

  }

Unfortunately, because of type erasure, the "obvious" implementation of returnedClass() doesn't work. Indeed, an often used pattern in generic programming is to require the type argument class as a method parameter:

  public abstract class AbstractUserType<T> implements UserType {

    private Class<T> returnedClass;
    protected AbstractUserType(Class<T> returnedClass) {
      this.returnedClass = returnedClass;
    }
    public Class returnedClass() {
      return returnedClass;
    }

    ...
  }

While this works, it does so at the cost of forcing clients to repeat themselves:

  public class DateType 
    extends AbstractUserType<Date> // One for the money
  
    public DateType() {
      super(Date.class)// Two for the show
    }

    ...
  }

It turns out, however, that even though we cannot access the type of T directly, we can get at our current class, and use the new interfaces extending java.lang.reflect.Type (introduced in Java 5) to get at what we need. A new method on Class was introduced, getGenericSuperclass(). If the class's parent is a generic class, then this will return a ParameterizedType. The getActualTypeArguments() method on ParameterizedType in turn provides an array of the actual type arguments that were used in extending the parent class.

At first glance, then, it seems that the following ought to do the trick:

  public abstract class AbstractUserType<T> implements UserType {
    ...
    public Class returnedClass {
      ParameterizedType parameterizedType =
        (ParameterizedTypegetClass().getGenericSuperClass();
     return (Classparameterizedtype.getActualTypeArguments()[0];
    }
    ...
  }

Indeed, for a class that directly extends AbstractUserType (and provides a non-array class for the type parameter), this works well. However, in general, several problems can occur:

  • For a class extending AbstractUserType<int[]>, the result of the call to getActualTypeArguments()[0] will be a GenericArrayType, even though one might expect it to be of type Class.
  • If Child extends AbstractUserType, and Grandchild extends Child, then the type returned by Grandchild.class.getGenericSuperClass() will be referencing Child, not AbstractUserType, and hence any actual type arguments would be those provided by Grandchild. Even worse, if Child is not itself a generic class, then Grandchild.class.getGenericSuperClass() will return Child.class, which is of type Class, not ParameterizedType.
  • Given class declarations:

      public class Child<S> extends AbstractUserType<T>{...}

      public class GrandChild extends Child<Long>{...}

    then Child.class.getGenericSuperClass() will return a ParameterizedType whose actual type argument is a TypeVariable representing the type parameter S. This type variable (or one which is .equals() to it) will also be the sole element of the array returned by Grandchild.class.getTypeParameters(). To get the "actual actual" type argument to AbstractUserType, it is necessary to link these two together.
That said, it is possible to accomplish what we want. The first step is to provide a method which might have been polite for Sun to include in the Type interface itself (as it stands, Type is strictly a marker interface):

  /**
   * Get the underlying class for a type, or null if the type is a variable type.
   @param type the type
   @return the underlying class
   */
  public static Class<?> getClass(Type type) {
    if (type instanceof Class) {
      return (Classtype;
    }
    else if (type instanceof ParameterizedType) {
      return getClass(((ParameterizedTypetype).getRawType());
    }
    else if (type instanceof GenericArrayType) {
      Type componentType = ((GenericArrayTypetype).getGenericComponentType();
      Class<?> componentClass = getClass(componentType);
      if (componentClass != null ) {
        return Array.newInstance(componentClass, 0).getClass();
      }
      else {
        return null;
      }
    }
    else {
      return null;
    }
  }

Note that we basically "give up" if we hit an unbound type variable; since the goal here is to find a class, we might as well.

The next step is a bit more involved. We need to look at the actual type arguments provided to the super class of the class in question. If that super class is the base class we are interested in, then we are done. Otherwise, we need to repeat this process. However, the actual type arguments we have just looked at may themselves be used as actual type arguments to the next class up the inheritance hierarchy. Unfortunately, Java will not track this for us; we'll need to do it ourselves.

  /**
   * Get the actual type arguments a child class has used to extend a generic base class.
   *
   @param baseClass the base class
   @param childClass the child class
   @return a list of the raw classes for the actual type arguments.
   */
  public static <T> List<Class<?>> getTypeArguments(
    Class<T> baseClass, Class<? extends T> childClass) {
    Map<Type, Type> resolvedTypes = new HashMap<Type, Type>();
    Type type = childClass;
    // start walking up the inheritance hierarchy until we hit baseClass
    while (! getClass(type).equals(baseClass)) {
      if (type instanceof Class) {
        // there is no useful information for us in raw types, so just keep going.
        type = ((Classtype).getGenericSuperclass();
      }
      else {
        ParameterizedType parameterizedType = (ParameterizedTypetype;
        Class<?> rawType = (ClassparameterizedType.getRawType();
  
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
        for (int i = 0; i < actualTypeArguments.length; i++) {
          resolvedTypes.put(typeParameters[i], actualTypeArguments[i]);
        }
  
        if (!rawType.equals(baseClass)) {
          type = rawType.getGenericSuperclass();
        }
      }
    }
  
    // finally, for each actual type argument provided to baseClass, determine (if possible)
    // the raw class for that type argument.
    Type[] actualTypeArguments;
    if (type instanceof Class) {
      actualTypeArguments = ((Classtype).getTypeParameters();
    }
    else {
      actualTypeArguments = ((ParameterizedTypetype).getActualTypeArguments();
    }
    List<Class<?>> typeArgumentsAsClasses = new ArrayList<Class<?>>();
    // resolve types by chasing down type variables.
    for (Type baseType: actualTypeArguments) {
      while (resolvedTypes.containsKey(baseType)) {
        baseType = resolvedTypes.get(baseType);
      }
      typeArgumentsAsClasses.add(getClass(baseType));
    }
    return typeArgumentsAsClasses;
  }

Finally, we can accomplish our original goal:

  public abstract class AbstractUserType<T> implements UserType {
    ...
    public Class returnedClass {
      return getTypeArguments(AbstractUserType.class, getClass()).get(0);
    }
    ...
  }

While in this case, we are returning raw classes, other use cases might want to see the extended type information for the actual type arguments. Unfortunately, we cannot do this in the case where an actual type argument is a type variable. For example, if the actual type argument for type parameter T is Long, and we are trying to resolve List<T>, we cannot do so without creating a new ParameterizedType instance for the type Long<T>. Since the ParameterizedType implementation provided by Sun is non-instantiable by mere mortals, this would require (re)implementing ParameterizedType. However, since the algorithm for the hashCode method for ParameterizedType is not documented, this cannot be safely accomplished. In particular, it would not be possible to create one of Gafter's Super Type Tokens to represent the actual type argument.

That limitation noted, this can still be useful, and not just for the motivating example above. For example, one could extend ArrayList to get the dynamic return type of toArray() right without help:

  public abstract class TypeAwareArrayList<T> extends ArrayList<T> {
    @Override public T[] toArray() {
      return toArray(
        (T[]) Array.newInstance(
          ReflectionUtils.getTypeArguments(TypeAwareArrayList.class, getClass()).get(0),
        size()));
    }
  }

Note that TypeAwareArrayList is declared abstract. This forces client code to extend it, if only trivially, so that the type information is available:

  TypeAwareArrayList<String> stringList
    new TypeAwareArrayList<String>(){}// notice the trivial anonymous inner class
  ...
  String[] stringArray = stringList.toArray();

While the discussion above has been focused on subclasses, similar generics-aware reflection can be done for methods and fields. As with classes, the main thing to remember is that while the dynamic runtime typing of objects is ignorant of generic typing, the type arguments to statically declared types can be discovered through reflection. Hopefully, Java 7 can "erase erasure", and get rid of this frustrating distinction.


Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Reflecting generics Posted: Jun 24, 2007 3:15 AM
Reply to this message Reply
You have explained super type tokens very well. However super type tokens have limitations, for example nexted generics - see:

http://www.artima.com/forums/flat.jsp?forum=106&thread=206350

An alternative is to use a factory as a type token.

Ian Robertson

Posts: 68
Nickname: ianr
Registered: Apr, 2007

Re: Reflecting generics Posted: Jun 25, 2007 7:02 AM
Reply to this message Reply
The goal here isn't so much to create a type token as it is to adhere to the DRY principal. The problem with the factory approach is the need to declare the type three times - once as a type argument, and once as the return type for the factory create statement, and once in the create statement body.

The weakness in supertype tokens you point out is that it's possible to loose the distinction between two types which differ only in their type arguments. However, this is moot when the sole goal is instance creation, since such instances are indistinguishable at runtime anyway.

Morgan Conrad

Posts: 307
Nickname: miata71
Registered: Mar, 2006

Re: Reflecting generics Posted: Jun 25, 2007 11:31 AM
Reply to this message Reply
I'm a big fan of DRY, but, in this case, just passing in the type as a parameter is starting to look pretty good! It's very clear, there isn't pages of utility code, and no need for an unusual "abstract" class where nothing is abstract, requiring an unusual anonymous empty inner class.

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Reflecting generics Posted: Jun 25, 2007 5:43 PM
Reply to this message Reply
> The goal here isn't so much to create a type token as it
> is to adhere to the DRY principal. The problem with the
> factory approach is the need to declare the type three
> times - once as a type argument, and once as the return
> type for the factory create statement, and once in the
> create statement body.

Sure, but you have had to declare parametric types in plenty of places as well. Doesn't seem much of a gain.

> The weakness in supertype tokens you point out is that
> it's possible to loose the distinction between two types
> which differ only in their type arguments. However, this
> is moot when the sole goal is instance creation, since
> such instances are indistinguishable at runtime anyway.

Just creating instances is a limited case, there is plenty of generic code that wants to act like a container. Neil gave an example in his blog that I used in mine.

Another alternative along the lines that you suggest is:

  public abstract class TypeAwareArrayList< T > extends ArrayList< T > {
    public abstract Class< T > getElementsClass();
 
    @Override public final T[] toArray() { return toArray( (T[]) Array.newInstance( getElementsClass(), size() ) ); }
  }
  ...
  class StringList extends TypeAwareArrayList< String > {
    @Override public final Class< String > getElementsClass() { return String.class; }
  }
  ...
  StringList stringList = new StringList();
  String[] stringArray = stringList.toArray(); 


This is similar to using a factory method and similar to the checked versions of collections already in Java. None of these options are ideal.

I think that now people are used to generics it will be possible to break migration compatibility and reify generics. There are few non-generic libraries that I use now so it would be relatively easy task to re-compile these with a new compiler to reify them. Before J5 you would also have had to re-write them, obviously a bigger task.

paulo faria

Posts: 14
Nickname: i30817
Registered: Jun, 2007

Re: Reflecting generics Posted: Jun 26, 2007 6:31 PM
Reply to this message Reply
I've just signed in to ask a question. In the java desktop forums there was a vague muttering of a swing 2.0 some time ago, and one of the proposals that i made, was to create a api amenable to method chaining, to which some other user replied that it was difficult to maintain such an api because code couldn't be reused by inheritance, without redefining the signatures of each and every void methods in itself and its subclasses to. My question is this: would runtime generics allow us to declare an api of the genre
public this setSomething(T something) where this would be interpreted to be the concrete type of the class that calls the method (no interfaces allowed obviously)?

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Reflecting generics Posted: Jun 27, 2007 1:38 AM
Reply to this message Reply
> I've just signed in to ask a question. In the java desktop
> forums there was a vague muttering of a swing 2.0 some
> time ago, and one of the proposals that i made, was to
> create a api amenable to method chaining, to which some
> other user replied that it was difficult to maintain such
> an api because code couldn't be reused by inheritance,
> without redefining the signatures of each and every void
> methods in itself and its subclasses to. My question is
> this: would runtime generics allow us to declare an api of
> the genre
> public this setSomething(T something) where this would be
> interpreted to be the concrete type of the class that
> calls the method (no interfaces allowed obviously)?

You post is really off topic, but here goes.

What you are talking about is self types, though the keyword this has been proposed for Java like in your example. Self types can currently be done with generics like this:
package selftypes;
 
import static java.lang.System.*;
 
public class Main {
    public static void main( final String[] notUsed ) {
        final MySomething something = new MySomething();
        final MySomething something2 = something.setSomething( something );
        out.println( something2 );
    }
 
    abstract static class Something< S extends Something< S > > {
        S something;
 
        S setSomething( final S something ) {
            this.something = something;
            @SuppressWarnings( "unchecked" ) final S self = (S) this;
            return self;
        }
    }
 
    static class MySomething extends Something< MySomething > {}
}

Enum and Comparable of examples of self typing in the Java standard library.

Peter Kasson

Posts: 1
Nickname: pkasson
Registered: Jun, 2007

Re: Reflecting generics Posted: Jun 27, 2007 4:23 AM
Reply to this message Reply
This is good information. Is it also possible to get the parameter names for methods that have generics?

The reflection kit, at least for 5.0 does not allow the retrieval of the parm names.

Thanks.

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Reflecting generics Posted: Jun 27, 2007 5:12 PM
Reply to this message Reply
Whilst not knocking your code, it is very good, and agreeing with you that it would be nice if these methods were in the standard library. The code doesn't seem to help much. The example you give is:

  TypeAwareArrayList< String > stringList = new TypeAwareArrayList< String >() {}; // notice the trivial anonymous inner class


The trivial anonymous inner class might not be realistic, e.g. suppose you want a constructor with arguments or suppose you need a serial version UID.

If the collections were modified to accept an optional element factory in their constructors, CollectionFactories.integer in the example below. And factories were added to the collection classes themeseves to allow creation that inferred type from their factory argument, called ni in example below. Then you could write:

List< Integer > l = ArrayList.ni( CollectionFactories.integer );


This would appear to satisfy the DRY principle you are striving for to the same extent, two mentions of the type, and be simpler than using the reflection on generics technique proposed both in coding the API and in use.

Ian Robertson

Posts: 68
Nickname: ianr
Registered: Apr, 2007

Re: Reflecting generics Posted: Jun 27, 2007 9:05 PM
Reply to this message Reply
There is certainly no requirement that the child class be anonymous; one could instead write:
public class StringArrayList extends TypeAwareArrayList<String> {
  // custom constructors, serial version UID, etc...
}

Indeed, if you are going to want a TypeAwareArrayList<String> frequently, this would be a prefered approach to anonymous inner classes, if only to avoid several copies of the "same" class.

Of course, a down side to this approach is the need to explicitely declare a child of TypeAwareArrayList for each parameter class of interest. If I'm not mistaken, your CollectionFactories approach has a similar issue, which presumably can also be addressed through annonymous inner classes when appropriate.

As for including something like what I wrote in the standard library, I would much prefer to see Sun just give us reified types.

Ian Robertson

Posts: 68
Nickname: ianr
Registered: Apr, 2007

Re: Reflecting generics Posted: Jun 27, 2007 9:10 PM
Reply to this message Reply
> I'm a big fan of DRY, but, in this case, just passing in
> the type as a parameter is starting to look pretty good!
> It's very clear, there isn't pages of utility code, and
> d no need for an unusual "abstract" class where nothing is
> abstract, requiring an unusual anonymous empty inner class.

I'm very sympathetic to your point of view :). Would your opinion change if the pages of utility code were in an open source library?

Ian Robertson

Posts: 68
Nickname: ianr
Registered: Apr, 2007

Re: Reflecting generics Posted: Jun 27, 2007 9:20 PM
Reply to this message Reply
> Is it also possible to get the parameter names for methods that have generics?

I'm pretty sure that it's not. In fact, if you look at the byte code for a generated class, the parameter names are not present unless the code was compiled with debugging info.

Howard Lovatt

Posts: 321
Nickname: hlovatt
Registered: Mar, 2003

Re: Reflecting generics Posted: Jun 27, 2007 10:50 PM
Reply to this message Reply
> There is certainly no requirement that the child class be
> anonymous; one could instead write:
>
> ...
>
> Of course, a down side to this approach is the need to
> explicitely declare a child of TypeAwareArrayList for each
> parameter class of interest. If I'm not mistaken, your
> CollectionFactories approach has a similar issue, which
> presumably can also be addressed through annonymous inner
> classes when appropriate.

Yes

> As for including something like what I wrote in the
> standard library, I would much prefer to see Sun just give
> us reified types.

Yes again - I would prefer reified generics to any of the options suggested

Morgan Conrad

Posts: 307
Nickname: miata71
Registered: Mar, 2006

Re: Reflecting generics Posted: Jun 28, 2007 6:56 AM
Reply to this message Reply
> I'm very sympathetic to your point of view :). Would your
> opinion change if the pages of utility code were in an
> open source library?

You code is cool Ian, don't get me wrong. Open Source would be nicer. But nicest is for Sun to do it. You identified a few places where you "can't quite" do what you need (e.g. ParameterizedType issues) - maybe if Sun just fixes them???

Also, I have hardly ever needed this feature. So requiring yet another open source jar file, plus some "tricky" code, is a high barrier for my uses.

a o

Posts: 1
Nickname: aao
Registered: Nov, 2007

Re: Reflecting generics Posted: Nov 1, 2007 12:23 PM
Reply to this message Reply
It all sounds very nice, but none of it addresses the main problem T.class
for example the following code still cannot be generic (http://java.sun.com/docs/books/tutorial/reflect/special/enumMembers.html)

import java.util.Arrays;
import static java.lang.System.out;
 
enum Eon { HADEAN, ARCHAEAN, PROTEROZOIC, PHANEROZOIC }
 
public class EnumConstants {
    public static void main(String... args) {
	try {
	    Class<?> c = (args.length == 0 ? Eon.class : Class.forName(args[0]));
	    out.format("Enum name:  %s%nEnum constants:  %s%n",
		       c.getName(), Arrays.asList(c.getEnumConstants()));
	    if (c == Eon.class)
		out.format("  Eon.values():  %s%n",
			   Arrays.asList(Eon.values()));
 
        // production code should handle this exception more gracefully
	} catch (ClassNotFoundException x) {
	    x.printStackTrace();
	}
    }
}
[java]

Flat View: This topic has 26 replies on 2 pages [ 1  2 | » ]
Topic: Scala, Patterns and The Perl Effect Previous Topic   Next Topic Topic:

Sponsored Links



Google
  Web Artima.com   

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