web-dev-qa-db-fra.com

Comment puis-je déterminer le type d'un champ générique en Java?

J'ai essayé de déterminer le type d'un champ dans une classe. J'ai vu toutes les méthodes d'introspection mais je n'ai pas tout à fait compris comment le faire. Cela va être utilisé pour générer du xml/json à partir d'une classe Java. J'ai examiné un certain nombre de questions ici mais je n'ai pas trouvé exactement ce dont j'avais besoin.

Exemple:

class Person {
    public final String name;
    public final List<Person> children;
}

Lorsque je rassemble cet objet, j'ai besoin de savoir que le champ chidren est une liste d'objets de type Person, donc je peux le marshaller correctement.

J'avais essayé

for (Field field : Person.class.getDeclaredFields()) {
    System.out.format("Type: %s%n", field.getType());
}

Mais cela me dira seulement que c'est un List, pas un List de Persons

Merci

36
Juan Mendes

Jetez un œil à Obtention des types de champs à partir du Java Tutorial Trail: The Reflection API .

Fondamentalement, ce que vous devez faire est d'obtenir tous Java.lang.reflect.Field de votre classe et appelez Field#getType() sur chacun d'eux (Cochez Modifier ci-dessous). Pour obtenir tous les champs d'objet , y compris les champs d'accès public, protégé, package et privé, utilisez simplement Class.getDeclaredFields() . Quelque chose comme ça:

for (Field field : Person.class.getDeclaredFields()) {
    System.out.format("Type: %s%n", field.getType());
    System.out.format("GenericType: %s%n", field.getGenericType());
}

EDIT: Comme indiqué par wowest dans un commentaire, vous devez en fait appeler Field#getGenericType() , vérifiez si le Type renvoyé est un ParameterizedType, puis saisissez les paramètres en conséquence. Utilisez ParameterizedType#getRawType() et ParameterizedType#getActualTypeArgument() pour obtenir le type brut et un tableau de l'argument types d'un ParameterizedType respectivement. Le code suivant illustre cela:

for (Field field : Person.class.getDeclaredFields()) {
    System.out.print("Field: " + field.getName() + " - ");
    Type type = field.getGenericType();
    if (type instanceof ParameterizedType) {
        ParameterizedType pType = (ParameterizedType)type;
        System.out.print("Raw type: " + pType.getRawType() + " - ");
        System.out.println("Type args: " + pType.getActualTypeArguments()[0]);
    } else {
        System.out.println("Type: " + field.getType());
    }
}

Et produirait:

Field: name - Type: class Java.lang.String
Field: children - Raw type: interface Java.util.List - Type args: class foo.Person
58
Pascal Thivent

Voici un exemple qui répond à ma question

class Person {
  public final String name;
  public final List<Person> children;  
}

//in main
Field[] fields = Person.class.getDeclaredFields();
for (Field field : fields) {
  Type type = field.getGenericType();
  System.out.println("field name: " + field.getName());
  if (type instanceof ParameterizedType) {
    ParameterizedType ptype = (ParameterizedType) type;
    ptype.getRawType();
    System.out.println("-raw type:" + ptype.getRawType());
    System.out.println("-type arg: " + ptype.getActualTypeArguments()[0]);
  } else {
    System.out.println("-field type: " + field.getType());
  }
}

Cette sorties

 nom de champ: nom 
 - type de champ: classe Java.lang.String 
 nom de champ: enfants 
 - type brut: interface Java.util.List 
 - tapez arg: classe com.blah.Person 
5
Juan Mendes

Je n'ai trouvé aucun cadre qui détermine un type de champ générique à travers les couches d'héritage, j'ai donc écrit une méthode:

Cette logique détermine le type via les informations de champ et la classe d'objets actuelle.

Listing 1 - logique:

public static Class<?> determineType(Field field, Object object) {
    Class<?> type = object.getClass();
    return (Class<?>) getType(type, field).type;
}

protected static class TypeInfo {
    Type type;
    Type name;

    public TypeInfo(Type type, Type name) {
        this.type = type;
        this.name = name;
    }

}

private static TypeInfo getType(Class<?> clazz, Field field) {
    TypeInfo type = new TypeInfo(null, null);
    if (field.getGenericType() instanceof TypeVariable<?>) {
        TypeVariable<?> genericTyp = (TypeVariable<?>) field.getGenericType();
        Class<?> superClazz = clazz.getSuperclass();

        if (clazz.getGenericSuperclass() instanceof ParameterizedType) {
            ParameterizedType paramType = (ParameterizedType) clazz.getGenericSuperclass();
            TypeVariable<?>[] superTypeParameters = superClazz.getTypeParameters();
            if (!Object.class.equals(paramType)) {
                if (field.getDeclaringClass().equals(superClazz)) {
                    // this is the root class an starting point for this search
                    type.name = genericTyp;
                    type.type = null;
                } else {
                    type = getType(superClazz, field);
                }
            }
            if (type.type == null || type.type instanceof TypeVariable<?>) {
                // lookup if type is not found or type needs a lookup in current concrete class
                for (int j = 0; j < superClazz.getTypeParameters().length; ++j) {
                    TypeVariable<?> superTypeParam = superTypeParameters[j];
                    if (type.name.equals(superTypeParam)) {
                        type.type = paramType.getActualTypeArguments()[j];
                        Type[] typeParameters = clazz.getTypeParameters();
                        if (typeParameters.length > 0) {
                            for (Type typeParam : typeParameters) {
                                TypeVariable<?> objectOfComparison = superTypeParam;
                                if(type.type instanceof TypeVariable<?>) {
                                    objectOfComparison = (TypeVariable<?>)type.type;
                                }
                                if (objectOfComparison.getName().equals(((TypeVariable<?>) typeParam).getName())) {
                                    type.name = typeParam;
                                    break;
                                }
                            }
                        }
                        break;
                    }
                }
            }
        }
    } else {
        type.type = field.getGenericType();
    }

    return type;
}

Listing 2 - Échantillons/tests:

class GenericSuperClass<E, T, A> {
    T t;
    E e;
    A a;
    BigDecimal b;
}

class GenericDefinition extends GenericSuperClass<Integer, Integer, Integer> {

}

@Test
public void testSimpleInheritanceTypeDetermination() {
    GenericDefinition Gd = new GenericDefinition();
    Field field = ReflectionUtils.getField(Gd, "t");
    Class<?> clazz = ReflectionUtils.determineType(field, Gd);
    Assert.assertEquals(clazz, Integer.class);
    field = ReflectionUtils.getField(Gd, "b");
    clazz = ReflectionUtils.determineType(field, Gd);
    Assert.assertEquals(clazz, BigDecimal.class);
}

class MiddleClass<A, E> extends GenericSuperClass<E, Integer, A> { }

// T = Integer, E = String, A = Double
class SimpleTopClass extends MiddleClass<Double, String> { }

@Test
public void testSimple2StageInheritanceTypeDetermination() {
    SimpleTopClass stc = new SimpleTopClass();
    Field field = ReflectionUtils.getField(stc, "t");
    Class<?> clazz = ReflectionUtils.determineType(field, stc);
    Assert.assertEquals(clazz, Integer.class);
    field = ReflectionUtils.getField(stc, "e");
    clazz = ReflectionUtils.determineType(field, stc);
    Assert.assertEquals(clazz, String.class);
    field = ReflectionUtils.getField(stc, "a");
    clazz = ReflectionUtils.determineType(field, stc);
    Assert.assertEquals(clazz, Double.class);
}

class TopMiddleClass<A> extends MiddleClass<A, Double> { }

// T = Integer, E = Double, A = Float
class ComplexTopClass extends TopMiddleClass<Float> {}

@Test void testComplexInheritanceTypDetermination() {
    ComplexTopClass ctc = new ComplexTopClass();
    Field field = ReflectionUtils.getField(ctc, "t");
    Class<?> clazz = ReflectionUtils.determineType(field, ctc);
    Assert.assertEquals(clazz, Integer.class);
    field = ReflectionUtils.getField(ctc, "e");
    clazz = ReflectionUtils.determineType(field, ctc);
    Assert.assertEquals(clazz, Double.class);
    field = ReflectionUtils.getField(ctc, "a");
    clazz = ReflectionUtils.determineType(field, ctc);
    Assert.assertEquals(clazz, Float.class);
}

class ConfusingClass<A, E> extends MiddleClass<E, A> {}
// T = Integer, E = Double, A = Float ; this class should map between a and e
class TopConfusingClass extends ConfusingClass<Double, Float> {}

@Test
public void testConfusingNamingConvetionWithInheritance() {
    TopConfusingClass tcc = new TopConfusingClass();
    Field field = ReflectionUtils.getField(tcc, "t");
    Class<?> clazz = ReflectionUtils.determineType(field, tcc);
    Assert.assertEquals(clazz, Integer.class);
    field = ReflectionUtils.getField(tcc, "e");
    clazz = ReflectionUtils.determineType(field, tcc);
    Assert.assertEquals(clazz, Double.class);
    field = ReflectionUtils.getField(tcc, "a");
    clazz = ReflectionUtils.determineType(field, tcc);
    Assert.assertEquals(clazz, Float.class);
    field = ReflectionUtils.getField(tcc, "b");
    clazz = ReflectionUtils.determineType(field, tcc);
    Assert.assertEquals(clazz, BigDecimal.class);
}

class Pojo {
    Byte z;
}

@Test
public void testPojoDetermineType() {
    Pojo pojo = new Pojo();
    Field field = ReflectionUtils.getField(pojo, "z");
    Class<?> clazz = ReflectionUtils.determineType(field, pojo);
    Assert.assertEquals(clazz, Byte.class);
}

J'ai hâte d'entendre vos commentaires!

4
javaBeCool

prenez cet extrait:

 for (Field field : Person.class.getFields()) {
        System.out.println(field.getType());
 }

la classe de clé est Field

3
dfa

Voici mon point de vue. Il ne peut pas gérer tous les cas possibles (et a sûrement quelques bugs), mais il gère tous les cas qui se produisent dans mon code jusqu'à présent. Cela inclut ces déclarations, qui devraient être un bon début pour de nombreux cas d'utilisation:

  private int                                                primitiveField1;

  private Object                                             field1;
  private List<Integer>                                      field2;
  private Map<Integer, String>                               field3;
  private Map<? extends String, List<Map<Class<?>, Object>>> field4;

  private char[]                                             array1;
  private Character[]                                        array2;
  private Class<? extends Integer>[]                         array3;
  private List<Integer>[]                                    array4;

  private InnerClass<String>                                 innerClass;

La mise en oeuvre:

  public static String getDeclaration(Field field) {
    return getDeclaration(field.getGenericType());
  }

  private static String getDeclaration(Type genericType) {
    if(genericType instanceof ParameterizedType) {
      // types with parameters
      ParameterizedType parameterizedType = (ParameterizedType) genericType;
      String declaration = parameterizedType.getRawType().getTypeName();
      declaration += "<";

      Type[] typeArgs = parameterizedType.getActualTypeArguments();

      for(int i = 0; i < typeArgs.length; i++) {
        Type typeArg = typeArgs[i];

        if(i > 0) {
          declaration += ", ";
        }

        // note: recursive call
        declaration += getDeclaration(typeArg);
      }

      declaration += ">";
      declaration = declaration.replace('$', '.');
      return declaration;
    }
    else if(genericType instanceof Class<?>) {
      Class<?> clazz = (Class<?>) genericType;

      if(clazz.isArray()) {
        // arrays
        return clazz.getComponentType().getCanonicalName() + "[]";
      }
      else {
        // primitive and types without parameters (normal/standard types)
        return clazz.getCanonicalName();
      }
    }
    else {
      // e.g. WildcardTypeImpl (Class<? extends Integer>)
      return genericType.getTypeName();
    }
  }
2
Reto Höhener

Comme le souligne dfa, vous pouvez obtenir le type effacé avec Java.lang.reflect.Field.getType. Vous pouvez obtenir le type générique avec Field.getGenericType (qui peut avoir des caractères génériques et des paramètres génériques liés et toutes sortes de folies). Vous pouvez obtenir les champs via Class.getDeclaredFields (Class.getFields vous donnera des champs publics (y compris ceux du supertpye) - inutiles). Pour obtenir les champs de type de base, passez par Class.getSuperclass. Remarque pour vérifier les modificateurs de Field.getModifiers - les champs statiques ne vous intéresseront probablement pas.

1