Quels sont les avantages de l'effacement des types Java?


98

J'ai lu un tweet aujourd'hui qui disait:

C'est drôle quand les utilisateurs de Java se plaignent de l'effacement de type, qui est la seule chose que Java a eu raison, tout en ignorant tout ce qui ne va pas.

Ainsi ma question est:

L'effacement de type Java présente-t-il des avantages? Quels sont les avantages techniques ou de style de programmation qu'il offre (éventuellement) autres que la préférence des implémentations JVM pour la rétrocompatibilité et les performances d'exécution?


6
Si vous ne voulez pas que votre question soit fermée comme "basée sur une opinion", vous devriez la poser différemment. (Comme "quels sont les avantages techniques de l'effacement de caractères"). Mieux encore, demandez au tweeter ce qu'il dit vraiment.
Stephen C

3
Supprimez le "pourquoi est-ce une bonne chose". La "bonne chose" est une caractérisation manifestement subjective, et cela soulève (évidemment) la question que l'effacement de type est une bonne chose ... ce qui est controversé.
Stephen C

3
Que Dieu nous aide si ce site va dégénérer en questions sur tout ce qui a jamais été publié sur Twitter. Au moins, citez une source fiable pour votre question.
Marquis of Lorne le

21
Pourquoi la question doit-elle provenir d'une «source fiable»? C'est juste idiot.
vertti

4
Eh bien, la plupart des problèmes ici ne proviennent d'aucune source, à l'exception des problèmes propres aux questionneurs avec un logiciel / langage / quoi que ce soit spécifique, et heureusement il y a beaucoup de professionnels qui «perdent» leur temps à aider ces personnes.
vertti

Réponses:


90

L'effacement de type est bon

Tenons-nous en aux faits

Un grand nombre de réponses à ce jour concernent trop l'utilisateur Twitter. Il est utile de rester concentré sur les messages et non sur le messager. Il y a un message assez cohérent avec même juste les extraits mentionnés jusqu'à présent:

C'est drôle quand les utilisateurs de Java se plaignent de l'effacement de type, qui est la seule chose que Java a eu raison, tout en ignorant tout ce qui ne va pas.

J'obtiens d'énormes avantages (par exemple la paramétrie) et un coût nul (le coût présumé est une limite d'imagination).

new T est un programme cassé. Il est isomorphe à l'affirmation «toutes les propositions sont vraies». Je ne suis pas grand dedans.

Un objectif: des programmes raisonnables

Ces tweets reflètent une perspective qui ne s'intéresse pas à savoir si nous pouvons faire faire quelque chose à la machine , mais plutôt à savoir si nous pouvons penser que la machine fera quelque chose que nous voulons réellement. Un bon raisonnement est une preuve. Les preuves peuvent être spécifiées en notation formelle ou quelque chose de moins formel. Quel que soit le langage de spécification, ils doivent être clairs et rigoureux. Les spécifications informelles ne sont pas impossibles à structurer correctement, mais sont souvent défectueuses dans la programmation pratique. Nous nous retrouvons avec des remédiations comme des tests automatisés et exploratoires pour compenser les problèmes que nous avons avec le raisonnement informel. Cela ne veut pas dire que les tests sont intrinsèquement une mauvaise idée, mais l'utilisateur de Twitter cité suggère qu'il existe un bien meilleur moyen.

Notre objectif est donc d'avoir des programmes corrects sur lesquels nous pouvons raisonner clairement et rigoureusement d'une manière qui correspond à la manière dont la machine exécutera réellement le programme. Ce n’est cependant pas le seul objectif. Nous voulons également que notre logique ait un certain degré d'expressivité. Par exemple, il n'y a que tant de choses que nous pouvons exprimer avec la logique propositionnelle. C'est bien d'avoir une quantification universelle (∀) et existentielle (∃) à partir de quelque chose comme la logique du premier ordre.

Utiliser des systèmes de types pour le raisonnement

Ces objectifs peuvent être très bien traités par les systèmes de types. Cela est particulièrement clair en raison de la correspondance Curry-Howard . Cette correspondance est souvent exprimée par l'analogie suivante: les types sont aux programmes comme les théorèmes sont aux preuves.

Cette correspondance est assez profonde. Nous pouvons prendre des expressions logiques et les traduire par la correspondance aux types. Ensuite, si nous avons un programme avec le même type de signature qui compile, nous avons prouvé que l'expression logique est universellement vraie (une tautologie). C'est parce que la correspondance est bidirectionnelle. La transformation entre les mondes type / programme et théorème / preuve est mécanique et peut dans de nombreux cas être automatisée.

Curry-Howard joue bien dans ce que nous aimerions faire avec les spécifications d'un programme.

Les systèmes de types sont-ils utiles en Java?

Même avec une compréhension de Curry-Howard, certaines personnes trouvent qu'il est facile de rejeter la valeur d'un système de types, quand il

  1. il est extrêmement difficile de travailler avec
  2. correspond (par Curry-Howard) à une logique à expressivité limitée
  3. est cassé (ce qui permet de caractériser les systèmes comme «faibles» ou «forts»).

En ce qui concerne le premier point, peut-être que les IDE rendent le système de types de Java assez facile à utiliser (c'est très subjectif).

En ce qui concerne le deuxième point, Java correspond presque à une logique du premier ordre. Les génériques donnent à utiliser l'équivalent du système de type de la quantification universelle. Malheureusement, les jokers ne nous donnent qu'une petite fraction de la quantification existentielle. Mais la quantification universelle est un bon début. C'est bien de pouvoir dire que cela fonctionne de manière List<A>universelle pour toutes les listes possibles car A est complètement libre. Cela conduit à ce dont l'utilisateur de Twitter parle en ce qui concerne la «paramétrie».

Un article souvent cité sur la paramétrie est les théorèmes de Philip Wadler gratuitement! . Ce qui est intéressant à propos de cet article, c'est qu'à partir de la seule signature de type, nous pouvons prouver quelques invariants très intéressants. Si nous devions écrire des tests automatisés pour ces invariants, nous perdrions beaucoup de temps. Par exemple, pour List<A>, à partir de la signature de type uniquement pourflatten

<A> List<A> flatten(List<List<A>> nestedLists);

on peut raisonner que

flatten(nestedList.map(l -> l.map(any_function)))
     flatten(nestList).map(any_function)

C'est un exemple simple, et vous pouvez probablement raisonner à ce sujet de manière informelle, mais c'est encore plus agréable lorsque nous obtenons de telles preuves formellement gratuitement à partir du système de types et vérifiées par le compilateur.

Ne pas effacer peut conduire à des abus

Du point de vue de l'implémentation du langage, les génériques de Java (qui correspondent à des types universels) jouent très fortement dans la paramétrie utilisée pour obtenir des preuves de ce que font nos programmes. Cela arrive au troisième problème mentionné. Tous ces gains de preuve et d'exactitude nécessitent un système de type sonore implémenté sans défauts. Java a certainement des fonctionnalités de langage qui nous permettent de briser notre raisonnement. Ceux-ci incluent, mais ne sont pas limités à:

  • effets secondaires avec un système externe
  • réflexion

Les génériques non effacés sont à bien des égards liés à la réflexion. Sans effacement, des informations d'exécution sont fournies avec l'implémentation que nous pouvons utiliser pour concevoir nos algorithmes. Cela signifie que statiquement, lorsque nous raisonnons sur les programmes, nous n'avons pas une vue d'ensemble complète. La réflexion menace gravement l'exactitude de toutes les preuves sur lesquelles nous raisonnons statiquement. Ce n'est pas une coïncidence, la réflexion conduit également à une variété de défauts délicats.

Alors, quelles sont les façons dont les génériques non effacés pourraient être «utiles»? Considérons l'utilisation mentionnée dans le tweet:

<T> T broken { return new T(); }

Que se passe-t-il si T n'a pas de constructeur sans argument? Dans certaines langues, ce que vous obtenez est nul. Ou peut-être ignorez-vous la valeur nulle et passez directement à la levée d'une exception (à laquelle les valeurs nulles semblent conduire de toute façon). Parce que notre langage est Turing complet, il est impossible de raisonner pour savoir quels appels à brokenimpliqueront des types "sûrs" avec des constructeurs sans argument et lesquels ne le seront pas. Nous avons perdu la certitude que notre programme fonctionne universellement.

Effacer signifie que nous avons raisonné (alors effaçons)

Donc, si nous voulons raisonner sur nos programmes, il nous est fortement déconseillé d'utiliser des fonctionnalités de langage qui menacent fortement notre raisonnement. Une fois que nous avons fait cela, alors pourquoi ne pas simplement supprimer les types au moment de l'exécution? Ils ne sont pas nécessaires. Nous pouvons obtenir une certaine efficacité et simplicité avec la satisfaction qu'aucune conversion n'échouera ou que des méthodes pourraient manquer lors de l'invocation.

L'effacement encourage le raisonnement.


7
Pendant le temps que j'ai pris pour préparer cette réponse, d'autres personnes ont répondu avec des réponses similaires. Mais après avoir beaucoup écrit, j'ai décidé de le poster plutôt que de le supprimer.
Sukant Hajra

32
Avec égards, l'effacement de type était une demi-tentative pour apporter une fonctionnalité à Java sans rompre la compatibilité ascendante. Ce n'est pas une force. Si la principale préoccupation était que vous pourriez essayer d'instancier un objet sans constructeur sans paramètre, la bonne réponse est d'autoriser une contrainte de «types avec des constructeurs sans paramètre», et non de paralyser une fonctionnalité du langage.
base

5
De base, avec respect, vous ne faites pas un argument cohérent. Vous affirmez simplement que l'effacement est "paralysant" avec un mépris total pour la valeur des preuves à la compilation en tant qu'outil convaincant pour raisonner sur les programmes. De plus, ces avantages sont complètement orthogonaux du chemin tortueux et de la motivation que Java a pris pour implémenter des génériques. En outre, ces avantages sont valables pour les langues avec une mise en œuvre beaucoup plus éclairée de l'effacement.
Sukant Hajra

44
L'argument présenté ici n'est pas contre l'effacement, il est contre la réflexion (et la vérification du type d'exécution en général) - il affirme fondamentalement que la réflexion est mauvaise, donc le fait que l'effacement ne joue pas bien avec eux est bon. C'est un point valable en soi (bien que beaucoup de gens ne soient pas d'accord); mais cela n'a pas beaucoup de sens dans le contexte, car Java a déjà des contrôles de réflexion / d'exécution, et ils ne disparaissent pas et ne disparaîtront pas. Donc, avoir une construction qui ne joue pas avec cette partie existante et non obsolète du langage est une limitation, peu importe comment vous la regardez.
Pavel Minaev

10
Votre réponse est tout simplement hors sujet. Vous entrez une longue explication (bien qu'intéressante en soi) expliquant pourquoi l'effacement de type en tant que concept abstrait est généralement utile dans la conception de systèmes de type, mais le sujet est l'avantage d'une caractéristique spécifique de Java Generics appelée «effacement de type ", qui ne ressemble que superficiellement au concept que vous décrivez, échouant complètement à fournir les invariants intéressants et introduisant des contraintes déraisonnables.
Marko Topolnik

41

Les types sont une construction utilisée pour écrire des programmes d'une manière qui permet au compilateur de vérifier l'exactitude d'un programme. Un type est une proposition sur une valeur - le compilateur vérifie que cette proposition est vraie.

Pendant l'exécution d'un programme, il ne devrait pas y avoir besoin d'informations de type - cela a déjà été vérifié par le compilateur. Le compilateur doit être libre de rejeter ces informations afin d'effectuer des optimisations sur le code - faites-le fonctionner plus rapidement, générez un binaire plus petit, etc. L'effacement des paramètres de type facilite cela.

Java rompt le typage statique en permettant aux informations de type d'être interrogées au moment de l'exécution - réflexion, instanceof etc. Cela vous permet de construire des programmes qui ne peuvent pas être vérifiés statiquement - ils contournent le système de types. Il manque également des opportunités d'optimisation statique.

Le fait que les paramètres de type soient effacés empêche la construction de certaines instances de ces programmes incorrects, cependant, des programmes plus incorrects seraient interdits si plus d'informations de type étaient effacées et la réflexion et l'instance des fonctionnalités étaient supprimées.

L'effacement est important pour maintenir la propriété de «paramétrie» d'un type de données. Disons que j'ai un type "Liste" paramétré sur le type de composant T. ie List <T>. Ce type est une proposition selon laquelle ce type List fonctionne de la même manière pour tout type T.Le fait que T soit un paramètre de type abstrait et illimité signifie que nous ne savons rien de ce type et que nous sommes donc empêchés de faire quoi que ce soit de spécial pour des cas particuliers de T.

par exemple, disons que j'ai une liste xs = asList ("3"). J'ajoute un élément: xs.add ("q"). Je me retrouve avec ["3", "q"]. Puisque c'est paramétrique, je peux supposer que List xs = asList (7); xs.add (8) se termine par [7,8] Je sais du type qu'il ne fait pas une chose pour String et une chose pour Int.

De plus, je sais que la fonction List.add ne peut pas inventer des valeurs de T à partir de rien. Je sais que si mon asList ("3") a un "7" ajouté, les seules réponses possibles seraient construites à partir des valeurs "3" et "7". Il n'y a aucune possibilité qu'un "2" ou un "z" soit ajouté à la liste car la fonction serait incapable de la construire. Aucune de ces autres valeurs ne serait judicieuse à ajouter, et la paramétrie empêche la construction de ces programmes incorrects.

Fondamentalement, l'effacement empêche certains moyens de violer la paramétrie, éliminant ainsi les possibilités de programmes incorrects, ce qui est le but du typage statique.


15

(Bien que j'aie déjà écrit une réponse ici, revisitant cette question deux ans plus tard, je me rends compte qu'il existe une autre façon complètement différente d'y répondre, alors je laisse la réponse précédente intacte et j'ajoute celle-ci.)


Il est hautement discutable si le processus effectué sur Java Generics mérite le nom «effacement de type». Puisque les types génériques ne sont pas effacés mais remplacés par leurs homologues bruts, un meilleur choix semble être la "mutilation de type".

La caractéristique essentielle de l'effacement de type dans son sens communément compris oblige le moteur d'exécution à rester dans les limites du système de type statique en le rendant "aveugle" à la structure des données auxquelles il accède. Cela donne toute la puissance au compilateur et lui permet de prouver des théorèmes basés uniquement sur des types statiques. Il aide également le programmeur en contraignant les degrés de liberté du code, donnant plus de puissance au raisonnement simple.

L'effacement de type Java ne permet pas d'y parvenir - il paralyse le compilateur, comme dans cet exemple:

void doStuff(List<Integer> collection) { 
}

void doStuff(List<String> collection) // ERROR: a method cannot have 
                   // overloads which only differ in type parameters

(Les deux déclarations ci-dessus se réduisent à la même signature de méthode après l'effacement.)

D'un autre côté, le runtime peut toujours inspecter le type d'un objet et en raisonner, mais comme son aperçu du vrai type est paralysé par l'effacement, les violations de type statique sont simples à réaliser et difficiles à éviter.

Pour rendre les choses encore plus alambiquées, les signatures de type original et effacé coexistent et sont considérées en parallèle lors de la compilation. En effet, l'ensemble du processus ne consiste pas à supprimer les informations de type du runtime, mais à intégrer un système de type générique dans un système de type brut hérité pour maintenir la compatibilité ascendante. Ce bijou est un exemple classique:

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)

(Le redondant extends Objecta dû être ajouté pour préserver la compatibilité descendante de la signature effacée.)

Maintenant, avec cela à l'esprit, revisitons la citation:

C'est drôle quand les utilisateurs de Java se plaignent de l'effacement de type, qui est la seule chose que Java a bien

Qu'est- ce que Java a fait exactement ? Est-ce le mot lui-même, quel qu'en soit le sens? Pour le contraste, regardez le inttype humble : aucune vérification de type à l'exécution n'est jamais effectuée, ni même possible, et l'exécution est toujours parfaitement sûre de type. Voilà à quoi ressemble l'effacement de type lorsqu'il est bien fait: vous ne savez même pas qu'il est là.


10

La seule chose que je ne vois pas du tout considérée ici est que le polymorphisme d'exécution de la POO dépend fondamentalement de la réification des types au moment de l'exécution. Lorsqu'un langage dont l'épine dorsale est maintenue en place par des types refieds introduit une extension majeure de son système de types et le fonde sur l'effacement des types, la dissonance cognitive est le résultat inévitable. C'est précisément ce qui est arrivé à la communauté Java; c'est pourquoi l'effacement de type a suscité tant de controverses, et finalement pourquoi il est prévu de l'annuler dans une future version de Java . Trouver quelque chose de drôle dans cette plainte des utilisateurs de Java trahit soit un malentendu honnête de l'esprit de Java, soit une blague délibérément dépréciante.

L'affirmation «l'effacement est la seule chose que Java a bien» implique l'affirmation que «tous les langages basés sur une répartition dynamique par rapport au type d'exécution de l'argument de fonction sont fondamentalement défectueux». Bien que certainement une revendication légitime en soi, et qui peut même être considérée comme une critique valable de tous les langages OOP, y compris Java, elle ne peut pas se présenter comme un point pivot à partir duquel évaluer et critiquer des fonctionnalités dans le contexte de Java , où le polymorphisme d'exécution est axiomatique.

En résumé, alors que l'on peut valablement affirmer que "l'effacement de type est la voie à suivre dans la conception d'un langage", les positions prenant en charge l'effacement de type dans Java sont mal placées simplement parce qu'il est beaucoup, beaucoup trop tard pour cela et l'avait déjà été même au moment historique quand Oak a été adopté par Sun et renommé Java.



Quant à savoir si le typage statique lui-même est la bonne direction dans la conception des langages de programmation, cela s'inscrit dans un contexte philosophique beaucoup plus large de ce que nous pensons constituer l'activité de programmation . Une école de pensée, qui dérive clairement de la tradition classique des mathématiques, voit les programmes comme des instances d'un concept mathématique ou d'un autre (propositions, fonctions, etc.), mais il existe une classe d'approches entièrement différente, qui voit la programmation comme un moyen parlez à la machine et expliquez ce que nous en attendons. Dans cette optique, le programme est une entité dynamique, en croissance organique, à l'opposé dramatique de l'édifice soigneusement érigé d'un programme typé statiquement.

Il semblerait naturel de considérer les langages dynamiques comme un pas dans cette direction: la cohérence du programme émerge de bas en haut, sans interprétations a priori qui l'imposeraient de manière descendante. Ce paradigme peut être considéré comme une étape vers la modélisation du processus par lequel nous, les humains, devenons ce que nous sommes par le développement et l'apprentissage.


Merci pour ça. C'est le genre exact de compréhension plus profonde des philosophies impliquées que je m'attendais à voir pour cette question.
Daniel Langdon

La répartition dynamique n'a aucune dépendance vis-à-vis des types réifiés. Il repose souvent sur ce que la littérature de programmation appelle des «balises», mais même dans ce cas, il n'a pas besoin d'utiliser des balises. Si vous créez une interface (pour un objet) et que vous créez des instances anonymes de cette interface, alors vous effectuez une répartition dynamique sans avoir besoin du tout de types réifiés.
Sukant Hajra

@sukanthajra C'est juste la division des cheveux. La balise est les informations d'exécution dont vous avez besoin pour résoudre le type de comportement présenté par l'instance. Il est hors de portée du système de type statique, et c'est l'essence du concept en discussion.
Marko Topolnik

Il existe un type très statique conçu exactement pour ce raisonnement appelé le «type somme». On l'appelle aussi un "type variant" (dans le très bon livre de Benjamin Pierce sur les types et les langages de programmation). Donc, ce n'est pas du tout une fracture des cheveux. En outre, vous pouvez utiliser un catamorphisme / repli / visiteur pour une approche complètement sans étiquette.
Sukant Hajra

Merci pour la leçon de théorie, mais je ne vois pas la pertinence. Notre sujet est le système de types de Java.
Marko Topolnik

9

Une publication ultérieure du même utilisateur dans la même conversation:

new T est un programme cassé. Il est isomorphe à l'affirmation «toutes les propositions sont vraies». Je ne suis pas grand dedans.

(C'était en réponse à une déclaration d'un autre utilisateur, à savoir que "il semble que dans certaines situations, le" nouveau T "serait mieux", l'idée étant qu'il new T()est impossible en raison de l'effacement de type. (Ceci est discutable - même si Tétaient disponibles à runtime, cela pourrait être une classe ou une interface abstraite, ou cela pourrait être Void, ou il pourrait manquer d'un constructeur no-arg, ou son constructeur no-arg pourrait être privé (par exemple, parce qu'il est supposé être une classe singleton), ou son Le constructeur no-arg pourrait spécifier une exception vérifiée que la méthode générique n'attrape pas ou ne spécifie pas - mais c'était la prémisse. Quoi qu'il en soit, il est vrai que sans effacement, vous pourriez au moins écrire T.class.newInstance(), ce qui gère ces problèmes.))

Cette vision, selon laquelle les types sont isomorphes aux propositions, suggère que l'utilisateur a une formation en théorie des types formels. (S) il n'aime très probablement pas les «types dynamiques» ou les «types d'exécution» et préférerait un Java sans downcasts et sans instanceofréflexion et ainsi de suite. (Pensez à un langage comme Standard ML, qui a un système de type très riche (statique) et dont la sémantique dynamique ne dépend d'aucune information de type.)

Il convient de garder à l'esprit, en passant, que l'utilisateur est à la traîne: s'il préfère probablement sincèrement les langues typées (statiquement), il n'essaie pas sincèrement de convaincre les autres de ce point de vue. Au contraire, le principal objectif du tweet original était de se moquer de ceux qui ne sont pas d'accord, et après que certains de ces désaccords ont sonné, l'utilisateur a publié des tweets de suivi tels que "la raison pour laquelle java a effacé le type est que Wadler et al savent quoi ils font, contrairement aux utilisateurs de java ". Malheureusement, cela rend difficile de savoir à quoi il pense réellement; mais heureusement, cela signifie probablement aussi que ce n'est pas très important de le faire. Les gens avec une profondeur réelle de leurs opinions ne recourent généralement pas à des trolls qui sont tout à fait sans contenu.


2
Il ne compile pas en Java car il a un effacement de type
Mark Rotteveel

1
@MarkRotteveel: Merci; J'ai ajouté un paragraphe pour expliquer (et commenter) cela.
ruakh

1
À en juger par le mélange de votes positifs et négatifs, c'est la réponse la plus controversée que j'ai jamais publiée. Je suis étrangement fier.
ruakh

6

Une bonne chose est qu'il n'était pas nécessaire de changer JVM lorsque les génériques ont été introduits. Java implémente les génériques au niveau du compilateur uniquement.


1
JVM change par rapport à quoi? Les modèles n'auraient pas non plus nécessité de modifications JVM.
Marquis of Lorne

5

La raison pour laquelle l'effacement de type est une bonne chose est que les choses qu'il rend impossible sont nuisibles. Empêcher l'inspection des arguments de type au moment de l'exécution facilite la compréhension et le raisonnement sur les programmes.

Une observation que j'ai trouvée quelque peu contre-intuitive est que lorsque les signatures de fonction sont plus génériques, elles deviennent plus faciles à comprendre. En effet, le nombre d'implémentations possibles est réduit. Considérez une méthode avec cette signature, dont nous savons en quelque sorte qu'elle n'a pas d'effets secondaires:

public List<Integer> XXX(final List<Integer> l);

Quelles sont les implémentations possibles de cette fonction? Tres beaucoup. Vous pouvez en dire très peu sur ce que fait cette fonction. Cela pourrait inverser la liste d'entrée. Il peut s'agir de jumeler des entiers, de les additionner et de renvoyer une liste de la moitié de la taille. Il existe de nombreuses autres possibilités qui pourraient être imaginées. Considérez maintenant:

public <T> List<T> XXX(final List<T> l);

Combien d'implémentations de cette fonction y a-t-il? Puisque l'implémentation ne peut pas connaître le type des éléments, un grand nombre d'implémentations peuvent désormais être exclues: les éléments ne peuvent pas être combinés, ajoutés à la liste ou filtrés, et al. Nous sommes limités à des choses comme: l'identité (pas de changement dans la liste), la suppression d'éléments ou l'inversion de la liste. Cette fonction est plus facile à raisonner en fonction de sa seule signature.

Sauf… en Java, vous pouvez toujours tromper le système de types. Étant donné que l'implémentation de cette méthode générique peut utiliser des éléments tels que des instanceofvérifications et / ou des transtypages en types arbitraires, notre raisonnement basé sur la signature de type peut facilement être rendu inutile. La fonction peut inspecter le type des éléments et faire un certain nombre de choses en fonction du résultat. Si ces hacks d'exécution sont autorisés, les signatures de méthode paramétrées nous deviennent beaucoup moins utiles.

Si Java n'avait pas d'effacement de type (c'est-à-dire que les arguments de type étaient réifiés au moment de l'exécution), cela permettrait simplement plus de manigances de ce type qui altèrent le raisonnement. Dans l'exemple ci-dessus, l'implémentation ne peut violer les attentes définies par la signature de type que si la liste contient au moins un élément; mais si elle Tétait réifiée, elle pourrait le faire même si la liste était vide. Les types réifiés ne feraient qu'accroître les (déjà très nombreuses) possibilités d'empêcher notre compréhension du code.

L'effacement de caractères rend la langue moins «puissante». Mais certaines formes de «pouvoir» sont en fait nuisibles.


1
Il semble que vous disiez soit que les génériques sont trop complexes pour que les développeurs Java «comprennent et raisonnent», ou qu'on ne peut pas leur faire confiance pour ne pas se tirer une balle dans le pied si on leur en donne la chance. Ce dernier semble être en phase avec l'éthos de Java (ala pas de types non signés, etc.) mais cela semble un peu condescendant aux développeurs
Basic

1
@Basic J'ai dû m'exprimer très mal, car ce n'est pas du tout ce que je voulais dire. Le problème n'est pas que les génériques sont complexes, c'est que des choses comme le casting et instanceofentravent notre capacité à raisonner sur ce que fait le code en fonction des types. Si Java devait réifier les arguments de type, cela ne ferait qu'empirer ce problème. L'effacement des types au moment de l'exécution a pour effet de rendre le système de types plus utile.
Lachlan

@lachlan, cela avait un sens parfait pour moi, et je ne pense pas qu'une lecture normale de celui-ci susciterait une insulte aux développeurs Java.
Rob Grant

3

Ce n'est pas une réponse directe (OP a demandé "quels sont les avantages", je réponds "quels sont les inconvénients")

Comparé au système de type C #, l'effacement de type Java est une vraie douleur pour deux raesons

Vous ne pouvez pas implémenter une interface deux fois

En C #, vous pouvez implémenter les deux IEnumerable<T1>et en IEnumerable<T2>toute sécurité, surtout si les deux types ne partagent pas un ancêtre commun (c'est-à-dire que leur ancêtre est Object ).

Exemple pratique: dans Spring Framework, vous ne pouvez pas implémenter ApplicationListener<? extends ApplicationEvent>plusieurs fois. Si vous avez besoin de comportements différents en fonction, Tvous devez testerinstanceof

Vous ne pouvez pas faire de nouveau T ()

(et vous avez besoin d'une référence à la classe pour faire cela)

Comme d'autres l'ont commenté, faire l'équivalent de new T()ne peut être fait que par réflexion, uniquement en invoquant une instance de Class<T>, en s'assurant des paramètres requis par le constructeur. C # vous permet de faire new T() uniquement si vous vous contraignez Tà un constructeur sans paramètre. Si Tne respecte pas cette contrainte, une erreur de compilation est générée.

En Java, vous serez souvent obligé d'écrire des méthodes qui ressemblent à ce qui suit

public <T> T create(....params, Class<T> classOfT)
{

    ... whatever you do
    ... you will end up
    T = classOfT.newInstance();


    ... or more advanced reflection
    Constructor<T> parameterizedConstructorThatYouKnowAbout = classOfT.getConstructor(...,...);
}

Les inconvénients du code ci-dessus sont:

  • Class.newInstance fonctionne uniquement avec un constructeur sans paramètre. Si aucun n'est disponible, ReflectiveOperationExceptionest lancé au moment de l'exécution
  • Le constructeur réfléchi ne met pas en évidence les problèmes au moment de la compilation comme ci-dessus. Si vous refactorisez, de vous permutez les arguments, vous ne saurez qu'au moment de l'exécution

Si j'étais l'auteur de C #, j'aurais introduit la possibilité de spécifier une ou plusieurs contraintes de constructeur qui sont faciles à vérifier au moment de la compilation (donc je peux exiger par exemple un constructeur avec des string,stringparamètres). Mais le dernier est la spéculation


2

Un point supplémentaire qu'aucune des autres réponses ne semble avoir pris en compte: si vous avez vraiment besoin de génériques avec un typage à l'exécution , vous pouvez l'implémenter vous-même comme ceci:

public class GenericClass<T>
{
     private Class<T> targetClass;
     public GenericClass(Class<T> targetClass)
     {
          this.targetClass = targetClass;
     }

Cette classe est alors capable de faire tout ce qui serait réalisable par défaut si Java n'utilisait pas d'effacement: elle peut allouer de nouveaux Ts (en supposant Tque son constructeur corresponde au modèle qu'elle espère utiliser), ou des tableaux de Ts, elle peut tester dynamiquement au moment de l'exécution si un objet particulier est un Tet changer de comportement en fonction de cela, et ainsi de suite.

Par exemple:

     public T newT () { 
         try {
             return targetClass.newInstance(); 
         } catch(/* I forget which exceptions can be thrown here */) { ... }
     }

     private T value;
     /** @throws ClassCastException if object is not a T */
     public void setValueFromObject (Object object) {
         value = targetClass.cast(object);
     }
}

Quelqu'un veut-il expliquer la raison du vote défavorable? C'est, pour autant que je puisse le voir, une parfaitement bonne explication de la raison pour laquelle le prétendu inconvénient du système d'effacement de type Java n'est pas du tout une limitation réelle. Donc quel est le problème?
Jules

Je vote. Non pas que je prenne en charge la vérification de type à l'exécution, mais cette astuce peut être pratique dans les mines de sel.
techtangents

3
Java est un langage des mines de sel, ce qui en fait une nécessité absolue étant donné le manque d'informations sur le type d'exécution. Espérons que l'effacement de type sera annulé dans une future version de Java, faisant des génériques une fonctionnalité beaucoup plus utile. Actuellement, le consensus est que leur rapport poussée / poids est inférieur à 1.
Marko Topolnik

Eh bien, bien sûr ... Tant que votre TargetType n'utilise pas de génériques lui-même. Mais cela semble assez limitatif.
base

Cela fonctionne également avec les types génériques. La seule contrainte est que si vous souhaitez créer une instance, vous avez besoin d'un constructeur avec les bons types d'arguments, mais tous les autres langages dont j'ai connaissance ont la même contrainte, donc je ne vois pas la limitation.
Jules

0

évite le gonflement du code de type C ++ car le même code est utilisé pour plusieurs types; cependant, l'effacement de type nécessite une distribution virtuelle alors que l'approche c ++ - code-bloat peut faire des génériques non distribués virtuellement


3
Cependant, la plupart de ces distributions virtuelles sont converties en distributions statiques au moment de l'exécution, ce n'est donc pas si grave que cela puisse paraître.
Piotr Kołaczkowski

1
très vrai, la disponibilité d'un compilateur au moment de l'exécution permet à java de faire beaucoup de choses intéressantes (et d'atteindre des performances élevées) par rapport au c ++.
nécromancien

wow, permettez-moi de l'écrire quelque part où java atteint des performances élevées par rapport à cpp ..
jungle_mole

1
@jungle_mole oui, merci. peu de gens réalisent l'avantage d'avoir un compilateur disponible au moment de l'exécution pour recompiler le code après le profilage. (ou que le garbage collection est big-oh (non-garbage) plutôt que la croyance naïve et incorrecte que le garbage collection est big-oh (garbage), (ou que bien que le garbage collection soit un léger effort, il compense en permettant un coût zéro allocation)). C'est un monde triste où les gens adoptent aveuglément ce que leur communauté croit et arrêtent de raisonner à partir des premiers principes.
nécromancien

0

La plupart des réponses concernent plus la philosophie de programmation que les détails techniques réels.

Et bien que cette question ait plus de 5 ans, la question persiste: pourquoi l'effacement de caractères est-il souhaitable d'un point de vue technique? Au final, la réponse est plutôt simple (à un niveau supérieur): https://en.wikipedia.org/wiki/Type_erasure

Les modèles C ++ n'existent pas au moment de l'exécution. Le compilateur émet une version entièrement optimisée pour chaque appel, ce qui signifie que l'exécution ne dépend pas des informations de type. Mais comment un JIT gère-t-il différentes versions de la même fonction? Ne serait-il pas préférable d'avoir une seule fonction? Je ne voudrais pas que le JIT doive optimiser toutes les différentes versions de celui-ci. Eh bien, mais qu'en est-il de la sécurité des types? Je suppose que ça doit sortir de la fenêtre.

Mais attendez une seconde: comment fonctionne .NET? Réflexion! De cette façon, ils n'ont qu'à optimiser une fonction et également obtenir des informations sur le type d'exécution. Et c'est pourquoi les génériques .NET étaient plus lents (bien qu'ils se soient beaucoup améliorés). Je ne dis pas que ce n'est pas pratique! Mais il est cher et ne doit pas être utilisé quand ce n'est pas absolument nécessaire (il n'est pas considéré comme coûteux dans les langages à typage dynamique car le compilateur / interpréteur repose de toute façon sur la réflexion).

De cette façon, la programmation générique avec effacement de type est proche de zéro surcoût (certaines vérifications / casts d'exécution sont toujours nécessaires): https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.