web-dev-qa-db-fra.com

Pourquoi devrais-je me soucier que Java n'a pas de génériques réifiés?

Ceci est venu comme une question que j'ai posée dans une interview récemment comme quelque chose que le candidat souhaitait voir ajouté à la langue Java. Il est communément identifié comme une douleur que Java n'a pas génériques réifiés mais, poussé, le candidat ne pouvait pas vraiment me dire le genre de choses qu'il aurait pu réaliser s'ils étaient là.

Évidemment, parce que les types bruts sont autorisés dans Java (et les contrôles non sécurisés), il est possible de subvertir les génériques et de se retrouver avec un List<Integer> Qui (par exemple) contient réellement Strings . Cela pourrait clairement être rendu impossible si les informations de type étaient réifiées; mais il doit y en avoir plus!

Les gens pourraient-ils poster des exemples de choses qu'ils souhaiteraient vraiment faire , si des génériques réifiés étaient disponibles? Je veux dire, évidemment, vous pourriez obtenir le type d'un List au moment de l'exécution - mais qu'en feriez-vous?

public <T> void foo(List<T> l) {
   if (l.getGenericType() == Integer.class) {
       //yeah baby! err, what now?

[~ # ~] edit [~ # ~] : Une mise à jour rapide de ceci car les réponses semblent être principalement préoccupées par la nécessité de passer un Class comme paramètre (par exemple EnumSet.noneOf(TimeUnit.class)). Je cherchais plus quelque chose dans le sens de où ce n'est tout simplement pas possible. Par exemple:

List<?> l1 = api.gimmeAList();
List<?> l2 = api.gimmeAnotherList();

if (l1.getGenericType().isAssignableFrom(l2.getGenericType())) {
    l1.addAll(l2); //why on earth would I be doing this anyway?
101
oxbow_lakes

Des quelques fois où j'ai rencontré ce "besoin", cela se résume finalement à cette construction:

public class Foo<T> {

    private T t;

    public Foo() {
        this.t = new T(); // Help?
    }

}

Cela fonctionne en C # en supposant que T a un constructeur par défaut . Vous pouvez même obtenir le type d'exécution par typeof(T) et obtenir les constructeurs par Type.GetConstructor() .

La solution commune Java serait de passer le Class<T> En argument.

public class Foo<T> {

    private T t;

    public Foo(Class<T> cls) throws Exception {
        this.t = cls.newInstance();
    }

}

(il ne doit pas nécessairement être passé comme argument constructeur, car un argument de méthode est également très bien, ce qui précède est juste un exemple, aussi le try-catch est omis par souci de concision)

Pour toutes les autres constructions de type générique, le type réel peut facilement être résolu avec un peu d'aide à la réflexion. Les questions et réponses ci-dessous illustrent les cas d'utilisation et les possibilités:

81
BalusC

La chose qui me mord le plus souvent est l'incapacité de tirer parti de la répartition multiple sur plusieurs types génériques. Ce qui suit n'est pas possible et il existe de nombreux cas où ce serait la meilleure solution:

public void my_method(List<String> input) { ... }
public void my_method(List<Integer> input) { ... }
99
RHSeeger

Sécurité de type vient à l'esprit. La descente vers un type paramétré sera toujours dangereuse sans génériques réifiés:

List<String> myFriends = new ArrayList();
myFriends.add("Alice");
getSession().put("friends", myFriends);
// later, elsewhere
List<Friend> myFriends = (List<Friend>) getSession().get("friends");
myFriends.add(new Friend("Bob")); // works like a charm!
// and so...
List<String> myFriends = (List<String>) getSession().get("friends");
for (String friend : myFriends) print(friend); // ClassCastException, wtf!? 

Aussi, les abstractions fuiraient moins - au moins celles qui pourraient être intéressées par les informations d'exécution sur leurs paramètres de type. Aujourd'hui, si vous avez besoin d'informations sur le type d'exécution de l'un des paramètres génériques, vous devez également transmettre son Class. De cette façon, votre interface externe dépend de votre implémentation (que vous utilisiez RTTI sur vos paramètres ou non).

34
gustafc

Vous seriez en mesure de créer des tableaux génériques dans votre code.

public <T> static void DoStuff() {
    T[] myArray = new T[42]; // No can do
}
26
Turnor

C'est une vieille question, il y a une tonne de réponses, mais je pense que les réponses existantes sont hors de propos.

"réifié" signifie juste réel et signifie généralement juste l'opposé de l'effacement de type.

Le gros problème lié à Java Generics:

  • Cette horrible exigence de boxe et de déconnexion entre les primitifs et les types de référence. Ce n'est pas directement lié à la réification ou à l'effacement de type. C #/Scala corrige cela.
  • Pas d'auto types. JavaFX 8 a dû supprimer les "constructeurs" pour cette raison. Absolument rien à voir avec l'effacement de caractères. Scala corrige cela, pas sûr de C #.
  • Aucune variance de type côté déclaration. C # 4.0/Scala l'ont. Absolument rien à voir avec l'effacement de caractères.
  • Impossible de surcharger void method(List<A> l) et method(List<B> l). Cela est dû à l'effacement du type mais est extrêmement mesquin.
  • Pas de prise en charge pour la réflexion de type runtime. C'est le cœur de l'effacement de type. Si vous aimez les compilateurs super avancés qui vérifient et prouvent autant de votre logique de programme au moment de la compilation, vous devez utiliser le moins possible la réflexion et ce type d'effacement de type ne devrait pas vous déranger. Si vous aimez une programmation de type plus fragmentaire, plus simple et plus dynamique et que vous ne vous souciez pas tellement d'un compilateur qui prouve le plus possible votre logique, alors vous voulez une meilleure réflexion et fixer l'effacement du type est important.
16
user2684301

La sérialisation serait plus simple avec la réification. Ce que nous voudrions, c'est

deserialize(thingy, List<Integer>.class);

Ce que nous devons faire c'est

deserialize(thing, new TypeReference<List<Integer>>(){});

semble laid et fonctionne de façon funky.

Il y a aussi des cas où il serait vraiment utile de dire quelque chose comme

public <T> void doThings(List<T> thingy) {
    if (T instanceof Q)
      doCrazyness();
  }

Ces choses ne mordent pas souvent, mais elles mordent quand elles se produisent.

12
Cogman

Mon exposition à Java Geneircs est assez limitée, et à part les points que d'autres réponses ont déjà mentionnés, il y a un scénario expliqué dans le livre Génériques Java et collections, par Maurice Naftalin et Philip Walder, où les génériques réifiés sont utiles.

Comme les types ne sont pas réifiables, il n'est pas possible d'avoir des exceptions paramétrées.

Par exemple, la déclaration du formulaire ci-dessous n'est pas valide.

class ParametericException<T> extends Exception // compile error

En effet, le capture La clause vérifie si l'exception levée correspond à un type donné. Cette vérification est identique à la vérification effectuée par le test d'instance et puisque le type n'est pas réifiable, la forme de déclaration ci-dessus n'est pas valide.

Si le code ci-dessus était valide, la gestion des exceptions de la manière suivante aurait été possible:

try {
     throw new ParametericException<Integer>(42);
} catch (ParametericException<Integer> e) { // compile error
  ...
}

Le livre mentionne également que si Java sont définis de la même manière que les modèles C++ sont définis (expansion), cela peut conduire à une implémentation plus efficace car cela offre plus de possibilités d'optimisation. Mais n'offre pas toute explication plus que cela, donc toute explication (pointeurs) des gens bien informés serait utile.

11
sateesh

Les tableaux joueraient probablement beaucoup mieux avec les génériques s'ils étaient réifiés.

8
Hank Gay

Une bonne chose serait d'éviter la boxe pour les types primitifs (valeur). Ceci est quelque peu lié à la plainte de tableau que d'autres ont soulevée, et dans les cas où l'utilisation de la mémoire est limitée, cela pourrait réellement faire une différence significative.

Il existe également plusieurs types de problèmes lors de l'écriture d'un framework où il est important de pouvoir réfléchir sur le type paramétré. Bien sûr, cela peut être contourné en passant un objet de classe autour lors de l'exécution, mais cela obscurcit l'API et impose une charge supplémentaire à l'utilisateur du framework.

5
kvb

J'ai un wrapper qui présente un jeu de résultats jdbc en tant qu'itérateur (cela signifie que je peux tester les opérations d'origine de la base de données beaucoup plus facilement grâce à l'injection de dépendances).

L'API ressemble à Iterator<T> où T est un type qui peut être construit en utilisant uniquement des chaînes dans le constructeur. L'itérateur examine ensuite les chaînes renvoyées par la requête SQL, puis essaie de les faire correspondre à un constructeur de type T.

Dans la manière actuelle dont les génériques sont implémentés, je dois également passer dans la classe des objets que je vais créer à partir de mon jeu de résultats. Si je comprends bien, si les génériques étaient réifiés, je pourrais simplement appeler T.getClass () pour obtenir ses constructeurs, et ne pas avoir à lancer le résultat de Class.newInstance (), ce qui serait bien plus net.

Fondamentalement, je pense que cela facilite l'écriture d'API (au lieu de simplement écrire une application), car vous pouvez en déduire beaucoup plus à partir d'objets, et donc moins de configuration sera nécessaire ... Je n'ai pas apprécié les implications des annotations jusqu'à ce que je les a vu être utilisés dans des choses comme le printemps ou xstream au lieu de rames de config.

5
James B

Ce n'est pas que vous réaliserez quelque chose d'extraordinaire. Ce sera simplement plus simple à comprendre. L'effacement de type semble être une période difficile pour les débutants, et cela nécessite finalement une compréhension du fonctionnement du compilateur.

Mon opinion est que les génériques sont simplement n extra qui économise beaucoup de casting redondant.

2
Bozho

Quelque chose que toutes les réponses ici ont manqué et qui est constamment un casse-tête pour moi, c'est que les types sont effacés, vous ne pouvez pas hériter deux fois d'une interface générique. Cela peut être un problème lorsque vous souhaitez créer des interfaces à grain fin.

    public interface Service<KEY,VALUE> {
           VALUE get(KEY key);
    }

    public class PersonService implements Service<Long, Person>,
        Service<String, Person> //Can not do!!
1
ExCodeCowboy

En voici une qui m'a attrapé aujourd'hui: sans réification, si vous écrivez une méthode qui accepte une liste varargs d'éléments génériques ... les appelants peuvent PENSER qu'ils sont sécuritaires, mais accidentellement passer dans n'importe quel vieux crud, et faire exploser votre méthode.

Semble peu probable que cela se produise? ... Bien sûr, jusqu'à ce que ... vous utilisiez Class comme type de données. À ce stade, votre appelant vous enverra avec plaisir de nombreux objets de classe, mais une simple faute de frappe vous enverra des objets de classe qui n'adhèrent pas à T et des catastrophes surviennent.

(NB: J'ai peut-être fait une erreur ici, mais en parcourant les "varargs génériques", ce qui précède semble être exactement ce à quoi vous vous attendez. La chose qui en fait un problème pratique est l'utilisation de Class, je pense - les appelants semblent être moins prudent :()


Par exemple, j'utilise un paradigme qui utilise des objets Class comme clé dans les cartes (c'est plus complexe qu'une simple carte - mais conceptuellement c'est ce qui se passe).

par exemple. cela fonctionne très bien dans Java Generics (exemple trivial):

public <T extends Component> Set<UUID> getEntitiesPossessingComponent( Class<T> componentType)
    {
        // find the entities that are mapped (somehow) from that class. Very type-safe
    }

par exemple. sans réification dans Java Generics, celui-ci accepte N'IMPORTE QUEL objet "Classe". Et ce n'est qu'une minuscule extension du code précédent:

public <T extends Component> Set<UUID> getEntitiesPossessingComponents( Class<T>... componentType )
    {
        // find the entities that are mapped (somehow) to ALL of those classes
    }

Les méthodes ci-dessus doivent être écrites des milliers de fois dans un projet individuel - ainsi la possibilité d'erreur humaine devient élevée. Le débogage des erreurs s'avère "pas amusant". J'essaie actuellement de trouver une alternative, mais je n'ai pas beaucoup d'espoir.

0
Adam