How should Type type-hierarchy types be implemented?
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;
}
}
java reflection
|
show 15 more comments
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;
}
}
java reflection
Why do you need to add equals/hashCode? Are you sure the builtin types are not correct?
– Peter Lawrey
Dec 27 '18 at 16:22
@PeterLawrey I need to check that the Types match. How could the builtin implementations be equal when there doesn't appear to be a spec. I mean you could probably work out what the implementation does by observation - it's not something you are going to use cryptography on.
– Tom Hawtin - tackline
Dec 27 '18 at 16:25
1
The implementations appear to have these methods from reading the source. You could try creating your own system, but that seems like a lot of work given it might never be needed. A simple solution is to compare thetoString()
results.
– Peter Lawrey
Dec 27 '18 at 16:30
@PeterLawrey I'm not sure that thetoString
is guaranteed to be unique. (Also bleurgh, what a hack. And not going to fit in myHashMap
nicely.)
– Tom Hawtin - tackline
Dec 27 '18 at 16:32
1
Additionally, be extra careful with recursive types, i.e.<U extends Comparable<U>>
– Federico Peralta Schaffner
Jan 4 at 13:55
|
show 15 more comments
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;
}
}
java reflection
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;
}
}
java reflection
java reflection
edited Jan 4 at 12:04
Tom Hawtin - tackline
asked Dec 27 '18 at 16:18


Tom Hawtin - tacklineTom Hawtin - tackline
127k28184274
127k28184274
Why do you need to add equals/hashCode? Are you sure the builtin types are not correct?
– Peter Lawrey
Dec 27 '18 at 16:22
@PeterLawrey I need to check that the Types match. How could the builtin implementations be equal when there doesn't appear to be a spec. I mean you could probably work out what the implementation does by observation - it's not something you are going to use cryptography on.
– Tom Hawtin - tackline
Dec 27 '18 at 16:25
1
The implementations appear to have these methods from reading the source. You could try creating your own system, but that seems like a lot of work given it might never be needed. A simple solution is to compare thetoString()
results.
– Peter Lawrey
Dec 27 '18 at 16:30
@PeterLawrey I'm not sure that thetoString
is guaranteed to be unique. (Also bleurgh, what a hack. And not going to fit in myHashMap
nicely.)
– Tom Hawtin - tackline
Dec 27 '18 at 16:32
1
Additionally, be extra careful with recursive types, i.e.<U extends Comparable<U>>
– Federico Peralta Schaffner
Jan 4 at 13:55
|
show 15 more comments
Why do you need to add equals/hashCode? Are you sure the builtin types are not correct?
– Peter Lawrey
Dec 27 '18 at 16:22
@PeterLawrey I need to check that the Types match. How could the builtin implementations be equal when there doesn't appear to be a spec. I mean you could probably work out what the implementation does by observation - it's not something you are going to use cryptography on.
– Tom Hawtin - tackline
Dec 27 '18 at 16:25
1
The implementations appear to have these methods from reading the source. You could try creating your own system, but that seems like a lot of work given it might never be needed. A simple solution is to compare thetoString()
results.
– Peter Lawrey
Dec 27 '18 at 16:30
@PeterLawrey I'm not sure that thetoString
is guaranteed to be unique. (Also bleurgh, what a hack. And not going to fit in myHashMap
nicely.)
– Tom Hawtin - tackline
Dec 27 '18 at 16:32
1
Additionally, be extra careful with recursive types, i.e.<U extends Comparable<U>>
– Federico Peralta Schaffner
Jan 4 at 13:55
Why do you need to add equals/hashCode? Are you sure the builtin types are not correct?
– Peter Lawrey
Dec 27 '18 at 16:22
Why do you need to add equals/hashCode? Are you sure the builtin types are not correct?
– Peter Lawrey
Dec 27 '18 at 16:22
@PeterLawrey I need to check that the Types match. How could the builtin implementations be equal when there doesn't appear to be a spec. I mean you could probably work out what the implementation does by observation - it's not something you are going to use cryptography on.
– Tom Hawtin - tackline
Dec 27 '18 at 16:25
@PeterLawrey I need to check that the Types match. How could the builtin implementations be equal when there doesn't appear to be a spec. I mean you could probably work out what the implementation does by observation - it's not something you are going to use cryptography on.
– Tom Hawtin - tackline
Dec 27 '18 at 16:25
1
1
The implementations appear to have these methods from reading the source. You could try creating your own system, but that seems like a lot of work given it might never be needed. A simple solution is to compare the
toString()
results.– Peter Lawrey
Dec 27 '18 at 16:30
The implementations appear to have these methods from reading the source. You could try creating your own system, but that seems like a lot of work given it might never be needed. A simple solution is to compare the
toString()
results.– Peter Lawrey
Dec 27 '18 at 16:30
@PeterLawrey I'm not sure that the
toString
is guaranteed to be unique. (Also bleurgh, what a hack. And not going to fit in my HashMap
nicely.)– Tom Hawtin - tackline
Dec 27 '18 at 16:32
@PeterLawrey I'm not sure that the
toString
is guaranteed to be unique. (Also bleurgh, what a hack. And not going to fit in my HashMap
nicely.)– Tom Hawtin - tackline
Dec 27 '18 at 16:32
1
1
Additionally, be extra careful with recursive types, i.e.
<U extends Comparable<U>>
– Federico Peralta Schaffner
Jan 4 at 13:55
Additionally, be extra careful with recursive types, i.e.
<U extends Comparable<U>>
– Federico Peralta Schaffner
Jan 4 at 13:55
|
show 15 more comments
2 Answers
2
active
oldest
votes
I've been solving this problem in unsatisfying ways for 10 years. First with Guice’s MoreTypes.java
, copy-pasted and revised with Gson’s GsonTypes.java
, and again in Moshi’s Util.java
.
Moshi has my best approach, which isn't to say that it's good.
You can't call equals()
on arbitrary implementations of Type and expect it to work.
This is because the Java Types APIs offers multiple incompatible ways to model arrays of simple classes. You can make a Date
as a Class<Date>
or as a GenericArrayType
whose component type is Date
. I believe you’ll get the former from reflection on a field of type Date
and the latter from reflection as the parameter of a field of type List<Date>
.
The hash codes aren't specified.
I also got to work on the implementation of these classes that Android uses. Very early versions of Android have different hash codes vs. Java, but everything you'll find in the wild today uses the same hash codes as Java.
The toString methods aren't good
If you're using types in error messages it sucks to have to write special code to print them nicely.
Copy Paste and Be Sad
My recommendation is to not use equals() + hashCode() with unknown Type implementations. Use a canonicalize function to convert into a specific known implementation and only compare within the ones you control.
add a comment |
Here is a little experiment that relies directly on the Sun API and reflection (that is, it uses reflection to work with classes that implement reflection):
import java.lang.Class;
import java.lang.reflect.*;
import java.util.Arrays;
import sun.reflect.generics.reflectiveObjects.*;
class Types {
private static Constructor<ParameterizedTypeImpl> PARAMETERIZED_TYPE_CONS =
((Constructor<ParameterizedTypeImpl>)
ParameterizedTypeImpl
.class
.getDeclaredConstructors()
[0]
);
static {
PARAMETERIZED_TYPE_CONS.setAccessible(true);
}
/**
* Helper method for invocation of the
*`ParameterizedTypeImpl` constructor.
*/
public static ParameterizedType parameterizedType(
Class<?> raw,
Type paramTypes,
Type owner
) {
try {
return PARAMETERIZED_TYPE_CONS.newInstance(raw, paramTypes, owner);
} catch (Exception e) {
throw new Error("TODO: better error handling", e);
}
}
// (similarly for `GenericArrayType`, `WildcardType` etc.)
/** Substitution of type variables. */
public static Type substituteTypeVariable(
final Type inType,
final TypeVariable<?> variable,
final Type replaceBy
) {
if (inType instanceof TypeVariable<?>) {
return replaceBy;
} else if (inType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) inType;
return parameterizedType(
((Class<?>) pt.getRawType()),
Arrays.stream(pt.getActualTypeArguments())
.map((Type x) -> substituteTypeVariable(x, variable, replaceBy))
.toArray(Type::new),
pt.getOwnerType()
);
} else {
throw new Error("TODO: all other cases");
}
}
// example
public static void main(String args) throws InstantiationException {
// type in which we will replace a variable is `List<E>`
Type t =
java.util.LinkedList
.class
.getGenericInterfaces()
[0];
// this is the variable `E` (hopefully, stability not guaranteed)
TypeVariable<?> v =
((Class<?>)
((ParameterizedType) t)
.getRawType()
)
.getTypeParameters()
[0];
// This should become `List<String>`
Type s = substituteTypeVariable(t, v, String.class);
System.out.println("before: " + t);
System.out.println("after: " + s);
}
}
The result of substitution of E
by String
in List<E>
looks as follows:
before: java.util.List<E>
after: java.util.List<java.lang.String>
The main idea is as follows:
- Get the
sun.reflect.generics.reflectiveObjects.XyzImpl
classes - Get their constructors, ensure that they are
accessible
- Wrap the constructor
.newInstance
invocations in helper methods - Use the helper methods in a simple recursive method called
substituteTypeVariable
that rebuilds theType
-expressions with type variables substituted by concrete types.
I didn't implement every single case, but it should work with more complicated nested types too (because of the recursive invocation of substituteTypeVariable
).
The compiler doesn't really like this approach, it generates warnings about the usage of the internal Sun API:
warning: ParameterizedTypeImpl is internal proprietary API and may be removed in a future release
but, there is a @SuppressWarnings
for that.
The above Java code has been obtained by translating the following little Scala snippet (that's the reason why the Java code might look a bit strange and not entirely Java-idiomatic):
object Types {
import scala.language.existentials // suppress warnings
import java.lang.Class
import java.lang.reflect.{Array => _, _}
import sun.reflect.generics.reflectiveObjects._
private val ParameterizedTypeCons =
classOf[ParameterizedTypeImpl]
.getDeclaredConstructors
.head
.asInstanceOf[Constructor[ParameterizedTypeImpl]]
ParameterizedTypeCons.setAccessible(true)
/** Helper method for invocation of the `ParameterizedTypeImpl` constructor. */
def parameterizedType(raw: Class[_], paramTypes: Array[Type], owner: Type)
: ParameterizedType = {
ParameterizedTypeCons.newInstance(raw, paramTypes, owner)
}
// (similarly for `GenericArrayType`, `WildcardType` etc.)
/** Substitution of type variables. */
def substituteTypeVariable(
inType: Type,
variable: TypeVariable[_],
replaceBy: Type
): Type = {
inType match {
case v: TypeVariable[_] => replaceBy
case pt: ParameterizedType => parameterizedType(
pt.getRawType.asInstanceOf[Class[_]],
pt.getActualTypeArguments.map(substituteTypeVariable(_, variable, replaceBy)),
pt.getOwnerType
)
case sthElse => throw new NotImplementedError()
}
}
// example
def main(args: Array[String]): Unit = {
// type in which we will replace a variable is `List<E>`
val t =
classOf[java.util.LinkedList[_]]
.getGenericInterfaces
.head
// this is the variable `E` (hopefully, stability not guaranteed)
val v =
t
.asInstanceOf[ParameterizedType]
.getRawType
.asInstanceOf[Class[_]] // should be `List<E>` with parameter
.getTypeParameters
.head // should be `E`
// This should become `List<String>`
val s = substituteTypeVariable(t, v, classOf[String])
println("before: " + t)
println("after: " + s)
}
}
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53947920%2fhow-should-type-type-hierarchy-types-be-implemented%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
I've been solving this problem in unsatisfying ways for 10 years. First with Guice’s MoreTypes.java
, copy-pasted and revised with Gson’s GsonTypes.java
, and again in Moshi’s Util.java
.
Moshi has my best approach, which isn't to say that it's good.
You can't call equals()
on arbitrary implementations of Type and expect it to work.
This is because the Java Types APIs offers multiple incompatible ways to model arrays of simple classes. You can make a Date
as a Class<Date>
or as a GenericArrayType
whose component type is Date
. I believe you’ll get the former from reflection on a field of type Date
and the latter from reflection as the parameter of a field of type List<Date>
.
The hash codes aren't specified.
I also got to work on the implementation of these classes that Android uses. Very early versions of Android have different hash codes vs. Java, but everything you'll find in the wild today uses the same hash codes as Java.
The toString methods aren't good
If you're using types in error messages it sucks to have to write special code to print them nicely.
Copy Paste and Be Sad
My recommendation is to not use equals() + hashCode() with unknown Type implementations. Use a canonicalize function to convert into a specific known implementation and only compare within the ones you control.
add a comment |
I've been solving this problem in unsatisfying ways for 10 years. First with Guice’s MoreTypes.java
, copy-pasted and revised with Gson’s GsonTypes.java
, and again in Moshi’s Util.java
.
Moshi has my best approach, which isn't to say that it's good.
You can't call equals()
on arbitrary implementations of Type and expect it to work.
This is because the Java Types APIs offers multiple incompatible ways to model arrays of simple classes. You can make a Date
as a Class<Date>
or as a GenericArrayType
whose component type is Date
. I believe you’ll get the former from reflection on a field of type Date
and the latter from reflection as the parameter of a field of type List<Date>
.
The hash codes aren't specified.
I also got to work on the implementation of these classes that Android uses. Very early versions of Android have different hash codes vs. Java, but everything you'll find in the wild today uses the same hash codes as Java.
The toString methods aren't good
If you're using types in error messages it sucks to have to write special code to print them nicely.
Copy Paste and Be Sad
My recommendation is to not use equals() + hashCode() with unknown Type implementations. Use a canonicalize function to convert into a specific known implementation and only compare within the ones you control.
add a comment |
I've been solving this problem in unsatisfying ways for 10 years. First with Guice’s MoreTypes.java
, copy-pasted and revised with Gson’s GsonTypes.java
, and again in Moshi’s Util.java
.
Moshi has my best approach, which isn't to say that it's good.
You can't call equals()
on arbitrary implementations of Type and expect it to work.
This is because the Java Types APIs offers multiple incompatible ways to model arrays of simple classes. You can make a Date
as a Class<Date>
or as a GenericArrayType
whose component type is Date
. I believe you’ll get the former from reflection on a field of type Date
and the latter from reflection as the parameter of a field of type List<Date>
.
The hash codes aren't specified.
I also got to work on the implementation of these classes that Android uses. Very early versions of Android have different hash codes vs. Java, but everything you'll find in the wild today uses the same hash codes as Java.
The toString methods aren't good
If you're using types in error messages it sucks to have to write special code to print them nicely.
Copy Paste and Be Sad
My recommendation is to not use equals() + hashCode() with unknown Type implementations. Use a canonicalize function to convert into a specific known implementation and only compare within the ones you control.
I've been solving this problem in unsatisfying ways for 10 years. First with Guice’s MoreTypes.java
, copy-pasted and revised with Gson’s GsonTypes.java
, and again in Moshi’s Util.java
.
Moshi has my best approach, which isn't to say that it's good.
You can't call equals()
on arbitrary implementations of Type and expect it to work.
This is because the Java Types APIs offers multiple incompatible ways to model arrays of simple classes. You can make a Date
as a Class<Date>
or as a GenericArrayType
whose component type is Date
. I believe you’ll get the former from reflection on a field of type Date
and the latter from reflection as the parameter of a field of type List<Date>
.
The hash codes aren't specified.
I also got to work on the implementation of these classes that Android uses. Very early versions of Android have different hash codes vs. Java, but everything you'll find in the wild today uses the same hash codes as Java.
The toString methods aren't good
If you're using types in error messages it sucks to have to write special code to print them nicely.
Copy Paste and Be Sad
My recommendation is to not use equals() + hashCode() with unknown Type implementations. Use a canonicalize function to convert into a specific known implementation and only compare within the ones you control.
answered Jan 4 at 12:43
Jesse WilsonJesse Wilson
25.5k27088
25.5k27088
add a comment |
add a comment |
Here is a little experiment that relies directly on the Sun API and reflection (that is, it uses reflection to work with classes that implement reflection):
import java.lang.Class;
import java.lang.reflect.*;
import java.util.Arrays;
import sun.reflect.generics.reflectiveObjects.*;
class Types {
private static Constructor<ParameterizedTypeImpl> PARAMETERIZED_TYPE_CONS =
((Constructor<ParameterizedTypeImpl>)
ParameterizedTypeImpl
.class
.getDeclaredConstructors()
[0]
);
static {
PARAMETERIZED_TYPE_CONS.setAccessible(true);
}
/**
* Helper method for invocation of the
*`ParameterizedTypeImpl` constructor.
*/
public static ParameterizedType parameterizedType(
Class<?> raw,
Type paramTypes,
Type owner
) {
try {
return PARAMETERIZED_TYPE_CONS.newInstance(raw, paramTypes, owner);
} catch (Exception e) {
throw new Error("TODO: better error handling", e);
}
}
// (similarly for `GenericArrayType`, `WildcardType` etc.)
/** Substitution of type variables. */
public static Type substituteTypeVariable(
final Type inType,
final TypeVariable<?> variable,
final Type replaceBy
) {
if (inType instanceof TypeVariable<?>) {
return replaceBy;
} else if (inType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) inType;
return parameterizedType(
((Class<?>) pt.getRawType()),
Arrays.stream(pt.getActualTypeArguments())
.map((Type x) -> substituteTypeVariable(x, variable, replaceBy))
.toArray(Type::new),
pt.getOwnerType()
);
} else {
throw new Error("TODO: all other cases");
}
}
// example
public static void main(String args) throws InstantiationException {
// type in which we will replace a variable is `List<E>`
Type t =
java.util.LinkedList
.class
.getGenericInterfaces()
[0];
// this is the variable `E` (hopefully, stability not guaranteed)
TypeVariable<?> v =
((Class<?>)
((ParameterizedType) t)
.getRawType()
)
.getTypeParameters()
[0];
// This should become `List<String>`
Type s = substituteTypeVariable(t, v, String.class);
System.out.println("before: " + t);
System.out.println("after: " + s);
}
}
The result of substitution of E
by String
in List<E>
looks as follows:
before: java.util.List<E>
after: java.util.List<java.lang.String>
The main idea is as follows:
- Get the
sun.reflect.generics.reflectiveObjects.XyzImpl
classes - Get their constructors, ensure that they are
accessible
- Wrap the constructor
.newInstance
invocations in helper methods - Use the helper methods in a simple recursive method called
substituteTypeVariable
that rebuilds theType
-expressions with type variables substituted by concrete types.
I didn't implement every single case, but it should work with more complicated nested types too (because of the recursive invocation of substituteTypeVariable
).
The compiler doesn't really like this approach, it generates warnings about the usage of the internal Sun API:
warning: ParameterizedTypeImpl is internal proprietary API and may be removed in a future release
but, there is a @SuppressWarnings
for that.
The above Java code has been obtained by translating the following little Scala snippet (that's the reason why the Java code might look a bit strange and not entirely Java-idiomatic):
object Types {
import scala.language.existentials // suppress warnings
import java.lang.Class
import java.lang.reflect.{Array => _, _}
import sun.reflect.generics.reflectiveObjects._
private val ParameterizedTypeCons =
classOf[ParameterizedTypeImpl]
.getDeclaredConstructors
.head
.asInstanceOf[Constructor[ParameterizedTypeImpl]]
ParameterizedTypeCons.setAccessible(true)
/** Helper method for invocation of the `ParameterizedTypeImpl` constructor. */
def parameterizedType(raw: Class[_], paramTypes: Array[Type], owner: Type)
: ParameterizedType = {
ParameterizedTypeCons.newInstance(raw, paramTypes, owner)
}
// (similarly for `GenericArrayType`, `WildcardType` etc.)
/** Substitution of type variables. */
def substituteTypeVariable(
inType: Type,
variable: TypeVariable[_],
replaceBy: Type
): Type = {
inType match {
case v: TypeVariable[_] => replaceBy
case pt: ParameterizedType => parameterizedType(
pt.getRawType.asInstanceOf[Class[_]],
pt.getActualTypeArguments.map(substituteTypeVariable(_, variable, replaceBy)),
pt.getOwnerType
)
case sthElse => throw new NotImplementedError()
}
}
// example
def main(args: Array[String]): Unit = {
// type in which we will replace a variable is `List<E>`
val t =
classOf[java.util.LinkedList[_]]
.getGenericInterfaces
.head
// this is the variable `E` (hopefully, stability not guaranteed)
val v =
t
.asInstanceOf[ParameterizedType]
.getRawType
.asInstanceOf[Class[_]] // should be `List<E>` with parameter
.getTypeParameters
.head // should be `E`
// This should become `List<String>`
val s = substituteTypeVariable(t, v, classOf[String])
println("before: " + t)
println("after: " + s)
}
}
add a comment |
Here is a little experiment that relies directly on the Sun API and reflection (that is, it uses reflection to work with classes that implement reflection):
import java.lang.Class;
import java.lang.reflect.*;
import java.util.Arrays;
import sun.reflect.generics.reflectiveObjects.*;
class Types {
private static Constructor<ParameterizedTypeImpl> PARAMETERIZED_TYPE_CONS =
((Constructor<ParameterizedTypeImpl>)
ParameterizedTypeImpl
.class
.getDeclaredConstructors()
[0]
);
static {
PARAMETERIZED_TYPE_CONS.setAccessible(true);
}
/**
* Helper method for invocation of the
*`ParameterizedTypeImpl` constructor.
*/
public static ParameterizedType parameterizedType(
Class<?> raw,
Type paramTypes,
Type owner
) {
try {
return PARAMETERIZED_TYPE_CONS.newInstance(raw, paramTypes, owner);
} catch (Exception e) {
throw new Error("TODO: better error handling", e);
}
}
// (similarly for `GenericArrayType`, `WildcardType` etc.)
/** Substitution of type variables. */
public static Type substituteTypeVariable(
final Type inType,
final TypeVariable<?> variable,
final Type replaceBy
) {
if (inType instanceof TypeVariable<?>) {
return replaceBy;
} else if (inType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) inType;
return parameterizedType(
((Class<?>) pt.getRawType()),
Arrays.stream(pt.getActualTypeArguments())
.map((Type x) -> substituteTypeVariable(x, variable, replaceBy))
.toArray(Type::new),
pt.getOwnerType()
);
} else {
throw new Error("TODO: all other cases");
}
}
// example
public static void main(String args) throws InstantiationException {
// type in which we will replace a variable is `List<E>`
Type t =
java.util.LinkedList
.class
.getGenericInterfaces()
[0];
// this is the variable `E` (hopefully, stability not guaranteed)
TypeVariable<?> v =
((Class<?>)
((ParameterizedType) t)
.getRawType()
)
.getTypeParameters()
[0];
// This should become `List<String>`
Type s = substituteTypeVariable(t, v, String.class);
System.out.println("before: " + t);
System.out.println("after: " + s);
}
}
The result of substitution of E
by String
in List<E>
looks as follows:
before: java.util.List<E>
after: java.util.List<java.lang.String>
The main idea is as follows:
- Get the
sun.reflect.generics.reflectiveObjects.XyzImpl
classes - Get their constructors, ensure that they are
accessible
- Wrap the constructor
.newInstance
invocations in helper methods - Use the helper methods in a simple recursive method called
substituteTypeVariable
that rebuilds theType
-expressions with type variables substituted by concrete types.
I didn't implement every single case, but it should work with more complicated nested types too (because of the recursive invocation of substituteTypeVariable
).
The compiler doesn't really like this approach, it generates warnings about the usage of the internal Sun API:
warning: ParameterizedTypeImpl is internal proprietary API and may be removed in a future release
but, there is a @SuppressWarnings
for that.
The above Java code has been obtained by translating the following little Scala snippet (that's the reason why the Java code might look a bit strange and not entirely Java-idiomatic):
object Types {
import scala.language.existentials // suppress warnings
import java.lang.Class
import java.lang.reflect.{Array => _, _}
import sun.reflect.generics.reflectiveObjects._
private val ParameterizedTypeCons =
classOf[ParameterizedTypeImpl]
.getDeclaredConstructors
.head
.asInstanceOf[Constructor[ParameterizedTypeImpl]]
ParameterizedTypeCons.setAccessible(true)
/** Helper method for invocation of the `ParameterizedTypeImpl` constructor. */
def parameterizedType(raw: Class[_], paramTypes: Array[Type], owner: Type)
: ParameterizedType = {
ParameterizedTypeCons.newInstance(raw, paramTypes, owner)
}
// (similarly for `GenericArrayType`, `WildcardType` etc.)
/** Substitution of type variables. */
def substituteTypeVariable(
inType: Type,
variable: TypeVariable[_],
replaceBy: Type
): Type = {
inType match {
case v: TypeVariable[_] => replaceBy
case pt: ParameterizedType => parameterizedType(
pt.getRawType.asInstanceOf[Class[_]],
pt.getActualTypeArguments.map(substituteTypeVariable(_, variable, replaceBy)),
pt.getOwnerType
)
case sthElse => throw new NotImplementedError()
}
}
// example
def main(args: Array[String]): Unit = {
// type in which we will replace a variable is `List<E>`
val t =
classOf[java.util.LinkedList[_]]
.getGenericInterfaces
.head
// this is the variable `E` (hopefully, stability not guaranteed)
val v =
t
.asInstanceOf[ParameterizedType]
.getRawType
.asInstanceOf[Class[_]] // should be `List<E>` with parameter
.getTypeParameters
.head // should be `E`
// This should become `List<String>`
val s = substituteTypeVariable(t, v, classOf[String])
println("before: " + t)
println("after: " + s)
}
}
add a comment |
Here is a little experiment that relies directly on the Sun API and reflection (that is, it uses reflection to work with classes that implement reflection):
import java.lang.Class;
import java.lang.reflect.*;
import java.util.Arrays;
import sun.reflect.generics.reflectiveObjects.*;
class Types {
private static Constructor<ParameterizedTypeImpl> PARAMETERIZED_TYPE_CONS =
((Constructor<ParameterizedTypeImpl>)
ParameterizedTypeImpl
.class
.getDeclaredConstructors()
[0]
);
static {
PARAMETERIZED_TYPE_CONS.setAccessible(true);
}
/**
* Helper method for invocation of the
*`ParameterizedTypeImpl` constructor.
*/
public static ParameterizedType parameterizedType(
Class<?> raw,
Type paramTypes,
Type owner
) {
try {
return PARAMETERIZED_TYPE_CONS.newInstance(raw, paramTypes, owner);
} catch (Exception e) {
throw new Error("TODO: better error handling", e);
}
}
// (similarly for `GenericArrayType`, `WildcardType` etc.)
/** Substitution of type variables. */
public static Type substituteTypeVariable(
final Type inType,
final TypeVariable<?> variable,
final Type replaceBy
) {
if (inType instanceof TypeVariable<?>) {
return replaceBy;
} else if (inType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) inType;
return parameterizedType(
((Class<?>) pt.getRawType()),
Arrays.stream(pt.getActualTypeArguments())
.map((Type x) -> substituteTypeVariable(x, variable, replaceBy))
.toArray(Type::new),
pt.getOwnerType()
);
} else {
throw new Error("TODO: all other cases");
}
}
// example
public static void main(String args) throws InstantiationException {
// type in which we will replace a variable is `List<E>`
Type t =
java.util.LinkedList
.class
.getGenericInterfaces()
[0];
// this is the variable `E` (hopefully, stability not guaranteed)
TypeVariable<?> v =
((Class<?>)
((ParameterizedType) t)
.getRawType()
)
.getTypeParameters()
[0];
// This should become `List<String>`
Type s = substituteTypeVariable(t, v, String.class);
System.out.println("before: " + t);
System.out.println("after: " + s);
}
}
The result of substitution of E
by String
in List<E>
looks as follows:
before: java.util.List<E>
after: java.util.List<java.lang.String>
The main idea is as follows:
- Get the
sun.reflect.generics.reflectiveObjects.XyzImpl
classes - Get their constructors, ensure that they are
accessible
- Wrap the constructor
.newInstance
invocations in helper methods - Use the helper methods in a simple recursive method called
substituteTypeVariable
that rebuilds theType
-expressions with type variables substituted by concrete types.
I didn't implement every single case, but it should work with more complicated nested types too (because of the recursive invocation of substituteTypeVariable
).
The compiler doesn't really like this approach, it generates warnings about the usage of the internal Sun API:
warning: ParameterizedTypeImpl is internal proprietary API and may be removed in a future release
but, there is a @SuppressWarnings
for that.
The above Java code has been obtained by translating the following little Scala snippet (that's the reason why the Java code might look a bit strange and not entirely Java-idiomatic):
object Types {
import scala.language.existentials // suppress warnings
import java.lang.Class
import java.lang.reflect.{Array => _, _}
import sun.reflect.generics.reflectiveObjects._
private val ParameterizedTypeCons =
classOf[ParameterizedTypeImpl]
.getDeclaredConstructors
.head
.asInstanceOf[Constructor[ParameterizedTypeImpl]]
ParameterizedTypeCons.setAccessible(true)
/** Helper method for invocation of the `ParameterizedTypeImpl` constructor. */
def parameterizedType(raw: Class[_], paramTypes: Array[Type], owner: Type)
: ParameterizedType = {
ParameterizedTypeCons.newInstance(raw, paramTypes, owner)
}
// (similarly for `GenericArrayType`, `WildcardType` etc.)
/** Substitution of type variables. */
def substituteTypeVariable(
inType: Type,
variable: TypeVariable[_],
replaceBy: Type
): Type = {
inType match {
case v: TypeVariable[_] => replaceBy
case pt: ParameterizedType => parameterizedType(
pt.getRawType.asInstanceOf[Class[_]],
pt.getActualTypeArguments.map(substituteTypeVariable(_, variable, replaceBy)),
pt.getOwnerType
)
case sthElse => throw new NotImplementedError()
}
}
// example
def main(args: Array[String]): Unit = {
// type in which we will replace a variable is `List<E>`
val t =
classOf[java.util.LinkedList[_]]
.getGenericInterfaces
.head
// this is the variable `E` (hopefully, stability not guaranteed)
val v =
t
.asInstanceOf[ParameterizedType]
.getRawType
.asInstanceOf[Class[_]] // should be `List<E>` with parameter
.getTypeParameters
.head // should be `E`
// This should become `List<String>`
val s = substituteTypeVariable(t, v, classOf[String])
println("before: " + t)
println("after: " + s)
}
}
Here is a little experiment that relies directly on the Sun API and reflection (that is, it uses reflection to work with classes that implement reflection):
import java.lang.Class;
import java.lang.reflect.*;
import java.util.Arrays;
import sun.reflect.generics.reflectiveObjects.*;
class Types {
private static Constructor<ParameterizedTypeImpl> PARAMETERIZED_TYPE_CONS =
((Constructor<ParameterizedTypeImpl>)
ParameterizedTypeImpl
.class
.getDeclaredConstructors()
[0]
);
static {
PARAMETERIZED_TYPE_CONS.setAccessible(true);
}
/**
* Helper method for invocation of the
*`ParameterizedTypeImpl` constructor.
*/
public static ParameterizedType parameterizedType(
Class<?> raw,
Type paramTypes,
Type owner
) {
try {
return PARAMETERIZED_TYPE_CONS.newInstance(raw, paramTypes, owner);
} catch (Exception e) {
throw new Error("TODO: better error handling", e);
}
}
// (similarly for `GenericArrayType`, `WildcardType` etc.)
/** Substitution of type variables. */
public static Type substituteTypeVariable(
final Type inType,
final TypeVariable<?> variable,
final Type replaceBy
) {
if (inType instanceof TypeVariable<?>) {
return replaceBy;
} else if (inType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) inType;
return parameterizedType(
((Class<?>) pt.getRawType()),
Arrays.stream(pt.getActualTypeArguments())
.map((Type x) -> substituteTypeVariable(x, variable, replaceBy))
.toArray(Type::new),
pt.getOwnerType()
);
} else {
throw new Error("TODO: all other cases");
}
}
// example
public static void main(String args) throws InstantiationException {
// type in which we will replace a variable is `List<E>`
Type t =
java.util.LinkedList
.class
.getGenericInterfaces()
[0];
// this is the variable `E` (hopefully, stability not guaranteed)
TypeVariable<?> v =
((Class<?>)
((ParameterizedType) t)
.getRawType()
)
.getTypeParameters()
[0];
// This should become `List<String>`
Type s = substituteTypeVariable(t, v, String.class);
System.out.println("before: " + t);
System.out.println("after: " + s);
}
}
The result of substitution of E
by String
in List<E>
looks as follows:
before: java.util.List<E>
after: java.util.List<java.lang.String>
The main idea is as follows:
- Get the
sun.reflect.generics.reflectiveObjects.XyzImpl
classes - Get their constructors, ensure that they are
accessible
- Wrap the constructor
.newInstance
invocations in helper methods - Use the helper methods in a simple recursive method called
substituteTypeVariable
that rebuilds theType
-expressions with type variables substituted by concrete types.
I didn't implement every single case, but it should work with more complicated nested types too (because of the recursive invocation of substituteTypeVariable
).
The compiler doesn't really like this approach, it generates warnings about the usage of the internal Sun API:
warning: ParameterizedTypeImpl is internal proprietary API and may be removed in a future release
but, there is a @SuppressWarnings
for that.
The above Java code has been obtained by translating the following little Scala snippet (that's the reason why the Java code might look a bit strange and not entirely Java-idiomatic):
object Types {
import scala.language.existentials // suppress warnings
import java.lang.Class
import java.lang.reflect.{Array => _, _}
import sun.reflect.generics.reflectiveObjects._
private val ParameterizedTypeCons =
classOf[ParameterizedTypeImpl]
.getDeclaredConstructors
.head
.asInstanceOf[Constructor[ParameterizedTypeImpl]]
ParameterizedTypeCons.setAccessible(true)
/** Helper method for invocation of the `ParameterizedTypeImpl` constructor. */
def parameterizedType(raw: Class[_], paramTypes: Array[Type], owner: Type)
: ParameterizedType = {
ParameterizedTypeCons.newInstance(raw, paramTypes, owner)
}
// (similarly for `GenericArrayType`, `WildcardType` etc.)
/** Substitution of type variables. */
def substituteTypeVariable(
inType: Type,
variable: TypeVariable[_],
replaceBy: Type
): Type = {
inType match {
case v: TypeVariable[_] => replaceBy
case pt: ParameterizedType => parameterizedType(
pt.getRawType.asInstanceOf[Class[_]],
pt.getActualTypeArguments.map(substituteTypeVariable(_, variable, replaceBy)),
pt.getOwnerType
)
case sthElse => throw new NotImplementedError()
}
}
// example
def main(args: Array[String]): Unit = {
// type in which we will replace a variable is `List<E>`
val t =
classOf[java.util.LinkedList[_]]
.getGenericInterfaces
.head
// this is the variable `E` (hopefully, stability not guaranteed)
val v =
t
.asInstanceOf[ParameterizedType]
.getRawType
.asInstanceOf[Class[_]] // should be `List<E>` with parameter
.getTypeParameters
.head // should be `E`
// This should become `List<String>`
val s = substituteTypeVariable(t, v, classOf[String])
println("before: " + t)
println("after: " + s)
}
}
edited Jan 5 at 2:51
answered Jan 2 at 4:46
Andrey TyukinAndrey Tyukin
29.6k42351
29.6k42351
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53947920%2fhow-should-type-type-hierarchy-types-be-implemented%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Why do you need to add equals/hashCode? Are you sure the builtin types are not correct?
– Peter Lawrey
Dec 27 '18 at 16:22
@PeterLawrey I need to check that the Types match. How could the builtin implementations be equal when there doesn't appear to be a spec. I mean you could probably work out what the implementation does by observation - it's not something you are going to use cryptography on.
– Tom Hawtin - tackline
Dec 27 '18 at 16:25
1
The implementations appear to have these methods from reading the source. You could try creating your own system, but that seems like a lot of work given it might never be needed. A simple solution is to compare the
toString()
results.– Peter Lawrey
Dec 27 '18 at 16:30
@PeterLawrey I'm not sure that the
toString
is guaranteed to be unique. (Also bleurgh, what a hack. And not going to fit in myHashMap
nicely.)– Tom Hawtin - tackline
Dec 27 '18 at 16:32
1
Additionally, be extra careful with recursive types, i.e.
<U extends Comparable<U>>
– Federico Peralta Schaffner
Jan 4 at 13:55