Code by Any Other Name
Reflecting generics
by Ian Robertson
June 23, 2007
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.
--------------------------------------------------------------------------------
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:
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:
While this works, it does so at the cost of forcing clients to repeat themselves:
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:
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:
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):
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.
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:
Note that TypeAwareArrayList is declared abstract. This forces client code to extend it, if only trivially, so that the type information is available:
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.
Reflecting generics
by Ian Robertson
June 23, 2007
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.
--------------------------------------------------------------------------------
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 owner) throws SQLException; /** * Write an instance of the mapped class to a prepared statement. */ public void nullSafeSet(PreparedStatement st, Object value, int index) throws 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 owner) throws SQLException; abstract protected void set(PreparedStatement st, T value, int index) throws SQLException; public void nullSafeSet(PreparedStatement st, Object value, int index) throws SQLException { set(st, (T) value, 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 = (ParameterizedType) getClass().getGenericSuperClass(); return (Class) parameterizedtype.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 (Class) type; } else if (type instanceof ParameterizedType) { return getClass(((ParameterizedType) type).getRawType()); } else if (type instanceof GenericArrayType) { Type componentType = ((GenericArrayType) type).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 = ((Class) type).getGenericSuperclass(); } else { ParameterizedType parameterizedType = (ParameterizedType) type; Class<?> rawType = (Class) parameterizedType.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 = ((Class) type).getTypeParameters(); } else { actualTypeArguments = ((ParameterizedType) type).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.
相关推荐
Swift中的Runtime字典转模型是iOS开发中常见的一种技术,特别是在处理网络请求返回的数据时。RunTime(运行时)在Objective-C中是一个核心概念,而在Swift中,虽然它不像OC那样显式,但仍然存在,并且可以用于实现...
let property = Mirror(reflecting: instance).descendants.first { $0.label == key } guard let property = property else { continue } setValue(value, forKeyPath: key, on: instance, withProperty: ...
JFugue是一个开源的Java API,它为编程音乐提供了一个简单而直接的途径,使得开发者可以在Java应用程序中直接创造音乐,而无需直接处理复杂的MIDI消息。它背后的原理是在后台自动生成MIDI消息,但允许用户以一种自然...
【Bose博士301 Direct Reflecting 扬声器系统说明书】 Bose博士301 Direct Reflecting扬声器系统是一款先进的音响设备,专为家庭娱乐和音乐爱好者设计。这款扬声器系统采用了Bose的Direct/Reflecting技术,旨在提供...
以下是一个简单的示例,展示了如何实现字典转模型和模型转字典: ```swift import Foundation class BaseModel { func toDictionary() -> [String: Any] { var dictionary = [String: Any]() let mirror = ...
基于MATLAB实现的intelligent reflecting surface智能反射面+使用说明文档.rar 1、代码压缩包内容 主函数:main.m; 调用函数:其他m文件;无需运行 运行结果效果图; 2、代码运行版本 Matlab 2020b;若运行有误,...
Mechanism and sensing applications of antiresonant reflecting guidance in an alcohol-filled simplified hollow-core (SHC) photonic crystal fiber (PCF) are demonstrated. By filling one air hole in the ...
本压缩包"computing-reflecting-series.rar"提供了一种使用MATLAB进行反射波模拟和处理的方法,主要涉及以下几个核心知识点: 1. 反射波:在地球内部不同介质的界面,地震波会部分反射,部分透射。这些反射波携带了...
转动的彩色立方体,绘制一个不停转动的彩色立体球,可以绕着任意方向旋转; 2) 采用一定的光照明技术,考虑环境光、反射光...the use of blanking algorithm, reflecting the relationship between cube block surfac
let mirror = Mirror(reflecting: self) for child in mirror.children { dict[child.label ?? ""] = child.value } return dict } } ``` 接下来,我们关注如何将字典转换为JSON。在Objective-C中,可以使用`...
常见的策略有填充零(padding with zeros)、镜像边界(reflecting the borders)或者最近邻居插值(nearest-neighbor interpolation)。 5. **创建新图像**:根据计算出的新坐标,创建一个新的图像数组,并将旋转...
The quantum vacuum plays a central role in physics. Quantum electrodynamics (QED) predicts that the properties of the fermionic quantum vacuum can be probed by extremely large electromagnetic fields....
标题中的"IRS_IRS_"可能是指“智能反射表面(Intelligent Reflecting Surface)”的缩写,这是一种在无线通信领域新兴的技术。智能反射表面是一种能够动态调整其电磁特性,以改变无线信号传播路径和特性的二维超材料...
The new method is shown to be effective for mitigating the impact of numerical errors on reconstruction of coupling function for strongly reflecting Bragg gratings. As examples, a flat-top dispersion...
A reflecting ring focusing system for laser propulsion has been designed. A paraboloidal reflector of higher order is constructed to focus the laser energy on a ring which can produce a tire liked ...
s length on the spectral responses of Bragg-reflecting-coupler are investigated detailedly. Both numerical and experimental results demonstrate that signals outside of the gratings' stopband can ...
Anti-resonant reflecting photonic crystal structure in vertical-cavity surface-emitting lasers (VCSELs) to achieve photon confinement in lateral direction is introduced. This kind of design is ...