A simple, zero-dependency library for working with types. Supports Java 1.6+ and Android.
One of the sore points with Java involves working with type information. In particular, Java's generics do not provide a way to resolve or reify the type information for a given class. TypeTools looks to solve this by fully resolving generic type information declared on any class, interface, lambda expression or method.
The TypeResolver class provides some of the following methods:
Type reify(Type type, Class<S> context)
Returns a fully reifiedtypeusing type variable information from thecontext.Type reify(Type genericType)
Returns a fully reifiedgenericTypeusing information from the generic declaration.Class<?>[] resolveRawArguments(Class<T> type, Class<S> subType)
Resolves the raw arguments for atypeusing type variable information from asubType.Class<?> resolveRawArgument(Class<T> type, Class<S> subType)
Resolves the raw argument for atypeusing type variable information from asubType.Type resolveGenericType(Class<?> type, Type subType)
Resolves the generictypeusing type variable information from asubType.Class<?> resolveRawClass(Type genericType, Class<?> subType)
Resolves the raw class for agenericTypeusing type variable information from asubType.
A typical use case is to resolve arguments for a type, given a sub-type:
interfaceFoo<T1, T2> {}
classBarimplementsFoo<Integer, String> {}
Class<?>[] typeArgs = TypeResolver.resolveRawArguments(Foo.class, Bar.class);
asserttypeArgs[0] == Integer.class;
asserttypeArgs[1] == String.class;Type arguments can also be resolved from lambda expressions:
Function<String, Integer> strToInt = s -> Integer.valueOf(s);
Class<?>[] typeArgs = TypeResolver.resolveRawArguments(Function.class, strToInt.getClass());
asserttypeArgs[0] == String.class;
asserttypeArgs[1] == Integer.class;And from method references:
Comparator<String> comparator = String::compareToIgnoreCase;
Class<?> typeArg = TypeResolver.resolveRawArgument(Comparator.class, comparator.getClass());
asserttypeArg == String.class;We can reify more complex generic type parameters:
interfaceFoo<T> {}
classBarimplementsFoo<List<Integer>> {}
TypetypeArgs = TypeResolver.reify(Foo.class, Bar.class);
ParameterizedTypeparamType = (ParameterizedType) typeArgs;
Type[] actualTypeArgs = paramType.getActualTypeArguments();
ParameterizedTypearg = (ParameterizedType)actualTypeArgs[0];
assertparamType.getRawType() == Foo.class;
assertarg1.getRawType() == List.class;
assertarg1.getActualTypeArguments()[0] == Integer.class;We can also resolve the raw class for type parameters on fields and methods:
classEntity<IDextendsSerializable> {
IDid;
voidsetId(IDid) {}
}
classSomeEntityextendsEntity<Long> {}
TypefieldType = Entity.class.getDeclaredField("id").getGenericType();
TypemutatorType = Entity.class.getDeclaredMethod("setId", Serializable.class).getGenericParameterTypes()[0];
assertTypeResolver.resolveRawClass(fieldType, SomeEntity.class) == Long.class;
assertTypeResolver.resolveRawClass(mutatorType, SomeEntity.class) == Long.class;And we can reify generic type parameters from fields or methods.
Layer supertypes often utilize type parameters that are populated by subclasses. A common use case for TypeTools is to resolve the type arguments for a layer supertype given a sub-type.
Following is an example Generic DAO layer supertype implementation:
classDevice {}
classRouterextendsDevice {}
classGenericDAO<T, IDextendsSerializable> {
protectedClass<T> persistentClass;
protectedClass<ID> idClass;
privateGenericDAO() {
Class<?>[] typeArguments = TypeResolver.resolveRawArguments(GenericDAO.class, getClass());
this.persistentClass = (Class<T>) typeArguments[0];
this.idClass = (Class<ID>) typeArguments[1];
}
}
classDeviceDAO<TextendsDevice> extendsGenericDAO<T, Long> {}
classRouterDAOextendsDeviceDAO<Router> {}We can assert that type arguments are resolved as expected:
RouterDAOrouterDAO = newRouterDAO();
assertrouterDAO.persistentClass == Router.class;
assertrouterDAO.idClass == Long.class;By default, type variable information for each resolved type is weakly cached by the TypeResolver. Caching can be enabled/disabled via:
TypeResolver.enableCache();
TypeResolver.disableCache();Lambda type argument resolution is currently supported for:
- Oracle JDK 8, 9
- Open JDK 8, 9
When resolving type arguments with lambda expressions, only type parameters used in the functional interface's method signature can be resolved. Ex:
interfaceExtraFunction<T, R, Z> extendsFunction<T, R>{}
ExtraFunction<String, Integer, Long> strToInt = s -> Integer.valueOf(s);
Class<?>[] typeArgs = TypeResolver.resolveRawArguments(Function.class, strToInt.getClass());
asserttypeArgs[0] == String.class;
asserttypeArgs[1] == Integer.class;
asserttypeArgs[2] == Unknown.class;Since the type parameter Z in this example is unused by Function, its argument resolves to Unknown.class.
When using TypeTools in an OSGi environment where lambda or method reference type argument resolution is desired, the sun.reflect system package should be exported to the application bundles. For example, for Felix, add the following to your config.properties file:
org.osgi.framework.system.packages.extra=sun.reflect
JavaDocs are available here.
Copyright 2010-2019 Jonathan Halterman and friends. Released under the Apache 2.0 license.