2

What I'm trying to do is to get a generic typearg of a Field using

Type type = f.getGenericType();
if (type instanceof ParameterizedType) {
    ParameterizedType ptype = (ParameterizedType) type;
    // ...
    Class<?> typearg0 = ??? // ptype.getActualTypeArguments()[0]
}

(where f is an instance of java.lang.Field). I need that instance of Class<?> to perform a Foo.class.isAssignableFrom(typearg0) check later, which only takes Class<T>es as an argument. At the same time, I know (or expect) said typearg0 to be a parameterized type as well, so there's a contradiction: typearg0 cannot be Class<?> and ParameterizedType at the same time, because Class<?> is itself a class and does not implement ParameterizedType.

Can I achieve the isAssignableFrom() check in any other way? Or is my goal generally impossible to achieve?

Thank you very much!

Edit:

Here is an example:

Suppose you expect a Field of type Map<Foo, Set<Bar>>, but all you got is a java.lang.reflect.Field instance. Now, you don't want it to precisely match Map<Foo, Set<Bar>>, you just want the respective types to be instances of Map or Set. It is not a rigid specification check, it's more of a "sanity check". I would attempt this check like this:

  1. Get the field type as a class (.getType())
  2. Check whether Map is assignable from the field type
  3. Get the field type as a (possibly) parameterized type (.getGenericType())
  4. Check that the first generic parameter is Foo (this should be an exact match)
  5. Get the second generic parameter as a class
  6. Check whether Set is assignable from this nested type
  7. Check whether the nested type is itself a ParameterizedType and cast it
  8. Check that the first generic parameter of the nested type is Bar (this should be an exact match)

However, I don't know how to achieve 5 and/or 6.

(using java 11)

Raphael Tarita
  • 770
  • 1
  • 6
  • 31

1 Answers1

2

Just like you've done with Map, Set is likely to be an instance of ParameterizedType so you can check it and then cast it.

From that ParameterizedType (which represents the Set<Something>), you can get use getRawType to get a Set. That's a Type too -- but this should be safely castable to Class. Just in case it's not, you can use a fallback and load the class using its name.

final Type valueType = mapType.getActualTypeArguments()[1];
final ParameterizedType pValueType = (ParameterizedType) valueType;
final Class<?> valueClass = pValueType.getRawType() instanceof Class ?
    ((Class<?>) pValueType.getRawType()) :
    // Should be castable, but just in case it's not, fallback
    getClass().getClassLoader().loadClass(
        pValueType.getRawType().getTypeName()
    );
if (Set.class.isAssignableFrom(valueClass)) { /* do something*/ }

Here's a full runnable example.

class Foo {}
class Bar {}
interface MyValidMap extends Map<Foo, Set<Bar>> {}

public class Application {
    public final HashMap<Foo, HashSet<Bar>> good = null;
    public final Map<Foo, Set<Bar>> better = null;
    public final String notAMap = null;
    public final Map<String, Set<Bar>> badKey = null;
    public final Map<Foo, List<String>> badValue = null;
    public final Map<Foo, Set<String>> badSetElems = null;
    public final MyValidMap noTypeParamMap = null;

    public static void main(String[] args) throws Exception {
        for (Field field : Application.class.getFields()) {
            System.out.println(field.getName() + " - " + fieldMatches(field));
        }
    }

    private static String fieldMatches(Field mapField) throws Exception {
        if (!Map.class.isAssignableFrom(mapField.getType())) {
            return "Field is not a Map";
        }

        if (!(mapField.getGenericType() instanceof ParameterizedType)) {
            // We know it's a map, but it doesn't have type params. Probably something
            // like this: class MyStringMap implements Map<String, String>. You can do
            // something with getGenericInterfaces() but this seems so unlikely that
            // it could just be ignored.
            return "TODO";
        }

        final ParameterizedType mapType = (ParameterizedType) mapField.getGenericType();
        final Type keyType = mapType.getActualTypeArguments()[0];
        final Type valueType = mapType.getActualTypeArguments()[1];
        if (Foo.class != keyType) {
            return "Map's key type is not Foo";
        }

        if (!(valueType instanceof ParameterizedType)) {
            // Same problem as above. May be a Set without type params
            return "Map's value is (probably) not a Set";
        }

        final ParameterizedType pValueType = (ParameterizedType) valueType;
        final Class<?> valueClass = pValueType.getRawType() instanceof Class ?
            ((Class<?>) pValueType.getRawType()) :
            Application.class.getClassLoader().loadClass(
                pValueType.getRawType().getTypeName()
            );

        if (!Set.class.isAssignableFrom(valueClass)) {
            return "Map's value is not a Set";
        }

        final Type setElemType = pValueType.getActualTypeArguments()[0];
        if (setElemType != Bar.class) {
            return "Set's elements are not Bars";
        }
        return "Looks good";
    }
}

Output:

good - Looks good
better - Looks good
notAMap - Field is not a Map
badKey - Map's key type is not Foo
badValue - Map's value is (probably) not a Set
badSetElems - Set's elements are not Bars
noTypeParamMap - TODO
Michael
  • 41,989
  • 11
  • 82
  • 128
  • Extremely detailed and helpful answer, thank you so much! The fact that you can retrieve the `Class>` of the nested type by first casting it to a parameterized type and then invoking `getRawType()` was indeed the missing link I was looking for. – Raphael Tarita May 20 '21 at 19:06