Suppose you have the following abstract java class:
public abstract class AbstractRequestHandler<I,O> {
I input;
O output;
}
and the following child classes hierarchy:
public abstract class AbstractUserRequestHandler<I extends User,O> extends AbstractRequestHandler<I,O>{...}
public abstract class AbstractUniversityRequestHandler<I extends UniversityUser> extends AbstractUserRequestHandler<I,String>{...}
public class StudentRequestHandler extends AbstractUniversityRequestHandler<Student>{...}
public class TeacherRequestHandler extends AbstractUniversityRequestHandler<Teacher>{...}
Suppose you need to use at a given point on the super class the generic type, for example in order to deserialize on the constructor the request json to the specific request object using gson library as follow:
public AbstractRequestHandler(final String inputJson) {
input = new Gson().fromJson(inputJson,typeOfI);
}
You need the type of generic I within variable "typeOfI"
Is there a global solution that allows to get the generic type specified by a concrete child class that respects the following constraints?
The type is gotten at runtime regardless the child classes hierarchy ( that can be also more complex the one given as example on this question )
The developer just needs to define the generic extending the super class without manually specify the generic type somewhere on concrete child class ( for example on overrided method or constructor )
So that if you want to define a new concrete child that assign a new value to a generic you can just write the following concrete class for example:
public class StudentRequestHandler extends AbstractUniversityRequestHandler<Student>{
public StudentRequestHandler(String inputJson) {
super(inputJson);
}
}
I found the following solutions but they don't respect both the asked solution constraints.
Solution that breaks constraint n°2
A solution could be to define an abstract method on the superclass as follow
protected abstract Type getRequestType();
and then implement it on every concrete child class that defines the generic:
public class StudentRequestHandler extends AbstractUniversityRequestHandler<Student>{
public StudentRequestHandler(String inputJson) {
super(inputJson);
}
#Override
protected Type getRequestType() {
return Student.class;
}
}
Then the getRequestType() method can be used on constructor on the target superclass:
public AbstractRequestHandler(final String inputJson) {
request = new Gson().fromJson(inputJson,getRequestType());
}
But even if it works regardless the child classes hierarchy ( respect constraint n°1 ) the developer should manually implement an abstract method on each concrete child class.
Solution that breaks constraint n°1
If the hierarchy is simple having only a direct child that extend from the target superclass, as for example:
public class TeacherRequestHandler extends AbstractRequestHandler<Teacher,String>{...}
a working solution has been proposed by #naikus ( https://stackoverflow.com/users/306602/naikus ) on the following stackoverflow thread:
Using a generic type of a subclass within it's abstract superclass?
However this doesn't work if the concrete class is not a direct child of the superclass that defines the generics ( as the one proposed as example on this question ).
Edit: after reading your answer and testing many other possible cases I decided to edit your code and re-write it to support all other possible edge cases to include tracking of generics nested deeply inside other generic types.
Sadly to support all cases we need a lot more code than you provided, generics are very tricky, like consider class like this:
private class SomeClass<A, B, C, D, E, F> {}
private class SomeConfusingClass<A> extends SomeClass<List<Void>[], List<? extends A>[], List<? extends A[][][]>[][][], List<? extends String[]>[], Map<List<? extends A[]>, A[][]>[], A> {}
private class TestClass extends SomeConfusingClass<Void> {}
To even start doing this we need to have own implementation of java generic types to later be able to construct types like List<String>[] as there is no way to create such type dynamically with raw java API.
This is pretty popular way of handling generic in libraries like that, you can see similar thing in jackson library and many more.
So we need implementation of GenericArrayType, ParameterizedType and WildcardType:
private static class ResolvedGenericArrayType implements GenericArrayType {
private final Type genericComponentType;
ResolvedGenericArrayType(Type genericComponentType) {
this.genericComponentType = genericComponentType;
}
#Override
public Type getGenericComponentType() {
return genericComponentType;
}
public String toString() {
return getGenericComponentType().toString() + "[]";
}
#Override
public boolean equals(Object o) {
if (o instanceof GenericArrayType) {
GenericArrayType that = (GenericArrayType) o;
return Objects.equals(genericComponentType, that.getGenericComponentType());
} else
return false;
}
#Override
public int hashCode() {
return Objects.hashCode(genericComponentType);
}
}
private static class ResolvedParameterizedType implements ParameterizedType {
private final Type[] actualTypeArguments;
private final Class<?> rawType;
private final Type ownerType;
private ResolvedParameterizedType(Type rawType, Type[] actualTypeArguments, Type ownerType) {
this.actualTypeArguments = actualTypeArguments;
this.rawType = (Class<?>) rawType;
this.ownerType = (ownerType != null) ? ownerType : this.rawType.getDeclaringClass();
}
public Type[] getActualTypeArguments() {
return actualTypeArguments.clone();
}
public Class<?> getRawType() {
return rawType;
}
public Type getOwnerType() {
return ownerType;
}
#Override
public boolean equals(Object o) {
if (!(o instanceof ParameterizedType)) {
return false;
}
ParameterizedType that = (ParameterizedType) o;
if (this == that)
return true;
Type thatOwner = that.getOwnerType();
Type thatRawType = that.getRawType();
return Objects.equals(ownerType, thatOwner) && Objects.equals(rawType, thatRawType) &&
Arrays.equals(actualTypeArguments, that.getActualTypeArguments());
}
#Override
public int hashCode() {
return Arrays.hashCode(actualTypeArguments) ^
Objects.hashCode(ownerType) ^
Objects.hashCode(rawType);
}
public String toString() {
StringBuilder sb = new StringBuilder();
if (ownerType != null) {
sb.append(ownerType.getTypeName());
sb.append("$");
if (ownerType instanceof ResolvedParameterizedType) {
sb.append(rawType.getName().replace(((ResolvedParameterizedType) ownerType).rawType.getName() + "$", ""));
} else
sb.append(rawType.getSimpleName());
} else
sb.append(rawType.getName());
if (actualTypeArguments != null) {
StringJoiner sj = new StringJoiner(", ", "<", ">");
sj.setEmptyValue("");
for (Type t : actualTypeArguments) {
sj.add(t.getTypeName());
}
sb.append(sj.toString());
}
return sb.toString();
}
}
private static class ResolvedWildcardType implements WildcardType {
private final Type[] upperBounds;
private final Type[] lowerBounds;
public ResolvedWildcardType(Type[] upperBounds, Type[] lowerBounds) {
this.upperBounds = upperBounds;
this.lowerBounds = lowerBounds;
}
public Type[] getUpperBounds() {
return upperBounds.clone();
}
public Type[] getLowerBounds() {
return lowerBounds.clone();
}
public String toString() {
Type[] lowerBounds = getLowerBounds();
Type[] bounds = lowerBounds;
StringBuilder sb = new StringBuilder();
if (lowerBounds.length > 0)
sb.append("? super ");
else {
Type[] upperBounds = getUpperBounds();
if (upperBounds.length > 0 && !upperBounds[0].equals(Object.class)) {
bounds = upperBounds;
sb.append("? extends ");
} else
return "?";
}
StringJoiner sj = new StringJoiner(" & ");
for (Type bound : bounds) {
sj.add(bound.getTypeName());
}
sb.append(sj.toString());
return sb.toString();
}
#Override
public boolean equals(Object o) {
if (o instanceof WildcardType) {
WildcardType that = (WildcardType) o;
return Arrays.equals(this.getLowerBounds(), that.getLowerBounds()) && Arrays.equals(this.getUpperBounds(), that.getUpperBounds());
} else
return false;
}
#Override
public int hashCode() {
Type[] lowerBounds = getLowerBounds();
Type[] upperBounds = getUpperBounds();
return Arrays.hashCode(lowerBounds) ^ Arrays.hashCode(upperBounds);
}
}
You can basically copy them from JDK and just do some cleanup.
Next utility we need is a function to validate at the end if we did everything right, like we don't want to return Map<List<? extends X>[]> where X is still not resolved TypeVariable:
private static boolean isDefined(Type type) {
if (type instanceof Class) {
return true;
}
if (type instanceof GenericArrayType) {
return isDefined(((GenericArrayType) type).getGenericComponentType());
}
if (type instanceof WildcardType) {
for (Type lowerBound : ((WildcardType) type).getLowerBounds()) {
if (!isDefined(lowerBound)) {
return false;
}
}
for (Type upperBound : ((WildcardType) type).getUpperBounds()) {
if (!isDefined(upperBound)) {
return false;
}
}
return true;
}
if (!(type instanceof ParameterizedType)) {
return false;
}
for (Type typeArgument : ((ParameterizedType) type).getActualTypeArguments()) {
if (!isDefined(typeArgument)) {
return false;
}
}
return true;
}
Simple recursive function will do this for us. We just check for every possible generic type and check if every member of it is also defined, and unless we will find some hidden TypeVariable we are fine.
Main function can stay the same as in your code, we only will edit that one check at the end to use our new function:
public static Type getParameterizedType(Class<?> klass, Class<?> rootClass, int paramTypeNumber) throws GenericsException {
int targetClassParametersNumber = rootClass.getTypeParameters().length;
if (targetClassParametersNumber == 0) {
throw new GenericsException(String.format("Target class [%s] has no parameters type", rootClass.getName()));
} else if (targetClassParametersNumber - 1 < paramTypeNumber)
throw new GenericsException(String.format("Target class [%s] has parameters type which index start from [0] to [%s]. You requested instead parameter with index [%s]", rootClass, paramTypeNumber - 1, targetClassParametersNumber));
Type type = analyzeParameterizedTypes(klass, klass, rootClass, paramTypeNumber, null);
if (!isDefined(type))
throw new GenericsException(String.format("Parameter [%s] with index [%d] defined on class [%s] has not been valued yet on child class [%s]", type, paramTypeNumber, rootClass.getName(), klass.getName()));
return type;
}
Now lets work on our main
public static Type analyzeParameterizedTypes(final Class<?> klass, final Class<?> targetClass, final Class<?> rootClass, final int paramTypeNumber, Map<Integer, Type> childClassTypes) throws GenericsException {
function, the begging stays the same, we collect all TypeVariable to simple map, keeping already collected information from previous loop on previous class.
Type superclassType = klass.getGenericSuperclass();
Map<TypeVariable<?>, Type> currentClassTypes = new HashMap<>();
int z = 0;
if (childClassTypes != null) {
for (TypeVariable<?> variable : klass.getTypeParameters()) {
currentClassTypes.put(variable, childClassTypes.get(z));
z++;
}
}
Then we have our loop collecting and refining our type arguments:
Map<Integer, Type> superClassesTypes = new HashMap<>();
if (superclassType instanceof ParameterizedType) {
int i = 0;
for (final Type argType : ((ParameterizedType) superclassType).getActualTypeArguments()) {
if (argType instanceof TypeVariable) {
superClassesTypes.put(i, currentClassTypes.containsKey(argType) ? currentClassTypes.get(argType) : argType);
} else {
superClassesTypes.put(i, refineType(klass, argType, currentClassTypesByName));
}
i++;
}
}
There 2 paths for each type argument, if its TypeVariable we just keep tracking it, and if its anything else we try to "refine" it from any possible references to TypeVariable. This is the most complicated process of this code, and this is why we needed all these classes above.
We start from this simple recursive dispatch method that handles all possible types:
private static Type refineType(Type type, Map<TypeVariable<?>, Type> typeVariablesMap) throws GenericsException {
if (type instanceof Class) {
return type;
}
if (type instanceof GenericArrayType) {
return refineArrayType((GenericArrayType) type, typeVariablesMap);
}
if (type instanceof ParameterizedType) {
return refineParameterizedType((ParameterizedType) type, typeVariablesMap);
}
if (type instanceof WildcardType) {
return refineWildcardType((WildcardType) type, typeVariablesMap);
}
if (type instanceof TypeVariable) {
return typeVariablesMap.get(type);
}
throw new GenericsException("Unsolvable generic type: " + type);
}
And small utility method to run it on array of types:
private static Type[] refineTypes(Type[] types, Map<TypeVariable<?>, Type> typeVariablesMap) throws GenericsException {
Type[] refinedTypes = new Type[types.length];
for (int i = 0; i < types.length; i++) {
refinedTypes[i] = refineType(types[i], typeVariablesMap);
}
return refinedTypes;
}
Each type goes to own function, or if its TypeVariable we just fetch resolved one from map. Note that this can return null, and I did not handle it here. This could be improved later. For classes we don't need to do anything so we can just return class itself.
For GenericArrayType we need to first find out how many dimension such array might have (this could be handled by recursion in our refine method too, but then its a bit harder to debug in my opinion):
private static int getArrayDimensions(GenericArrayType genericArrayType) {
int levels = 1;
GenericArrayType currentArrayLevel = genericArrayType;
while (currentArrayLevel.getGenericComponentType() instanceof GenericArrayType) {
currentArrayLevel = (GenericArrayType) currentArrayLevel.getGenericComponentType();
levels += 1;
}
return levels;
}
Then we want to extract that nested component type of array, so for List<A>[][][] we want just List<A>:
private static Type getArrayNestedComponentType(GenericArrayType genericArrayType) {
GenericArrayType currentArrayLevel = genericArrayType;
while (currentArrayLevel.getGenericComponentType() instanceof GenericArrayType) {
currentArrayLevel = (GenericArrayType) currentArrayLevel.getGenericComponentType();
}
return currentArrayLevel.getGenericComponentType();
}
And then we need to refine this type, so our List<A> will change to eg List<String>:
Type arrayComponentType = refineType(getArrayNestedComponentType(genericArrayType), typeVariablesMap);
And rebuild our generic structure using refined type, so our created List<String> will change back to List<String>[][][]:
private static Type buildArrayType(Type componentType, int levels) throws GenericsException {
if (componentType instanceof Class) {
return Array.newInstance(((Class<?>) componentType), new int[levels]).getClass();
} else if (componentType instanceof ParameterizedType) {
GenericArrayType genericArrayType = new ResolvedGenericArrayType(componentType);
for (int i = 1; i < levels; i++) {
genericArrayType = new ResolvedGenericArrayType(genericArrayType);
}
return genericArrayType;
} else {
throw new GenericsException("Array can't be of generic type");
}
}
And whole function looks like this:
private static Type refineArrayType( GenericArrayType genericArrayType, Map<TypeVariable<?>, Type> typeVariablesMap) throws GenericsException {
int levels = getArrayDimensions(genericArrayType);
Type arrayComponentType = refineType(getArrayNestedComponentType(genericArrayType), typeVariablesMap);
return buildArrayType(arrayComponentType, levels);
}
For ParameterizedType its much simpler, we just refine type arguments, and create new ParameterizedType instance with these refined arguments:
private static Type refineParameterizedType(ParameterizedType parameterizedType, Map<TypeVariable<?>, Type> typeVariablesMap) throws GenericsException {
Type[] refinedTypeArguments = refineTypes(parameterizedType.getActualTypeArguments(), typeVariablesMap);
return new ResolvedParameterizedType(parameterizedType.getRawType(), refinedTypeArguments, parameterizedType.getOwnerType());
}
Same for WildcardType:
private static Type refineWildcardType(WildcardType wildcardType, Map<TypeVariable<?>, Type> typeVariablesMap) throws GenericsException {
Type[] refinedUpperBounds = refineTypes(wildcardType.getUpperBounds(), typeVariablesMap);
Type[] refinedLowerBounds = refineTypes(wildcardType.getLowerBounds(), typeVariablesMap);
return new ResolvedWildcardType(refinedUpperBounds, refinedLowerBounds);
}
And this leaves us with whole analyze function looking like this:
public static Type analyzeParameterizedTypes(final Class<?> klass, final Class<?> targetClass, final Class<?> rootClass, final int paramTypeNumber, Map<Integer, Type> childClassTypes) throws GenericsException {
Type superclassType = klass.getGenericSuperclass();
Map<TypeVariable<?>, Type> currentClassTypes = new HashMap<>();
int z = 0;
if (childClassTypes != null) {
for (TypeVariable<?> variable : klass.getTypeParameters()) {
currentClassTypes.put(variable, childClassTypes.get(z));
z++;
}
}
Map<Integer, Type> superClassesTypes = new HashMap<>();
if (superclassType instanceof ParameterizedType) {
int i = 0;
for (final Type argType : ((ParameterizedType) superclassType).getActualTypeArguments()) {
if (argType instanceof TypeVariable) {
superClassesTypes.put(i, currentClassTypes.getOrDefault(argType, argType));
} else {
superClassesTypes.put(i, refineType(argType, currentClassTypes));
}
i++;
}
}
if (klass != rootClass) {
final Class<?> superClass = klass.getSuperclass();
if (superClass == null)
throw new GenericsException(String.format("Class [%s] not found on class parent hierarchy [%s]", rootClass, targetClass));
return analyzeParameterizedTypes(superClass, targetClass, rootClass, paramTypeNumber, superClassesTypes);
}
return childClassTypes.get(paramTypeNumber);
}
Example usage:
private class SomeClass<A, B, C, D, E, F> {}
private class SomeConfusingClass<A> extends SomeClass<List<Void>[], List<? extends A>[], List<? extends A[][][]>[][][], List<? extends String[]>[], Map<List<? extends A[]>, A[][]>[], A> {}
private class TestClass extends SomeConfusingClass<Void> {}
public static void main(String[] args) throws Exception {
System.out.println(GenericsUtils.getParameterizedType(TestClass.class, SomeClass.class, 0));
System.out.println(GenericsUtils.getParameterizedType(TestClass.class, SomeClass.class, 1));
System.out.println(GenericsUtils.getParameterizedType(TestClass.class, SomeClass.class, 2));
System.out.println(GenericsUtils.getParameterizedType(TestClass.class, SomeClass.class, 3));
System.out.println(GenericsUtils.getParameterizedType(TestClass.class, SomeClass.class, 4));
System.out.println(GenericsUtils.getParameterizedType(TestClass.class, SomeClass.class, 5));
}
And results:
java.util.List<java.lang.Void>[]
java.util.List<? extends java.lang.Void>[]
java.util.List<? extends java.lang.Void[][][]>[][][]
java.util.List<? extends java.lang.String[]>[]
java.util.Map<java.util.List<? extends java.lang.Void[]>, java.lang.Void[][]>[]
class java.lang.Void
Whole code with tests can be found here: https://gist.github.com/GotoFinal/33b9e282f270dbfe61907aa830c27587 or here: https://github.com/GotoFinal/generics-utils/tree/edge-cases-1
Based on OP original answer code, but with most of edge cases covered.
The answer is: Java does not support reified generics, see this feature request from 2004 with lots of duplicates. See also:
C# has reified generics, Java does not
Kotlin reified generics example
Java generics, type erasure and again Kotlin's reified generics
So unless you want to switch to Kotlin, there is simply nothing you can do because generic type information in Java is available to the compiler only, not during runtime (reified generics).
I am sorry if you do not like the answer, but still it is correct as of Java 13 in early 2020.
I think a working solution would be to extend the one proposed by #naikus. It only needs to go up in the hierarchy on the constructor.
import java.lang.reflect.ParameterizedType;
public abstract class AbstractRequestHandler<I,O> {
protected I input;
protected O output;
protected Class<I> inputClass;
protected Class<O> outputClass;
protected AbstractRequestHandler() {
Class<?> clazz = getClass();
while (!clazz.getSuperclass().equals(AbstractRequestHandler.class)) {
clazz = clazz.getSuperclass();
}
ParameterizedType genericSuperclass = (ParameterizedType) clazz.getGenericSuperclass();
this.inputClass = (Class<I>) genericSuperclass.getActualTypeArguments()[0];
this.outputClass = (Class<O>) genericSuperclass.getActualTypeArguments()[1];
}
}
I've worked to an utility library that offers a method that generally solve the question analyzing recursively all parent classes hierarchy to get a specific generic type.
It is available on my GitHub project: https://github.com/gregorycallea/generics-utils
UPDATE: Thanks to #GoToFinal user that with his great effort improved the project covering also several differents complex
generics case ( such as GenericArrayType, ParameterizedType and
WildcardType).
For all details about these improvements see his answer on this question.
This is the summarized scenario the method works on:
Suppose you have a parameterized root class with an undefined number of generics defined.
Example: Let's consider as root class the following "Base" class that defines 3 generics:
private class Base<I, E, F> {
I var1;
E var2;
F var3;
}
NOTE: To each generic is assigned an index starting from 0. So index mapping for this class is:
I = 0
E = 1
F = 2
Suppose this root class have a complex and multi-leveled hierarchy of child classes.
Example:
// Base<I,E,F>
// BaseA<G,H> extends Base<H,Boolean,G>
// BaseB<T> extends BaseA<T,String>
// BaseC<H> extends BaseB<H>
// BaseD extends BaseC<Integer>
// BaseE extends BaseD
// BaseF extends BaseE
// BaseG extends BaseF
// BaseH<H> extends BaseG<H,Double>
// BaseI<T> extends BaseF<T>
// BaseL<J> extends BaseI<J>
// BaseM extends BaseL<Float>
// BaseN extends BaseM
NOTE: Notice that walking the child hierarchy new parameterized classes are defined and also some classes are not parameterized at all
Then suppose you want to choose whatever class on root class child hierarchy and then get the exactly type of a specific generic defined on root class starting from this.
Example:
You want to know the type of E generic ( with index = 1 ) defined on Base class starting from child class BaseN.
To do this you can simply execute the GenericsUtils.getParameterizedType method as follow:
Type targetType = GenericsUtils.getParameterizedType(GenericChildClass.class, RootClass.class, genericRootClassIndex);
Example:
Type EType = GenericsUtils.getParameterizedType(BaseN.class, Base.class, 1);
I evaluated several cases for this example scenario with unit tests.
Take a look at:
https://github.com/gregorycallea/generics-utils/blob/master/src/test/java/com/github/gregorycallea/generics/GenericsUtilsTest.java
About the initial scenario exposed on my question instead we can use this method on AbstractRequestHandler constructor as follow:
public abstract class AbstractRequestHandler<I,O> {
I input;
O output;
public AbstractRequestHandler(String inputJson) throws GenericsException {
this.input = new Gson().fromJson(inputJson,GenericsUtils.getParameterizedType(getClass(), AbstractRequestHandler.class, 0));
}
}
I'm looking through HTMLUnit sources and can't quite figure out how is generic type supposed to work here. It's never used inside the method, what's the point of making the method generic?
public <T> List<T> getByXPath(final String xpathExpr) {
PrefixResolver prefixResolver = null;
if (hasFeature(XPATH_SELECTION_NAMESPACES)) {
/*
* See if the document has the SelectionNamespaces property defined. If so, then
* create a PrefixResolver that resolves the defined namespaces.
*/
final Document doc = getOwnerDocument();
if (doc instanceof XmlPage) {
final ScriptableObject scriptable = ((XmlPage) doc).getScriptableObject();
if (ScriptableObject.hasProperty(scriptable, "getProperty")) {
final Object selectionNS =
ScriptableObject.callMethod(scriptable, "getProperty", new Object[]{"SelectionNamespaces"});
if (selectionNS != null && !selectionNS.toString().isEmpty()) {
final Map<String, String> namespaces = parseSelectionNamespaces(selectionNS.toString());
if (namespaces != null) {
prefixResolver = new PrefixResolver() {
#Override
public String getBaseIdentifier() {
return namespaces.get("");
}
#Override
public String getNamespaceForPrefix(final String prefix) {
return namespaces.get(prefix);
}
#Override
public String getNamespaceForPrefix(final String prefix, final Node node) {
throw new UnsupportedOperationException();
}
#Override
public boolean handlesNullPrefixes() {
return false;
}
};
}
}
}
}
}
return XPathUtils.getByXPath(this, xpathExpr, prefixResolver);
}
The generics here are used to infer the type T into the XPathUtils.getByXPath method, which signature is
public static <T> List<T> getByXPath(DomNode node, String xpathExpr, PrefixResolver resolver)
When compiler looks at this statement
return XPathUtils.getByXPath(this, xpathExpr, prefixResolver);
it infers the T type from the outer getByXPath method, which in turn infers T from its invocation.
So if you call List<HtmlDivision> list = page.getByXPath(expr); then HtmlDivision will be inferred into getByXPath and then into XPathUtils.getByXPath
It's used for the return type (List<T>), so the returned List will have that specific generic type. This also means that XPathUtils.getByXPath(…) returns a List<T>, since that's what it's returning at the bottom of the method, so it's used inside the method there.
So here's a slightly tricky question (for me).
I have a generic object. Call it MyObject. This object has a method which returns something of the type T:
public class MyObject<T>
{
private T _t;
public MyObject(T t)
{
_t = t;
}
//...
public T get()
{
return _t;
}
}
(Obviously my "MyObject" does a bit more but that's the gist).
Now, I want to have a map of this type:
Map<String, MyObject<?>> m = new HashMap<>();
I want to be able to fetch maps using some predefined string name, and these maps can be of any MyObject. For example, I could call:
m.put("map_1", new MyObject<String>("String"));
m.put("map_2", new MyObject<Integer>(new Integer(3));
m.put("map_3", new MyObject<Long>(new Long(5));
etc.
But - and here's the tricky part - I want the map to "remember" the parameterized type of MyObject when I fetch some value from the map. Using
m.get("map_1");
would return a
MyObject<Object>
type, since the map was defined as containing
MyObject<?>
values. Thus:
m.get("map_1").get() // <-- This is an Object, not a String!
What modification (if any) is possible, in order to be able to get the correct - full - information regarding the MyObject fetched object, such that invoking the last line (m.get("map_1")) would return a
MyObject<String>
Thanks :)
Amir.
Typesafe Heterogeneous Containers from Joshua Bloch's Effective Java might work here. Basically you add a Class object to represent the type.
public class MyObject<T>
{
private T _t;
private Class<T> type;
public MyObject( Class<T> type, T t)
{
_t = t;
this.type = type;
}
//...
public T get()
{
return _t;
}
public Class<T> getType() { return type; }
}
Then you could do something like this:
public <T> T get( Map<String, MyObject<?>> map, String key, Class<T> type ) {
return type.cast( m.get( key ).get() );
}
Which is safe and will compile, but will throw a runtime error if you get the type wrong.
(Note I didn't actually compile that, so I might have syntax errors floating around. But most folks don't know how to use Class to cast objects.)
You can get the class.
Class c = m.get("map_1").get().getClass();
if (String.class.equals(c)) {
System.out.println("its a String");
}
Here is a full test.
public class GenericsTest {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
Map<String, MyObject<?>> map = new HashMap<>();
MyObject<String> obj = new MyObject<>("hello");
map.put("greeting", obj);
Class c = map.get("greeting").get().getClass();
if (String.class.equals(c)) {
System.out.println("its a String");
}
}
static class MyObject<T> {
T t;
public MyObject(T t) {
this.t = t;
}
T get() {
return t;
}
}
}
The type system only knows about types, not objects, and therefore can not distinguish "key1" from "key2", because both are of type String.
If keys have different types, the easiest way is to encapsulate a weakly typed map, and use reflective casts to prove to the compiler the types are correct:
class Favorites {
private Map<Class<?>,?> map = new HashMap<>();
<V> V get(Class<V> clazz) {
return clazz.cast(map.get(clazz));
}
<V> void put(Class<V> clazz, V value) {
map.put(clazz, value);
}
}
Favorites favs = new Favorites();
favs.put(String.class, "hello");
favs.put(Integer.class, 42);
favs.get(String.class).charAt(1);
I know this sounds a little crazy but here it is. I have an enum type that represents represents a bunch of different properties. Each could be just a string but it would be nice to enforce some kind of type safety. So basically check the type associated with each enum value and throw an exception if there is a mismatch. I guess it could be done with instance of but I am curious if there is another way to do this without instanceof. I know that may not be possible but I am curious.
Edit, I created a new example that I think illustrates what I am asking better:
public class CmisProperties {
public enum CmisPropEnum{
Name (PropertyIds.NAME, new String() ),
CreatedBy (PropertyIds.CREATED_BY, new String() ),
CreationDate (PropertyIds.CREATION_DATE, new Date() ),
LastModifiedBy (PropertyIds.LAST_MODIFIED_BY, new String() ),
LastModificationDate (PropertyIds.LAST_MODIFICATION_DATE, new Date() ),
ChangeToken (PropertyIds.CHANGE_TOKEN, new String() );
private String propId;
CmisPropEnum ( String propId , Object templateObject ){
this.propId = propId;
}
public <T> String getPropId(){
return propId;
}
}
private Map<CmisPropEnum, Object> propertyMap = new HashMap<CmisPropEnum, Object>();
public Object getProperty(CmisPropEnum propEnum){
return propertyMap.get(propEnum.getPropId());
}
public void setProperty( CmisPropEnum propEnum, Object value){
propertyMap.put(propEnum, value);
}
}
Later on I want this to happen:
CmisProperties props = new CmisProperties();
/* This causes a compile time exception */
props.setProperty(CmisPropEnum.CreationDate, "foobar" );
/* This I want to be ok, because the type matches that in the enum */
props.setProperty(CmisPropEnum.CreationDate, new Date() );
Check out Josh Bloch's Effective Java, Item 29, where he describes a "typesafe heterogeneous container" that he calls Favorites. The API is
public class Favorites {
public <T> void putFavorite(Class<T> type, T instance);
public <T> T getFavorite(Class<T> type);
}
I think it would fit your needs (probably change the name???). You could call
favorite.putFavorite(Name.getClass(), "Fred");
favorite.putFavorite(ADate.getClass(), new Date(1234));
and later
Date date = favorite.getFavorite(ADate.getClass());
As already mentioned by irreputable, you need classes to have variability based on types (i.e. generics). This is a corresponding generic version of your example:
public class Properties {
public static class Property<E> {
private Property(String name) { this.name = name; }
private final String name;
public String getName() { return name; }
}
public static final Property<String> NAME = new Property<String>("name");
// ... other properties
private Map<Property<?>, Object> propertyMap =
new HashMap<Property<?>, Object>();
#SuppressWarnings("unchecked")
public <E> E getProperty(Property<E> property){
return (E) propertyMap.get(property);
}
public <E> void setProperty(Property<E> property, E value){
propertyMap.put(property, value);
}
}
The usage is type-safe and checked at compile-time:
Properties p = new Properties();
p.setProperty(Properties.NAME, "a string"); // only strings allowed for NAME
String s = p.getProperty(Properties.NAME); // can only get strings for NAME
Enums can't be generic, so we need a normal class
public class Prop<T>
{
// some predefined props
static public final Prop<String> NAME = new Prop<>("Name", String.class);
...
public Prop(String name, Class<T> type) // it's ok, anyone can create new kind of Prop
{...}
Class<T> getClassT() {...}
}
then set/get property methods can have stronger static type checking:
private Map< Prop,Object > propMap = new HashMap<>();
public <T> void setProperty(Prop<T> key, T value){
propMap.put(key, value);
}
#SuppressWarnings("unchecked")
public <T> T getProperty(Prop<T> key)
{
return (T)propMap.get(key);
}
so that this won't compile
setProperty(Prop.NAME, new Integer(1)); // fail
int x = getProperty(Prop.NAME); //fail
note that, each entry in the propMap has a key Prop<X> and value X for some X, and X can be different from entry to entry. We cannot really express that constraint on Map in Java; but the constraint is indeed enforced by app logic (i.e. setProperty() only inserts such entries)
In getProperty we must suppress unchecked warning. It is justified, since we know the value for the key must be of type T, due to previously mentioned constraint. One trick to avoid explicitly suppressing the warning is by Class.cast()
public <T> T getProperty(Prop<T> key)
{
return key.getClassT().cast( propMap.get(key) );
}
but it's only a trick since essentially we moved #SupressWarnings to Class.cast(). This is a worse version in performance and in semantic clarity.