运行时的通用类​​类型参数详细信息

son*_*s21 11 java generics json jackson

如果我们提供足够的泛型信息,像 Jackson 这样的库可以从 JSON 创建对象。

在杰克逊,我们可以做到

    Class<?>[] parameterTypes; 
    JavaType type = objectMapper.getTypeFactory().constructParametricType(ObjectClass, parameterTypes);
    objectMapper.readValue(json, type);
Run Code Online (Sandbox Code Playgroud)

在 java 中,可以通过多种方式定义泛型类,例如一个泛型类具有另一个泛型类,并且可以具有另一个泛型类,为了简单说明,考虑这三个类。

  public class MultiLevelGenericTestData<T, V> {
    private GenericTestData<T> tGenericTestData;
    private GenericTestData<V> vGenericTestData;
  }

  public class MultiGenericTestData<K, V> {
    private K key;
    private V value;
  }
 
  public class MultiGenericTestDataSameType<T> {
    private GenericTestData<T> genericTestData;
    private MultiGenericTestData<T, T> multiGenericTestData;
  }
Run Code Online (Sandbox Code Playgroud)

我知道类型擦除和其他事情,但有没有办法识别类型 T, V从 的对象中MultiLevelGenericTestData

我想到的一种方法是检查泛型类型并查看它们的名称并检查所有字段,直到找到所有类型。一旦我们遇到具有相同泛型类型的多个字段的情况,这很快就会变得棘手,例如在MultiGenericTestDataSameType,我们应该只得到一种泛型类型。

 // This method should find all type's class names in the list
 // that can be used to construct the object without any issue.
void genericFieldClassNames(List<String> types, List<String> classes, Object payload)
      throws IllegalAccessException {
    for (Field field : payload.getClass().getDeclaredFields()) {
      // ignorefield without annotation
      if (!field.isAnnotationPresent(GenericField.class)) {
        continue;
      }
      Type genericType = field.getGenericType();
      // not a generic field
      if (genericType.equals(field.getType())) {
        continue;
      }
      // null value nothing can be done
      Object fieldVal = FieldUtils.readField(field, payload, true);
      if (fieldVal == null) {
        continue;
      }
      String genericFieldType = genericType.getTypeName();
      Class<?> fieldClass = fieldVal.getClass();
      // problematic cases when we start traversing up 
      if (genericFieldType.endsWith(">")) {
        genericFieldClassNames(types, classes, fieldVal);
      } else {
        // here a check can be added to avoid duplicate type name but as soon as  
        // we add type genericFieldType check it will fail when we have used similar  
        // types in construction like MultiGenericTestData<String, String>
        types.add(genericFieldType);
        classes.add(fieldClass.getName());
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)

可以通过方法找到类型参数的数量 getTypeParameters我们如何结合它来获得确切的类型信息。

例子

MultiLevelGenericTestData<String, String> data;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们应该得到 [String, String]

MultiLevelGenericTestData<String, Integer> data;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们应该得到 [String, Integer]

MultiGenericTestData<String, String> data;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们应该得到 [String, String]

MultiGenericTestDataSameType<String> data;
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们应该得到 [String]

例如,当类型 T 本身是泛型时,这变得更加有趣

MultiGenericTestDataSameType< MultiGenericTestData< String, Integer> > data;
Run Code Online (Sandbox Code Playgroud)

对于这些数据,我们应该得到MultiGenericTestData它的通用参数StringInteger.

编辑:

为了进一步澄清,我想在不创建任何其他类的情况下获取类型信息,并且应该将其传递给序列化程序,即我不想更改与此类 似的序列化程序方法签名[]byte serialize(Object payload)。我们可以创建任意数量的辅助类,也可以强制从某个超类扩展有效负载类,(超类可以具有提取通用信息的逻辑)。

Eug*_*ene 13

这是一个相当长的答案,但应该能让你进入一个很好的起点来做你想做的事。

在运行时获取泛型类型的“技巧”相当古老,并且最著名的(我猜)使用它的现代库是gsonguava。我想jackson使用相同的技巧,因为根本没有其他方法可以做到。

简单地说,你需要一个这样的类来开始:

static abstract class MappingRegistrar<IN> {

    private final Type type;

    protected MappingRegistrar() {
        // more will be here shortly
    }
 
    // ... more will come here shortly

} 
Run Code Online (Sandbox Code Playgroud)

如果你想创建它的一个实例,你不得不提供一个类,将扩展它。所以你被迫写一些类似的东西:

MappingRegistrar<String> one = new MappingRegistrar<>() {};
Run Code Online (Sandbox Code Playgroud)

如果您被迫提供这样的超类,那么技巧(在构造函数中)可以发生:

static abstract class MappingRegistrar<IN> {

    private final Type type;

    protected MappingRegistrar() {
        Class<?> cls = getClass();
        Type[] type = ((ParameterizedType) cls.getGenericSuperclass()).getActualTypeArguments();
        this.type = type[0];
    }

}
Run Code Online (Sandbox Code Playgroud)

现在您可以找出泛型类型。但不是这样。您需要正确解析它们,因为 aType实际上可以是多个事物...

static abstract class MappingRegistrar<IN> {

    private final Type type;

    protected MappingRegistrar() {
        Class<?> cls = getClass();
        Type[] type = ((ParameterizedType) cls.getGenericSuperclass()).getActualTypeArguments();
        this.type = type[0];
    }

    public void seeIt() {
        innerSeeIt(type);
    }

    private void innerSeeIt(Type type) {
        if (type instanceof Class) {
            Class<?> cls = (Class<?>) type;
            boolean isArray = cls.isArray();
            if (isArray) {
                System.out.print(cls.getComponentType().getSimpleName() + "[]");
                return;
            }
            System.out.print(cls.getSimpleName());

        }

        if (type instanceof TypeVariable) {
            Type[] bounds = ((TypeVariable<?>) type).getBounds();
            String s = Arrays.stream(bounds).map(Type::getTypeName).collect(Collectors.joining(", ", "[", "]"));
            System.out.print(s);
        }

        if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            String rawType = parameterizedType.getRawType().getTypeName();
            System.out.print(rawType + "<");
            Type[] arguments = parameterizedType.getActualTypeArguments();

            for (int i = 0; i < arguments.length; ++i) {
                innerSeeIt(arguments[i]);
                if (i != arguments.length - 1) {
                    System.out.print(", ");
                }

            }

            System.out.print(">");
            //System.out.println(Arrays.toString(arguments));
        }

        if (type instanceof GenericArrayType) {
            // you need to handle this one too
        }

        if (type instanceof WildcardType) {
            // you need to handle this one too, but it isn't trivial
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

这不是一个完整的实现,但对于一些示例,它会打印:

 public class Playground2<R extends Number & Serializable> {

    public static void main(String[] args) {
        new Playground2<Integer>().samples();
    }


    public void samples() {

        MappingRegistrar<String> one = new MappingRegistrar<>() {};
        one.seeIt();
        System.out.println("\n-------------");

        MappingRegistrar<String[]> two = new MappingRegistrar<>() {};
        two.seeIt();
        System.out.println("\n-------------");

        MappingRegistrar<R> three = new MappingRegistrar<>() {};
        three.seeIt();
        System.out.println("\n-------------");

        MappingRegistrar<MultiLevelGenericTestData<String, String>> four = new MappingRegistrar<>() {};
        four.seeIt();
        System.out.println("\n-------------");

        MappingRegistrar<MultiGenericTestDataSameType<MultiGenericTestData<String, Integer>>> five = new MappingRegistrar<>() {};
        five.seeIt();
        System.out.println("\n-------------");

    }
}
Run Code Online (Sandbox Code Playgroud)

结果是:

String
-------------
String[]
-------------
[java.lang.Number, java.io.Serializable]
-------------
Playground2$MultiLevelGenericTestData<String, String>
-------------
Playground2$MultiGenericTestDataSameType<Playground2$MultiGenericTestData<String, Integer>>
-------------
Run Code Online (Sandbox Code Playgroud)

  • 在杰克逊,他们称之为 [TypeReference](https://fasterxml.github.io/jackson-core/javadoc/2.2.0/com/fasterxml/jackson/core/type/TypeReference.html),是的,这看起来像同样的伎俩。 (2认同)