Blogeek|Sioc

Geekeries de tout poil

Retrouver le type des variables génériques en Java

Voici une classe qui permet de retrouver les types utilisées pour les variables génériques sur une instance d’une interface.
Plus précisément, l’utilisation est la suivante : tout d’abord on a défini une interface appelée I, qui utilise une ou plusieurs variables génériques. Par exemple :

public interface I {}

Ensuite, on crée des instances qui implémentent cette interface, soit en créant une classe explicite, soit à la volée avec une classe anonyme :

public class B {}
public class C {}
public class A implements I<b> {}
I&lt;?&gt; a = new A();
I&lt;?&gt; b = new I {};</b>

Le but est de pouvoir retrouver, à partir de la classe A ou d’une instance comme a ou b, la valeur de la variable T définie dans I et spécifiée dans les écritures suivantes, soit la classe B pour A et a, et la classe C pour l’instance b.
Voici comment on procède :

Class&lt;?&gt; c1 = GenericsHelper.getInterfaceGenericType(A.class, I.class); // vaut B.class
Class&lt;?&gt; c2 = GenericsHelper.getInterfaceGenericType(a, I.class); // vaut B.class
Class&lt;?&gt; c3 = GenericsHelper.getInterfaceGenericType(b, I.class); // vaut C.class

Il est possible aussi de résoudre des variables sur des interfaces possédant plusieurs variables génériques, en spécifiant l’index de celle-ci (ordre de déclaration), exemple :

interface I&lt;T,U&gt; {}
public class B {}
public class C {}
public class A implements I&lt;B,C&gt; {}
Class&lt;?&gt; c1 = GenericsHelper.getInterfaceGenericType(A.class, I.class, 0); // vaut B.class
Class&lt;?&gt; c2 = GenericsHelper.getInterfaceGenericType(A.class, I.class, 1); // vaut C.class
Class&lt;?&gt; c3 = GenericsHelper.getInterfaceGenericType(A.class, I.class); // vaut B.class, n°0 par défaut

Sans plus attendre, voici le code source de la classe :

import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Stack;
 
/**
 * Classe helper pour déterminer les types concrets des variables génériques définies dans des interfaces.
 * @author fguerry
 */
public class GenericsHelper {
 
    // constructeur privé = class util
    private GenericsHelper() {
    }
 
    /**
     *

Détermine le type concret d’une variable générique, sur une instance * donnée, la variable étant la première définie dans l’interface donnée

     *

Exemple : soit interface I<T,U> et I<?,?> a = new I<B,C>{};
* Alors : getInterfaceGenericType(a, I.class) vaut B.class
*

     * @param instance
     * @param interf
     * @return 
     */
    public static Class&lt;?&gt; getInterfaceGenericType(Object instance, Class&lt;?&gt; interf) {
        return getInterfaceGenericType(instance.getClass(), interf, 0);
    }
 
    /**
     *

Détermine le type concret d’une variable générique, sur une instance * donnée, la variable étant initialement définie dans l’interface donnée, à la position donnée.

     *

Exemple : soit interface I<T,U> et I<?,?> a = new I<B,C>{};
* Alors : getInterfaceGenericType(a, I.class, 0) vaut B.class
* et getInterfaceGenericType(a, I.class, 1) vaut C.class *

     * @param instance
     * @param interf
     * @param typeNumber
     * @return 
     */
    public static Class&lt;?&gt; getInterfaceGenericType(Object instance, Class&lt;?&gt; interf, int typeNumber) {
        return getInterfaceGenericType(instance.getClass(), interf, typeNumber);
    }
 
    /**
     *

Détermine le type concret d’une variable générique, au sein d’une classe d’implementation * donnée, la variable étant la première définie dans l’interface donnée

     *

Exemple : soit interface I<T,U> et class A implements I<B,C>
* Alors : getInterfaceGenericType(A.class, I.class) vaut B.class
*

     * @param implementor
     * @param interf
     * @return 
     */
    public static Class&lt;?&gt; getInterfaceGenericType(Class&lt;?&gt; implementor, Class&lt;?&gt; interf) {
        return getInterfaceGenericType(implementor, interf, 0);
    }
 
    /**
     *

Détermine le type concret d’une variable générique, au sein d’une classe d’implementation * donnée, la variable étant initialement définie dans l’interface donnée, à la position donnée.

     *

Exemple : soit interface I<T,U> et class A implements I<B,C>
* Alors : getInterfaceGenericType(A.class, I.class, 0) vaut B.class
* et getInterfaceGenericType(A.class, I.class, 1) vaut C.class *

     * @param implementor
     * @param interf
     * @param typeNumber
     * @return 
     */
    public static Class&lt;?&gt; getInterfaceGenericType(final Class&lt;?&gt; implementor, Class&lt;?&gt; interf, int typeNumber) {
        if (implementor == null) {
            throw new NullPointerException("The given implementor class must not be null.");
        }
        if (interf == null) {
            throw new NullPointerException("The given generic interface must not be null.");
        }
        if (typeNumber &lt; 0) {
            throw new IllegalArgumentException("The given typeNumber must not be negative.");
        }
        if (implementor.isInterface()) {
            throw new IllegalArgumentException("The given implementor class " + implementor.getCanonicalName() + " is actually an interface !");
        }
        if (Modifier.isAbstract(implementor.getModifiers())) {
            throw new IllegalArgumentException("The given implementor class " + implementor.getCanonicalName() + " is actually abstract and cannot be looked for implemented interface generic types !");
        }
        if (!interf.isInterface()) {
            throw new IllegalArgumentException("The given interface " + interf.getCanonicalName() + " is actually NOT an interface !");
        }
        Stack&lt;Class&lt;?&gt;&gt; parentStack = new Stack&lt;Class&lt;?&gt;&gt;();
        TypeVariable&lt;?&gt; backSearch = null;
        Class&lt;?&gt; currentClass = implementor;
        implWhile:
        while (currentClass != null) {
            // première passe : on cherche si l'interface est effectivement implémentée dans la classe, et si la variable est spécifiée
            // directement dans la déclaration implements d'une des classes de la hiérarchie
            final Type[] interfaces = currentClass.getGenericInterfaces();
            for (int index = 0; index &lt; interfaces.length; index++) {
                ParameterizedType param = (ParameterizedType) interfaces[index];
                if (param.getRawType().equals(interf)) {
                    final Type[] actualTypeArguments = param.getActualTypeArguments();
                    if (typeNumber &gt;= actualTypeArguments.length) {
                        throw new IllegalArgumentException("The given interface " + interf.getCanonicalName() + " doesn't have a n°" + typeNumber + " parameterizd type, but has only " + actualTypeArguments.length + " of them");
                    }
                    final Type name = actualTypeArguments[typeNumber];
                    if (name instanceof Class) {
                        // la variable est définie à ce niveau directement, ça veut dire que la clause implements a spécifié des types concrets
                        return (Class) name;
                    } else if (name instanceof TypeVariable) {
                        // sinon, la variable a été transmise à la déclaration de la classe, on passe donc à la phase 2
                        backSearch = (TypeVariable) name;
                        break implWhile;
                    }
                }
            }
            parentStack.push(currentClass);
            currentClass = currentClass.getSuperclass();
        }
        while (backSearch != null) {
            // phase 2 : on redescend la hierarchie grâce à la stack afin de déterminer sur quelle classe la viariable est définie
            final int index = indexOfTypeVariable(currentClass, backSearch);
            if (parentStack.isEmpty()) {
                // si à un moment donné on ne peut plus descendre et que la variable n'est toujours pas définie, cela
                // signifie qu'elle est définie nulle part... on ne peut donc pas donner de résultat !
                throw new IllegalArgumentException("The generic parameter n°" + typeNumber + " of interface " + interf.getCanonicalName() + " is not bound anywhere into class " + implementor.getCanonicalName() + ". You should create a fully typed subclass of this class to be able to use this function !!");
            }
            currentClass = parentStack.pop();
            ParameterizedType superclassbound = (ParameterizedType) currentClass.getGenericSuperclass();
            final Type name = superclassbound.getActualTypeArguments()[index];
            if (name instanceof Class) {
                // si la variable est définie à ce niveau, on s'arrête
                return (Class) name;
            } else if (name instanceof TypeVariable) {
                // sinon on continue de descendre
                backSearch = (TypeVariable) name;
            }
        }
        throw new IllegalArgumentException("The given class " + implementor.getCanonicalName() + " doesn't implements the given interface " + interf.getCanonicalName());
    }
 
    /**
     * Retrouve l'index d'une variable générique donnée dans la déclaration d'une classe donnée.
     * @param implementor
     * @param var
     * @return 
     */
    public static int indexOfTypeVariable(Class&lt;?&gt; implementor, TypeVariable&lt;?&gt; var) {
        final TypeVariable&lt;?&gt;[] parameters = implementor.getTypeParameters();
        for (int index = 0; index &lt; parameters.length; index++) {
            if (parameters[index].equals(var)) {
                return index;
            }
        }
        return -1;
    }
}

Categorised as: Java


Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *