web-dev-qa-db-fra.com

Comment vérifier si un type générique implémente un type spécifique d'interface générique en Java?

Duplicata possible:
Type générique de variable locale à l'exécution

Je suis nouveau dans les génériques Java, et venant d'un monde .NET, j'ai l'habitude d'écrire une méthode comme celle-ci:

public void genericMethod<T>(T genericObject)
{
    if (genericObject is IList<String>)
    {
        //Do something...
    }            
}

La méthode accepte un objet de type générique et vérifie si cet objet implémente une version spécifique de l'interface générique IList<>, Dans ce cas, IList<String>.

Maintenant, en Java, je suis capable de faire ceci:

public <T> void genericMethod(T genericObject)
{
    if (genericObject instanceof Set<?>)
    {
       //Do something...
    }
}

[~ # ~] mais [~ # ~]

Java ne pas laissez-moi faire if (genericObject instanceof Set<String>)

D'après ce que je sais, en raison de l'effacement de type, normalement dans Java cela serait pris en charge par un objet de classe, et nous ferions quelque chose comme ceci:

public <T> void genericMethod(T genericObject)
{
    Class<OurTestingType> testClass = OurTestingType.class;
    if (genericObject.getClass() == testClass)
    {
       //Do something...
    }
}

mais comme le type que je recherche est une interface générique, vous ne pouvez pas faire ceci:

Class<Set<String>> testClass = Set<String>.class

Alors, comment, en Java, puis-je vérifier si un objet générique implémente le type spécifique de Set<String>?

20
Eric Cornelson

Java implémente l'effacement, donc il n'y a aucun moyen de savoir à l'exécution si genericObject est une instance de Set<String> Ou non. La seule façon de garantir cela est d'utiliser des limites sur vos génériques ou de vérifier tous les éléments de l'ensemble.

Limites génériques à la compilation

Utilisation de la vérification des limites, qui sera vérifiée au moment de la compilation:

public <T extends SomeInterface> void genericMethod(Set<? extends T> tSet) {
    // Do something with tSet here
}

Java 8

Nous pouvons utiliser des flux en Java 8 pour le faire en mode natif sur une seule ligne:

public <T> void genericMethod(T t) {
    if (t instanceof Set<?>) {
        Set<?> set = (Set<?>) t;
        if (set.stream().allMatch(String.class:isInstance)) {
            Set<String> strs = (Set<String>) set;
            // Do something with strs here
        }
    }
}

Java 7 et versions antérieures

Avec Java 7 et plus, nous devons utiliser l'itération et la vérification de type:

public <T> void genericMethod(T t) {
    Set<String> strs = new HashSet<String>();
    Set<?> tAsSet;
    if (t instanceof Set<?>) {
        tAsSet = (Set<?>) t;
        for (Object obj : tAsSet) {
            if (obj instanceof String) {
                strs.add((String) obj);
            }
        }
        // Do something with strs here
    } else {
        // Throw an exception or log a warning or something.
    }
}

Goyave

Selon le commentaire de Mark Peters ci-dessous, Guava a également des méthodes qui le font pour vous si vous pouvez l'ajouter à votre projet:

public <T> void genericMethod(T t) {
    if (t instanceof Set<?>) {
        Set<?> set = (Set<?>) t;
        if (Iterables.all(set, Predicates.instanceOf(String.class))) {
            Set<String> strs = (Set<String>) set;
            // Do something with strs here
        }
    }
}

L'instruction, Iterables.all(set, Predicates.instanceOf(String.class)) est essentiellement la même chose que set instanceof Set<String>.

18
Brian

Malheureusement, vous n'avez pas cette option en Java. En Java, il n'y a pas runtime différence entre un List<String> Et un List<Integer>. C'est le compilateur qui vous assure de ne jamais add() un Integer à un List<String>. Même cette application du compilateur n'est pas stricte, vous pouvez donc "légalement" faire de telles abominations avec des transtypages non contrôlés ....

Dans l'ensemble, pour (presque) toute question d'identification de type à l'exécution, vous devez prendre List<String> Pour ce qu'il est réellement: juste un List brut. C'est ce qu'on appelle l'effacement de type .

Cela dit, rien ne vous empêche d'inspecter le contenu d'un List pour leurs types:

public boolean isListOf(List<?> list, Class<?> c) {
    for (Object o : list) {
        if (!c.isInstance(o)) return false;
    }

    return true;
}

Pour utiliser cette méthode:

    // ...
    if (genericObject instanceof List<?>) {
        if (isListOf((List<?>) genericObject, String.class)) {
            @SuppressWarnings("unchecked")
            List<String> strings = (List<String>) genericObject;
        }
    }

Une observation intéressante: si le list est vide, la méthode retourne true pour tous les types donnés. En fait, il n'y a aucune différence d'exécution entre un List<String> Vide et un List<Integer> Vide que ce soit .

9
Saintali