285 votes

Obtenir le type générique de java.util.List

J'ai;

List stringList = new ArrayList();
List integerList = new ArrayList();

Existe-t-il un moyen (facile) de récupérer le type générique de la liste ?

0 votes

Pour pouvoir inspecter de manière programmatique un objet de type List et voir son type générique. Une méthode peut vouloir insérer des objets en fonction du type générique de la collection. Cela est possible dans les langages qui implémentent des génériques au moment de l'exécution plutôt qu'au moment de la compilation.

4 votes

Correct -- à peu près la seule façon de permettre la détection en temps d'exécution est par sous-classement : vous POUVEZ réellement étendre le type générique puis, à l'aide de la réflexion, trouver la déclaration de type que la sous-type a utilisée. Cela demande beaucoup de réflexion, mais c'est possible. Malheureusement, il n'y a pas de moyen facile d'imposer l'utilisation d'une sous-classe générique.

1 votes

Surely stringList contains strings and integerList integers? Why make it more complicated?

440voto

BalusC Points 498232

Si ce sont effectivement des champs d'une certaine classe, vous pouvez les obtenir avec un peu d'aide de la réflexion:

package com.stackoverflow.q1942644;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;

public class Test {

    List stringList = new ArrayList<>();
    List integerList = new ArrayList<>();

    public static void main(String... args) throws Exception {
        Class testClass = Test.class;

        Field stringListField = testClass.getDeclaredField("stringList");
        ParameterizedType stringListType = (ParameterizedType) stringListField.getGenericType();
        Class stringListClass = (Class) stringListType.getActualTypeArguments()[0];
        System.out.println(stringListClass); // class java.lang.String

        Field integerListField = testClass.getDeclaredField("integerList");
        ParameterizedType integerListType = (ParameterizedType) integerListField.getGenericType();
        Class integerListClass = (Class) integerListType.getActualTypeArguments()[0];
        System.out.println(integerListClass); // class java.lang.Integer
    }

}

Vous pouvez également le faire pour les types de paramètres et le type de retour des méthodes.

Mais s'ils se trouvent dans le même contexte de la classe/méthode où vous en avez besoin, alors il n'y a pas d'intérêt à les connaître, car vous les avez déjà déclarés vous-même.

0 votes

While this is definitely an interesting exercise in generics, if you now what field you want, you usually know what type it is.. :) Determining the type of a parameter would've been a lot more useful I suppose. Good when making injection frameworks or similar though. +1

19 votes

Il y a certainement des situations où cela est plus que utile. Par exemple, dans les frameworks ORM sans configuration.

3 votes

..qui peut utiliser Class#getDeclaredFields() pour obtenir tous les champs sans avoir besoin de connaître le nom du champ.

23voto

tsaixingwei Points 268

Vous pouvez faire de même pour les paramètres de méthode également :

Method method = someClass.getDeclaredMethod("someMethod");
Type[] types = method.getGenericParameterTypes();
//Maintenant, en supposant que le premier paramètre de la méthode est de type List
ParameterizedType pType = (ParameterizedType) types[0];
Class clazz = (Class) pType.getActualTypeArguments()[0];
System.out.println(clazz); //affiche java.lang.Integer

20voto

falstro Points 16545

Réponse courte : non.

C'est probablement un doublon, je ne trouve pas le bon en ce moment.

Java utilise quelque chose appelé effacement de type, ce qui signifie qu'à l'exécution, les deux objets sont équivalents. Le compilateur sait que les listes contiennent des entiers ou des chaînes de caractères, et peut donc maintenir un environnement sûr en termes de type. Cette information est perdue (au niveau de l'instance de l'objet) à l'exécution, et la liste ne contient que des 'Objets'.

Vous POUVEZ découvrir un peu sur la classe, quels types elle pourrait être paramétrée par, mais normalement il s'agit juste de tout ce qui étend "Object", c'est-à-dire n'importe quoi. Si vous définissez un type comme

class  AClass {....}

AClass.class contiendra seulement le fait que le paramètre A est borné par MyClass, mais au-delà de ça, il n'y a aucun moyen de le savoir.

2 votes

Cela sera vrai si la classe concrète du générique n'est pas spécifiée; dans son exemple, il déclare explicitement les listes comme List et List; dans ces cas, l'effacement des types ne s'applique pas.

10voto

Aram Kocharyan Points 8530

Si vous avez besoin d'obtenir le type générique d'un type retourné, j'ai utilisé cette approche lorsque j'avais besoin de trouver des méthodes dans une classe qui retournait une Collection et d'accéder ensuite à leurs types génériques :

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;

public class Test {

    public List test() {
        return null;
    }

    public static void main(String[] args) throws Exception {

        for (Method method : Test.class.getMethods()) {
            Class returnClass = method.getReturnType();
            if (Collection.class.isAssignableFrom(returnClass)) {
                Type returnType = method.getGenericReturnType();
                if (returnType instanceof ParameterizedType) {
                    ParameterizedType paramType = (ParameterizedType) returnType;
                    Type[] argTypes = paramType.getActualTypeArguments();
                    if (argTypes.length > 0) {
                        System.out.println("Generic type is " + argTypes[0]);
                    }
                }
            }
        }

    }

}

Cela génère :

Type générique est class java.lang.String

4voto

Scott Morrison Points 1594

À l'exécution, non, vous ne pouvez pas.

Cependant, via la réflexion, les paramètres de type sont accessibles. Essayez

for(Field field : this.getDeclaredFields()) {
    System.out.println(field.getGenericType())
}

La méthode getGenericType() retourne un objet Type. Dans ce cas, ce sera une instance de ParametrizedType, qui a à son tour des méthodes getRawType() (qui contiendra List.class, dans ce cas) et getActualTypeArguments(), qui renverra un tableau (dans ce cas, de longueur un, contenant soit String.class soit Integer.class).

2 votes

Et cela fonctionne-t-il pour les paramètres reçus par une méthode au lieu des champs d'une classe ???

0 votes

@opensas Vous pouvez utiliser method.getGenericParameterTypes() pour obtenir les types déclarés des paramètres d'une méthode.

0 votes

Cela fonctionne parfaitement jusqu'à ce que vous ayez, disons, class Foo { public T bar; } Foo fooField; - dans ce cas, ce qui est renvoyé pour le membre 'bar' de fooField sera un TypeVariable au lieu d'une Classe. Même si vous et moi pouvons clairement voir que T sera Integer, pour une raison quelconque, nous ne pouvons récupérer cette information que de manière réflexive à partir d'une sous-classe de Foo en utilisant la méthode getGenericSuperclass de la sous-classe (qui renverra ensuite un ParametrizedType comme dans l'exemple ci-dessus).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X