Question Fonction générique Java: comment retourner le type générique


Voici un modèle générique Java:

public <T> T getResultData(Class<T> resultClass, other_args) { 
   ...
   return resultClass.cast(T-thing);
}

Un appel typique ressemble à:

   DoubleBuffer buffer;
   buffer = thing.getResultData(DoubleBuffer.class, args);

Je n'ai jamais été capable de comprendre comment utiliser ce modèle proprement lorsque le type de retour souhaité est lui-même générique. Pour être «concret», si une fonction comme celle-ci veut revenir Map<String,String>? Comme vous ne pouvez pas obtenir un objet de classe pour un générique, bien sûr, la seule option serait de passer Map.class, et alors vous avez besoin d'un casting et d'un @SuppressWarning après tout.

On se retrouve avec un appel comme:

Map<String, String> returnedMap;
returnedMap = thing.getResultData(Map.class, some_other_args);

Maintenant, on revient au besoin de lancer et de supprimer un avertissement.

Je suppose que l'on pourrait prendre quelque chose de la java.lang.reflect.Type famille au lieu de la Class, mais ceux-ci ne sont pas particulièrement faciles à concocter. Cela ressemble à:

class Dummy {
 Map<String, String> field;
}

...

Type typeObject = Dummy.class.getField("field").getGenericType();

Compte tenu de cela, la fonction appelée pourrait extraire le type de base et l'utiliser pour la distribution ou newInstance-ing, mais l'activité de champ factice est moche.

Notez que de telles fonctions n'appellent pas toujours newInstance. Evidemment, s’ils le font, ils n’ont pas besoin d’appeler resultClass.cast.


13
2017-12-24 16:58


origine


Réponses:


Vous ne pouvez pas faire cela dans l'API Java standard. Cependant, il existe des classes d’utilité disponibles qui peuvent Type hors d'une classe générique. Dans par exemple Google Gson (qui convertit JSON en Javabeans à part entière et vice versa), il y a un TypeToken classe. Vous pouvez l'utiliser comme suit:

List<Person> persons = new Gson().fromJson(json, new TypeToken<List<Person>>() {}.getType());

Une telle construction est exactement ce dont vous avez besoin. Tu peux trouver ici le code source, vous pouvez également le trouver utile. Il ne nécessite qu'un supplément getResultData() méthode qui peut accepter un Type.


7
2017-12-24 17:35



Donc, si j'ai bien compris ce que vous essayez vraiment de faire, c'est (pseudo-syntaxe illégale):

public <T, S, V> T<S, V> getResultData(Class<T<S, V>> resultClass, other_args) { 
  ...
  return resultClass.cast(T-thing);
}

Vous ne pouvez pas parce que vous ne pouvez pas paramétrer davantage le type générique T. Déranger avec java.lang.reflect.* ne va pas aider:

  1. Il n'y a pas de telle chose Class<Map<String, String>> et donc aucun moyen de créer dynamiquement son instance.
  2. Il n'y a aucun moyen de réellement déclarer la méthode en question comme le retour T<S, V>

1
2017-12-24 17:14



Bien,. voici une solution laide (partielle). Vous ne pouvez pas paramétrer de manière générique un paramètre générique, vous ne pouvez donc pas écrire

public <T<U,V>> T getResultData ...

mais vous pouvez retourner une collection paramétrée, par ex. pour un ensemble

public < T extends Set< U >, U > T getResultData( Class< T > resultClass, Class< U > paramClass ) throws IllegalAccessException, InstantiationException {
        return resultClass.newInstance();
    }

où vous pourriez vous débarrasser de U-param s'il existait dans la signature de la classe.


1
2017-12-24 17:17



Vous pouvez utiliser TypeLiteral si vous souhaitez utiliser des classes de conteneur typesafe. Ils sont utilisés dans Guice fournir des liaisons typesafe. Voir le code source du guice pour plus d'exemples et TypeLiteral classe.

Au fait, vous n'avez pas besoin de lancer, si vous appelez resultClass.newInstance() par exemple.

public <T> T getResultData(Class<T> resultClass, other_args) {       
   ...
   return resultClass.newInstance();
}

1
2017-12-24 17:02



Oui, je ne pense pas que ce soit possible.

Imaginez ce scénario. Vous avez un fichier avec un objet sérialisé que vous voulez lire. Si vous rendez la fonction générique, vous pourrez l'utiliser sans distribution. Mais disons que vous avez une carte sérialisée dans un fichier.

Lorsque vous le désérialisez, il y a pas moyen de savoir Quel était le type de carte générique d'origine? En ce qui concerne java, c'est juste une carte

Je me suis heurté à cela avec des listes, et c'était agaçant. Mais ce que j'ai fini par faire, c'est créer une classe générique de "conversion" qui prend une collection et un "convertisseur" (qui peut être un type interne anonyme).

Ensuite, lorsque vous parcourez la collection de conversion, chaque élément est converti. Et cela a résolu le problème d'avertissement. C'est beaucoup de travail juste pour se débarrasser de cet avertissement, mais je ne pense pas que ce soit la principale raison pour laquelle je suis venu avec ce cadre. Vous pouvez également faire des choses plus puissantes, comme prendre une collection de noms de fichiers, puis écrire un convertisseur qui charge les données dans le fichier ou effectue une requête, etc.


1
2017-12-24 19:19



Si vous n'avez pas vraiment besoin du type générique exact de la carte et que votre code ne repose pas sur ce type mais que vous êtes simplement agacé par les avertissements que vous pourriez écrire:

Map<?, ?> returnedMap;
returnedMap = thing.getResultData(Map.class, some_other_args);

Vous pouvez alors utiliser n'importe quelle méthode de carte non paramétrée comme containsKey, clear, iterator, etc.

Map<?, ?> returnedMap;
returnedMap = thing.getResultData(Map.class, some_other_args);

Object myKey = ...;

Object someValue = returnedMap.get(myKey);

for (Iterator<? extends Map.Entry<?,?>> it = returnedMap.entrySet().iterator(); it.hasNext(); )
  if (it.next().equals(myKey))
    it.remove();

for (Map.Entry<?,?> e : returnedMap.entrySet()) {
  if (e.getKey().equals(myKey))
    e.setValue(null); // if the map supports null values
}

doSomething(returnedMap);

...

<K,V> void doSomething(Map<K,V> map) { ... }

Il y a en fait beaucoup de choses que vous pouvez faire avec une carte de manière sécurisée sans connaître sa clé ou son type de valeur.


1
2017-08-04 16:37