[英]Generic class type parameter detail at runtime
如果我們提供足夠的泛型信息,像 Jackson 這樣的庫可以從 JSON 創建對象。
在傑克遜,我們可以做到
Class<?>[] parameterTypes;
JavaType type = objectMapper.getTypeFactory().constructParametricType(ObjectClass, parameterTypes);
objectMapper.readValue(json, type);
在 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;
}
我知道類型擦除和其他事情,但是有沒有辦法從MultiLevelGenericTestData
的對象中識別類型T, V
?
我想到的一種方法是檢查泛型類型並查看它們的名稱並檢查所有字段,直到找到所有類型。 一旦遇到具有相同泛型類型的多個字段的情況,這很快就會變得棘手,例如在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());
}
}
}
可以通過getTypeParameters
方法找到類型參數的數量,我們如何結合它來獲取確切的類型信息。
例子
MultiLevelGenericTestData<String, String> data;
在這種情況下,我們應該得到[String, String]
MultiLevelGenericTestData<String, Integer> data;
在這種情況下,我們應該得到[String, Integer]
MultiGenericTestData<String, String> data;
在這種情況下,我們應該得到[String, String]
MultiGenericTestDataSameType<String> data;
在這種情況下,我們應該得到[String]
例如,當類型 T 本身是泛型時,這變得更加有趣
MultiGenericTestDataSameType< MultiGenericTestData< String, Integer> > data;
對於這個數據,我們應該得到MultiGenericTestData
和它的通用參數String
和Integer
。
編輯:
為了進一步澄清,我想在不創建任何其他類的情況下獲取類型信息,並且應該將其傳遞給序列化程序,即我不想更改看起來類似於此[]byte serialize(Object payload)
序列化程序方法簽名. 我們可以創建任意數量的輔助類,也可以強制從某個超類擴展有效負載類,(超類可以具有提取通用信息的邏輯)。
這是一個相當長的答案,但應該能讓你進入一個很好的起點來做你想做的事。
在運行時獲取泛型類型的“技巧”相當古老,並且最著名的(我猜)使用的是gson
和guava
現代庫。 我猜jackson
使用了同樣的技巧,因為沒有其他方法可以做到這一點。
簡單地說,你需要一個這樣的類來開始:
static abstract class MappingRegistrar<IN> {
private final Type type;
protected MappingRegistrar() {
// more will be here shortly
}
// ... more will come here shortly
}
如果你想創建它的一個實例,你不得不提供一個類,將擴展它。 所以你被迫寫一些類似的東西:
MappingRegistrar<String> one = new MappingRegistrar<>() {};
如果您被迫提供這樣的超類,那么技巧(在構造函數中)可以發生:
static abstract class MappingRegistrar<IN> {
private final Type type;
protected MappingRegistrar() {
Class<?> cls = getClass();
Type[] type = ((ParameterizedType) cls.getGenericSuperclass()).getActualTypeArguments();
this.type = type[0];
}
}
現在您可以找出泛型類型。 但不是這樣。 你需要正確解析它們,因為一個Type
實際上可以是多個東西......
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
}
}
}
這不是一個完整的實現,但對於一些示例,它會打印:
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-------------");
}
}
結果是:
String
-------------
String[]
-------------
[java.lang.Number, java.io.Serializable]
-------------
Playground2$MultiLevelGenericTestData<String, String>
-------------
Playground2$MultiGenericTestDataSameType<Playground2$MultiGenericTestData<String, Integer>>
-------------
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.