How should Type type-hierarchy types be implemented?












14















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 Types, 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;
}
}









share|improve this question

























  • 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 my HashMap 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
















14















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 Types, 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;
}
}









share|improve this question

























  • 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 my HashMap 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














14












14








14


5






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 Types, 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;
}
}









share|improve this question
















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 Types, 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






share|improve this question















share|improve this question













share|improve this question




share|improve this question








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 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






  • 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











  • @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 my HashMap 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












2 Answers
2






active

oldest

votes


















5





+500









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.






share|improve this answer































    4














    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 the Type-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)
    }
    }





    share|improve this answer

























      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
      });


      }
      });














      draft saved

      draft discarded


















      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









      5





      +500









      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.






      share|improve this answer




























        5





        +500









        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.






        share|improve this answer


























          5





          +500







          5





          +500



          5




          +500





          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.






          share|improve this answer













          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.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Jan 4 at 12:43









          Jesse WilsonJesse Wilson

          25.5k27088




          25.5k27088

























              4














              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 the Type-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)
              }
              }





              share|improve this answer






























                4














                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 the Type-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)
                }
                }





                share|improve this answer




























                  4












                  4








                  4







                  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 the Type-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)
                  }
                  }





                  share|improve this answer















                  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 the Type-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)
                  }
                  }






                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  edited Jan 5 at 2:51

























                  answered Jan 2 at 4:46









                  Andrey TyukinAndrey Tyukin

                  29.6k42351




                  29.6k42351






























                      draft saved

                      draft discarded




















































                      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.




                      draft saved


                      draft discarded














                      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





















































                      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







                      Popular posts from this blog

                      MongoDB - Not Authorized To Execute Command

                      How to fix TextFormField cause rebuild widget in Flutter

                      Npm cannot find a required file even through it is in the searched directory