When generics were added to 1.5, java.lang.reflect
added a Type
interface with various subtypes to represent types. Class
is retrofitted to implement Type
for the pre-1.5 types. Type
subtypes are available for the new types of generic type from 1.5.
This is all well and good. A bit awkward as Type
has to be downcast to do anything useful, but doable with trial, error, fiddling and (automatic) testing. Except when it comes to implementation...
How should equals
and hashCode
be implemented. The API description for the ParameterizedType
subtype of Type
says:
Instances of classes that implement this interface must implement an equals() method that equates any two instances that share the same generic type declaration and have equal type parameters.
(I guess that means getActualTypeArguments
and getRawType
but not getOwnerType
??)
We know from the general contract of java.lang.Object
that hashCode
must also be implemented, but there appears to be no specification as what values this method should produce.
None of the other subtype of Type
appear to mention equals
or hashCode
, other than that Class
has distinct instances per value.
So what do I put in my equals
and hashCode
?
(In case you are wondering, I am attempting to substitute type parameters for actual types. So if I know at runtime TypeVariable<?>
T
is Class<?>
String
then I want to replace Type
s, so List<T>
becomes List<String>
, T[]
becomes String[]
, List<T>[]
(can happen!) becomes List<String>[]
, etc.)
Or do I have to create my own parallel type type hierarchy (without duplicating Type
for presumed legal reasons)? (Is there a library?)
Edit: There's been a couple of queries as to why I need this. Indeed, why look at generic type information at all?
I'm starting with a non-generic class/interface type. (If you want a parameterised types, such as List<String>
then you can always add a layer of indirection with a new class.) I am then following fields or methods. Those may reference parameterised types. So long as they aren't using wildcards, I can still work out actual static types when faced with the likes of T
.
In this way I can do everything with high quality, static typing. None of these instanceof
dynamic type checks in sight.
The specific usage in my case is serialisation. But it could apply to any other reasonable use of reflection, such as testing.
Current state of code I am using for the substitution below. typeMap
is a Map<String,Type>
. Present as an "as is" snapshot. Not tidied up in anyway at all (throw null;
if you don't believe me).
Type substitute(Type type) {
if (type instanceof TypeVariable<?>) {
Type actualType = typeMap.get(((TypeVariable<?>)type).getName());
if (actualType instanceof TypeVariable<?>) { throw null; }
if (actualType == null) {
throw new IllegalArgumentException("Type variable not found");
} else if (actualType instanceof TypeVariable<?>) {
throw new IllegalArgumentException("TypeVariable shouldn't substitute for a TypeVariable");
} else {
return actualType;
}
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType)type;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
int len = actualTypeArguments.length;
Type[] actualActualTypeArguments = new Type[len];
for (int i=0; i<len; ++i) {
actualActualTypeArguments[i] = substitute(actualTypeArguments[i]);
}
// This will always be a Class, wont it? No higher-kinded types here, thank you very much.
Type actualRawType = substitute(parameterizedType.getRawType());
Type actualOwnerType = substitute(parameterizedType.getOwnerType());
return new ParameterizedType() {
public Type[] getActualTypeArguments() {
return actualActualTypeArguments.clone();
}
public Type getRawType() {
return actualRawType;
}
public Type getOwnerType() {
return actualOwnerType;
}
// Interface description requires equals method.
@Override public boolean equals(Object obj) {
if (!(obj instanceof ParameterizedType)) {
return false;
}
ParameterizedType other = (ParameterizedType)obj;
return
Arrays.equals(this.getActualTypeArguments(), other.getActualTypeArguments()) &&
this.getOwnerType().equals(other.getOwnerType()) &&
this.getRawType().equals(other.getRawType());
}
};
} else if (type instanceof GenericArrayType) {
GenericArrayType genericArrayType = (GenericArrayType)type;
Type componentType = genericArrayType.getGenericComponentType();
Type actualComponentType = substitute(componentType);
if (actualComponentType instanceof TypeVariable<?>) { throw null; }
return new GenericArrayType() {
// !! getTypeName? toString? equals? hashCode?
public Type getGenericComponentType() {
return actualComponentType;
}
// Apparently don't have to provide an equals, but we do need to.
@Override public boolean equals(Object obj) {
if (!(obj instanceof GenericArrayType)) {
return false;
}
GenericArrayType other = (GenericArrayType)obj;
return
this.getGenericComponentType().equals(other.getGenericComponentType());
}
};
} else {
return type;
}
}