A simple, zero-dependency set of tools for working with Java types.
One of the sore points with Java involves working with type information. In particular, Java's generics do not provide a way to resolve 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.
Add TypeTools as a Maven dependency:
<dependency>
<groupId>net.jodah</groupId>
<artifactId>typetools</artifactId>
<version>0.4.0</version>
</dependency>
The TypeResolver class provides the following methods:
Class<?>[] resolveRawArguments(Class<T> type, Class<S> subType)
Resolves the raw arguments for atype
using type variable information from asubType
.Class<?> resolveRawArgument(Class<T> type, Class<S> subType)
Resolves the raw argument for atype
using type variable information from asubType
.Type resolveGenericType(Class<?> type, Type subType)
Resolves the generictype
using type variable information from asubType
.Class<?> resolveRawClass(Type genericType, Class<?> subType)
Resolves the raw class for agenericType
using type variable information from asubType
.
A typical use case is to resolve arguments for a type, given a sub-type:
interface Foo<T1, T2> {}
class Bar implements Foo<HashSet<Integer>, ArrayList<String>> {}
Class<?>[] typeArgs = TypeResolver.resolveRawArguments(Foo.class, Bar.class);
assert typeArgs[0] == HashSet.class;
assert typeArgs[1] == ArrayList.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());
assert typeArgs[0] == String.class;
assert typeArgs[1] == Integer.class;
And from method references:
Comparator<String> comparator = String::compareToIgnoreCase;
Class<?> typeArg = TypeResolver.resolveRawArgument(Comparator, comparator.getClass());
assert typeArg == String.class;
We can also resolve the raw class for any generic type, given a sub-type:
class Entity<ID extends Serializable> {
ID id;
void setId(ID id) {}
}
class SomeEntity extends Entity<Long> {}
Type fieldType = Entity.class.getDeclaredField("id").getGenericType();
Type mutatorType = Entity.class.getDeclaredMethod("setId", Serializable.class).getGenericParameterTypes()[0];
assert TypeResolver.resolveRawClass(fieldType, SomeEntity.class) == Long.class;
assert TypeResolver.resolveRawClass(mutatorType, SomeEntity.class) == Long.class;
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:
class Device {}
class Router extends Device {}
class GenericDAO<T, ID extends Serializable> {
protected Class<T> persistentClass;
protected Class<ID> idClass;
private GenericDAO() {
Class<?>[] typeArguments = TypeResolver.resolveRawArguments(GenericDAO.class, getClass());
this.persistentClass = (Class<T>) typeArguments[0];
this.idClass = (Class<ID>) typeArguments[1];
}
}
class DeviceDAO<T extends Device> extends GenericDAO<T, Long> {}
class RouterDAO extends DeviceDAO<Router> {}
We can assert that type arguments are resolved as expected:
RouterDAO routerDAO = new RouterDAO();
assert routerDAO.persistentClass == Router.class;
assert routerDAO.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
- Open JDK 8
When resolving type arguments with lambda expressions, only type parameters used in the functional interface's method signature can be resolved. Ex:
interface ExtraFunction<T, R, Z> extends Function<T, R>{}
ExtraFunction<String, Integer, Long> strToInt = s -> Integer.valueOf(s);
Class<?>[] typeArgs = TypeResolver.resolveRawArguments(Function.class, strToInt.getClass());
assert typeArgs[0] == String.class;
assert typeArgs[1] == Integer.class;
assert typeArgs[2] == Unknown.class;
Since the type parameter Z
in this example is unused by Function
, its argument resolves to Unknown.class
.
JavaDocs are available here.
Copyright 2010-2015 Jonathan Halterman - Released under the Apache 2.0 license.