A fork of the excellent GenTyRef library, adding support for working with AnnotatedTypes introduced in Java 8 plus many nifty features.
Table of Contents
- GeantyRef
- Goal
- Overview
- Usage
- Examples
- Getting the exact return type of a method
- Getting the exact type of a field
- Getting the exact types of method parameters
- Getting the exact super type
- Getting the exact sub type
- Getting annotated return/parameter/field/sub/super types
- Creating type literals using TypeToken
- Creating annotated type literals using TypeToken
- Creating types dynamically using TypeFactory
- Creating annotated types dynamically using TypeFactory
- Turning any Type into an AnnotatedType
- More
- Wiki
- License
This library aims to provide a simple way to analyse generic type information and dynamically create
(Annotated)Type instances, all at runtime.
All functionality of the library is exposed via a handful of classes:
GenericTypeReflector: contains static methods used for generic type analysisTypeFactory: contains static methods used forType/AnnotatedTypeinstance creationTypeToken: Used to createType/AnnotatedTypeliterals (using THC pattern)
<dependency>
<groupId>io.leangen.geantyref</groupId>
<artifactId>geantyref</artifactId>
<version>1.3.15</version>
</dependency>You can find instructions at maven.org
The simplest example would be having a class similar to this:
class StringList extends ArrayList<String> {
...
}Getting the exact return type of StringList's get method is rather difficult:
Method get = StringList.class.getMethod("get", int.class);
get.getGenericReturnType() //yields T, which is not very useful informationOn the other hand, running GenericTypeReflector.getExactReturnType(get, StringClass.class)
would yield String which is what we were looking for.
Presume we have two simple classes:
class Container<T> {
public T item;
}
class NumberContainer extends Container<Number> {}We again face issues when trying to discover the exact type of the item field:
Field item = NumberContainer.class.getField("item");
item.getGenericType(); //yields T againInstead, GenericTypeReflector.getExactFieldType(item, NumberContainer.class) returns Number as desired.
GenericTypeReflector.getExactParameterTypes(methodOrConstructor, aType)
If we had the classes defined as follows:
class Container<T> {
public T item;
}
class NumberContainer<T extends Number> extends Container<T> {}
class LongContainer extends NumberContainer<Long> {}If we'd call LongContainer.class.getGenericSuperclass() it would correctly return NumberContainer<Long>
but getting from there to Container<Long> is much more difficult, as there's no direct way.
GeantyRef allows us to simply call
GenericTypeReflector.getExactSuperType(LongContainer.class, Container.class) to get Container<Long>
Even more interestingly, it is sometimes possible to get the exact sub type of a type.
For example, if we had List<String> and we wanted ArrayList<String> if would be possible,
as the ArrayList's sole type parameter is coming from List, i.e. ArrayList does not define
any type parameters itself, List<String> already contains all the needed information.
This is rather difficult to calculate using standard reflection, but if we already had a reference to
List<String> called listOfString, it is enough to call:
GenericTypeReflector.getExactSubType(listOfString, ArrayList.class) to get ArrayList<String>
Still, how to get to listOfString? Probably by calling one of the methods described above.
But, it's also possible to create a type literal directly via TypeToken, or construct the desired
Type (List<String>) dynamically using TypeFactory.
It is worth noting that all the basic methods on the GenericTypeReflector listed above have
overloads that work with AnnotatedTypes.
class Person {
public List<@NonNull String> nicknames;
}
AnnotatedType listOfNonNullStrings = Person.class.getField("nicknames").getAnnotatedType();
Method get = List.class.getMethod("get", int.class);
//returns an AnnotatedType representing: @NonNull String
AnnotatedType nonNullString = GenericTypeReflector.getExactReturnType(get, listOfNonNullStrings);Similarly, getExactFieldType, getExactParameterTypes, getExactSuperType, getExactSubType
work with AnnotatedTypes.
This approach, known as Typesafe Heterogenous Container (THC) or type token, is widely used in libraries like Jackson or Gson that need to work with generic types. There are various sources describing the intricacies of this approach, Neal Gafter's blog being a classic one.
To obtain a Type instance representing a know generic type, such as List<String> it is enough to
do the following:
Type listOfString = new TypeToken<List<String>>(){}.getType();
What we're doing here is creating an anonymous subclass of a parameterized type (TypeToken) and
getting it's generic super class via getGenericSuperclass.
If instead we wanted an instance of an annotated type, such as List<@NonNull String>, the same
principle would apply:
AnnotatedType listOfNonNullString = new TypeToken<List<@NonNull String>>(){}.getAnnotatedType();
TypeToken is only useful if all the generic type information is known ahead of time. It will not
allow us to create a Type or AnnotatedType dynamically. For such a task, TypeFactory provides
adequate methods.
Class<List> listType = List.class;
Class<String> typeParameter = String.class;
//returns a Type representing List<String>
Type listOfStrings = TypeFactory.parameterizedClass(listType, typeParameter);
Class<Map> mapType = Map.class;
Class<String> keyTypeParameter = String.class;
Class<Number> valueTypeParameter = Number.class;
//returns a Type representing Map<String, Number>
Type mapOfStringNumber = TypeFactory.parameterizedClass(mapType, keyTypeParameter, valueTypeParameter);TypeFactory can also produce AnnotatedType instances, but this means we need to somehow obtain
instances of the annotations themselves (instances of Annotation).
An obvious way of getting them would be from an existing AnnotatedElement (annotatedElement.getAnnotations()).
Class, Parameter, Method, Constructor, Field all implement AnnotatedElement.
But, TypeFactory also allows you to instantiate an Annotation dynamically:
Map<String, Object> annotationParameters = new HashMap<>();
annotationParameters.put("name", "someName");
MyAnnotation myAnnotation = TypeFactory.annotation(MyAnnotation.class, annotationParameters);This produces an Annotation instance as if @MyAnnotation(name = "someName") was found in the sources.
Armed with this knowledge, it's easy to produce an AnnotatedType:
Class<List> listType = List.class;
Class<String> typeParameter = String.class;
Annotation[] annotations = { myAnnotation };
//returns an AnnotatedType representing: @MyAnnotation(name = "someName") List<String>
AnnotatedType annotatedListOfString = TypeFactory.parameterizedClass(listType, annotations, typeParameter);GenericTypeReflector also allows you to wrap any Type into an AnnotatedType by collecting its
direct annotations. For example:
@SuppressWarnings("unchecked")
class Something {}
//returns an AnnotatedType representing: @SuppressWarnings("unchecked") Something
AnnotatedType something = GenericTypeReflector.annotate(Something.class);This method will correctly turn a ParameterizedType into an AnnotatedParameterizedType,
WildcardType into an AnnotatedWildcardType etc.
To turn a ParameterizedType into an AnnotatedParameterizedType with customized annotations:
Type listOfStrings = TypeFactory.parameterizedClass(List.class, String.class);
Annotation[] typeAnnotations = { myAnnotation };
Annotation[] argumentAnnotations = { anotherAnnotation };
//Get an AnnotatedParameterizedType representing: @MyAnnotation List<@AnotherAnnotation String>
AnnotatedParameterizedType annotatedListOfAnnotatedStrings =
parameterizedAnnotatedType(listOfStrings, typeAnnotations, argumentAnnotations);There are more features provided by GenericTypeReflector that were not described in depth here,
like the possibility to replace annotations on an AnnotatedType via replaceAnnotations,
or update them via updateAnnotations, calculate hash codes and check equality of AnnoatedTypes
(as equals and hasCode are not overridden in Java's AnnotatedType implementations) etc, so
feel free to explore a bit on your own.
More info can be found at the project Wiki.