web-dev-qa-db-fra.com

Passer une classe avec le paramètre type comme paramètre type pour la méthode générique dans Java

Résumé du problème: Je voudrais passer une classe avec un paramètre de type (tel que ArrayList<SomeClass>, par exemple) à une méthode générique en tant que paramètre de type.

Disons que j'ai une méthode:

    public static <T> T getGenericObjectFromJson(String json, Class<T> genericType){
        // details unimportant, basically returns an object of specified type
        return JsonParser.fromJson(json, genericType); 
    }

Bien entendu, cette méthode fonctionnera parfaitement pour tout type de cours. Je peux appeler la méthode comme suit, par exemple:

getGenericObjectFromJson(jsonString, User.class)

Le problème: J'ai découvert que je ne pouvais pas faire ça:

getGenericObjectFromJson(jsonString, ArrayList<User>.class)

Syntaxiquement, ceci est évidemment invalide. Cependant, je ne suis pas certain de la manière dont je pourrais accomplir quelque chose comme ça. Je peux bien sûr passer ArrayList.class, toutefois, l’ajout du type générique le rend plus valide du point de vue de la syntaxe et je ne vois pas comment le contourner.

La seule solution directe a été quelque chose comme ceci (qui semble plutôt maladroit):

getGenericObjectFromJson(jsonString, new ArrayList<User>().getClass())

Cependant, nous finissons quand même par perdre le type générique et récupérons simplement une ArrayList de type inconnu (bien qu'elle puisse être convertie). De plus, instancier inutilement un objet.

Ma seule solution jusqu’à présent a été d’envelopper cette méthode dans une classe contenant un paramètre de type générique pouvant être instancié, comme suit:

public class JsonDeserializer<T>...

Dans ce cas, la méthode getGenericObjectFromJson utilisera le type générique de la classe.

La question (s): En fin de compte, je suis curieux de savoir pourquoi je ne peux pas passer une classe avec un paramètre de type, ET s'il existe un moyen d'accomplir ce que j'ai tenté de faire.

Comme toujours, laissez-moi savoir s'il y a des problèmes avec cette question.

52
Paul Richter

Ceci est effectivement possible en Java, en utilisant quelques "astuces". Ne succombez pas à la pression des fanatiques C #! (j/k)

Le "truc" consiste à créer une classe qui étend un type générique et à accéder à la valeur du paramètre type de la classe par le biais du Type renvoyé par .getGenericSuperclass() ou .getGenericInterfaces().

C'est assez encombrant. Pour simplifier nos vies, Google a déjà écrit la majeure partie de la partie ennuyeuse du code et l'a rendu disponible via Guava.

Vérifiez la classe TypeToken, qui fait exactement ce que vous voulez. Par exemple:

TypeToken<List<String>> stringListTok = new TypeToken<List<String>>() {};

Ensuite, vous passez autour de TypeToken<T> Au lieu de Class<T> Et c'est tout. Il vous fournit des méthodes pour réfléchir au type représenté par T.

En interne, on appelle simplement .getClass().getGenericSuperclass() (ou ...Interfaces()), puis de jolis moulages de Type à ParameterizedType et de récupérer toutes les informations de il (.getActualTypeArguments(), etc).

Enfin, si vous voulez faire quelque chose de similaire avec Dependency Injection (c'est-à-dire, supposons que vous deviez injecter un Class<T> Sur le constructeur d'une classe, ou que vous vouliez obtenir une instance d'une interface paramétrée, dans laquelle l'instance dépend du paramètre type), Google Guice (un conteneur DI de Google) utilise un mécanisme très similaire pour résoudre le problème, appelé TypeLiteral. L'utilisation et le code dans les coulisses sont presque identiques à TypeToken de Guava. Vérifiez-le ici: TypeLiteral

59
Bruno Reis