Java : une piste intéressante pour améliorer les types génériques
Un prototype de « generic specialization » en cours de développement
Le 2014-12-29 17:46:43, par Amine Horseman, Expert éminent sénior
Même si Java 9 n’est pas encore prêt, une équipe de développeurs est chargée de préparer les nouvelles fonctionnalités de la version 10. Annoncé en juillet dernier, le projet Valhalla, dirigé par Brian Goetz, a pour but d’étudier et tester ces nouvelles fonctionnalités dont la publication est prévue pour 2016.
Il y a quelques jours, Goetz publia un document où il présente l’état d’avancement concernant la gestion des types génériques, l’une des caractéristiques les plus critiquées du Java puisqu’il n’est pas possible, actuellement, d’appliquer des génériques aux types primitifs.
Étant donné qu’Oracle accorde une importance primordiale à la compatibilité avec les versions précédentes, le problème soulevé de par l’introduction d’un tel système de « génériques améliorés » doit être approché avec prudence. En effet, la difficulté est que le « système de types du langage Java n'a pas de racine unifiée », il n'y a pas de type en Java qui est à la fois un super-type d’« Object » et de « int ».
Comme l’explique Goetz, « l'un des premiers compromis avec les génériques en Java est que ces variables ne peuvent être instanciées qu’avec les types de référence, et non pas les types primitifs […] Nous voulons permettre aux classes génériques existantes d’être améliorées, sans avoir à les jeter et les remplacer par de nouvelles. Et en même temps ne pas forcer les programmes existants à être recompilés ou modifiés simplement pour continuer à travailler ».
Pour cela, plusieurs techniques potentielles sont en train d’être étudiées. L’une des pistes les plus prometteuses, appelée « generic specialization », consiste à continuer à représenter les types du genre List<Integer> et List<String> par List.class dans le runtime, tandis que les nouvelles déclarations du genre List<int> seront représentées par un autre type.
L’équipe du projet Valhalla est en train de préparer un prototype pour tester cette technique. Cependant, il est encore tôt pour savoir si elle permet de résoudre efficacement tous les problèmes actuels du typage générique de Java.
Source : Open JDK
Il y a quelques jours, Goetz publia un document où il présente l’état d’avancement concernant la gestion des types génériques, l’une des caractéristiques les plus critiquées du Java puisqu’il n’est pas possible, actuellement, d’appliquer des génériques aux types primitifs.
Étant donné qu’Oracle accorde une importance primordiale à la compatibilité avec les versions précédentes, le problème soulevé de par l’introduction d’un tel système de « génériques améliorés » doit être approché avec prudence. En effet, la difficulté est que le « système de types du langage Java n'a pas de racine unifiée », il n'y a pas de type en Java qui est à la fois un super-type d’« Object » et de « int ».
Comme l’explique Goetz, « l'un des premiers compromis avec les génériques en Java est que ces variables ne peuvent être instanciées qu’avec les types de référence, et non pas les types primitifs […] Nous voulons permettre aux classes génériques existantes d’être améliorées, sans avoir à les jeter et les remplacer par de nouvelles. Et en même temps ne pas forcer les programmes existants à être recompilés ou modifiés simplement pour continuer à travailler ».
Pour cela, plusieurs techniques potentielles sont en train d’être étudiées. L’une des pistes les plus prometteuses, appelée « generic specialization », consiste à continuer à représenter les types du genre List<Integer> et List<String> par List.class dans le runtime, tandis que les nouvelles déclarations du genre List<int> seront représentées par un autre type.
L’équipe du projet Valhalla est en train de préparer un prototype pour tester cette technique. Cependant, il est encore tôt pour savoir si elle permet de résoudre efficacement tous les problèmes actuels du typage générique de Java.
Source : Open JDK
-
adiGubaExpert éminent séniorC'est atténué par le fait que cela ne concernera que les primitifs/valeurs.
Les Generics avec des références fonctionneront toujours de la même manière.
Dans l'idée removeItem(E) et removeIndex(int) seront disponible pour tous les types, et les remove(Object)/remove(int) accessible uniquement pour les références pour la rétrocompatibilité.
Depuis Java 8 la javadoc inclut des onglets pour regrouper les méthodes selon plusieurs critères (instance, static, abstraite, concrète, default...).
On aura peut-être de nouveaux onglets pour gérer cela...
Mieux : c'est absent de ce document, mais dans la version précédente ils parlaient carrément de pouvoir faire une implémentation spécifique pour certains types.
Il serait ainsi possible par exemple d'avoir une implémentation d'ArrayList<boolean> basé sur un BitSet au lieu d'un boolean[]
C'est un prérequis à l'intégration des types valeurs.
Il serait absurde d’insérer des types valeurs dans le langage si on ne peut pas les utiliser avec les Generics.
Troll ? Ou alors tu es resté bloqué sur la fin des années 90 ?
Un peu de sérieux voyons...
Ben c'est le même projet : tout ceci est lié.
Sans specialization l'utilisation des types valeurs risque d'être problématique...
Cela existe déjà (mais pas dans le JDK en effet).
Par contre je n'y vois pas grand intérêt...
Je ne comprend pas trop.
Même si c'est chapeauté par Oracle l'évolution de Java est entre les mains de plusieurs entreprises/organisations...
Soit tu argumentes, soit c'est du troll pure et le mieux serait d'aller voir ailleurs.
Ici on préfère les discussions argumentés aux petites phrases toutes faites qui ne veulent rien dire...
Les types primitifs sont des types de base sans notion d'identité. Ils ne représente qu'un espace mémoire réservé à la valeur qu'ils représente.
Les types valeurs sont des types primitifs un peu plus évolué, dans le sens où ils peuvent être composé de plusieurs éléments (un peu comme une structure en C).
Leurs utilités en Java est restreinte, car en les utilisant on "perd" plein de fonctionnalité (pas d'héritage).
Toutefois cela a deux gros intérêts :
- Une occupation mémoire fixe, qui permet des allocations en bloc pour les tableaux.
Un tableau de 10 int occupera toujours la même taille en mémoire quelque soit les valeurs stockées, tandis qu'un tableau de 10 Object occupera plus ou moins de mémoire selon les objets qu'on y met. - Elle ne comporte que des données, et aucune informations d'identité propre à l'héritage et la POO.
Cela permet d'être "partagé" plus facilement avec du code natif, ou des instructions bas-niveau, sans avoir à faire des conversions dans tous les sens.
C'est important lorsqu'on fait des traitements 3D ou d'autres choses qui peuvent être manipulé directement par le GPU : on génère les données en Java et on les passe tel-quel pour un traitements optimisés (voir même parallélisés).
Ils ne vont pas tout changer : les Generics fonctionneront quasiment de la même manière pour les objets.
Ils vont juste introduire une système de template (un peu à la C#), mais uniquement pour les primitives/valeurs.
Sinon perso je ne trouve pas que l'implémentation des Generics soit aussi mauvaise que tout le monde le laisse entendre.
Au contraire je trouve que ses inconvénients sont largement plus décriés qu'ils ne le devraient, et qu'ils y a d'autres avantages qui sont mis sous silence :
- La compatibilité avec le code déjà compilé comme tu le dis,
- Mais également l’inter-compatibilité entre du code n'utilisant pas les Generics, et du code les utilisant, au sein d'un même programme sans avoir à faire des conversions.
- Une covariance/contravariance plus complète, et pas limité à la déclaration du type, et cela dès le début.
- Une compatibilité avec tous les langages tournant sur une JVM, même si ceux-ci n'ont pas forcément de notion de Generics.
a++le 31/12/2014 à 12:29 - Une occupation mémoire fixe, qui permet des allocations en bloc pour les tableaux.
-
adiGubaExpert éminent séniorLes types primitifs resteront tel quel (et donc n'accepterons pas null).
Quand aux cas comme Map, la specialization des Generics permettra de définir du code et des méthodes spécifiques pour des types.
Ainsi get(K) sera réservé aux objets, et on sera obligé de passer par getOrDefault(K,V) pour les types primitifs/values...
Le premier point est déjà fait par la JVM, qui peut optimiser pas mal de chose vira l'inlining et l'escape analysis...
Après changer la manière de stocker les primitifs n'apportera pas grand chose : ce ne sera pas aussi performants que de vrai primitif, et cela restera incompatible avec le code natif.
En Java il n'y a pas vraiment de gros soucis à passer par les types Wrapper.
Le problème c'est qu'on ne peut pas les passer tel quel vers le natif (logiciel ou matériel).
Et justement l'objectif des types valeurs est de définir des types values est de permettre de définir des structures de données identiques à ceux qu'on retrouve en natif, pour faciliter le partage des données.
Oui une "declaration-site variance" est plus simple et lisible, même si elle a ses propres défauts (déclaration sur le type uniquement, découpage en plusieurs sous-interfaces, moins de cohérence de l'ensemble...).
En même temps il faut dire que les wildcards de Java sont vite très difficile à lire !
Au passage voir l'URL de la JEP que j'ai évoqué plus haut dans cette discussion (si je ne me trompe pas), et qui consiste à apporter la "declaration-site variance" dans Java : http://openjdk.java.net/jeps/8043488
Les types values devraient permettre de créer des types non-signé (entre autres).
a++le 13/01/2015 à 17:39 -
adiGubaExpert éminent séniorLa syntaxe n'est pas du tout définitive. Pour le moment elle n'est donné que pour pouvoir "tester", mais il y a de forte chance que cela change.
Dans la dernière proposition il faut utiliser des layer pour définir des bloc de codes disponible pour l'un ou l'autre :
Code : 1
2
3
4
5
6
7
8
9
10interface Exemple<any T> { public T methodeCommune(); layer<ref T> { public T methodPourLesObjects(); } }
Il devrait même être possible de définir cela pour un type précis (layer<T=int>) voir même selon selon des critères d'héritage (layer<T extends Comparable<T>>).
Oui : on conservera une classe commune pour les références (comme actuellement), mais il y aura une classe pour chaque configuration utilisant une primitif/valeur.
Toutefois comme en C# cela devrait être fait dynamiquement à l'exécution (ie : lorsque tu compiles ta classe tu as un fichier .class, et la JVM génèrera automatiquement les différentes classes à l'utilisation).
Et on pourra bien sûr avoir plusieurs types génériques "anyifié" (ex: Map<any K, any V>)
Oui mais grosso-modo ta solution consiste à donner une identité aux primitives, en y ajoutant un header le décrivant.
Or si on veut partager les données avec du code natif, on ne peut pas utiliser cela car on doit utiliser exactement la même structure mémoire...
La JVM fait déjà pas mal de chose comme cela.
Le problème c'est pas vraiment les performances de la JVM en elle même, mais plus de la manière dont on communique avec "l'extérieur".
Passes une zone mémoire avec un tel header vers du code natif, et il ne pourra pas le lire "nativement".
Or l'objectif des types values c'est surtout ca : pourvoir représenter des données en mémoire de la même manière qu'en natif, afin de pouvoir communiquer plus facilement avec du natif (que ce soit logiciel ou hardware).
Oui, c'est bien ce que je dis : les wildcards sont plus complexes, mais permettent aussi plus de chose.
Non justement les types values devraient permettre cela. On pourra définir une infinité de type...
Après tout en mémoire il n'y a aucune différence entre un byte et un unsigned byte : il s'agit juste d'un octet.
La seule différence vient de l'implémentation des calculs et des opérations que l'on effectue dessus (d'ailleurs Java 8 a rajouté des méthodes dans les classe Integer/Long afin de traiter les int/long en tant qu'unsigned.)
Il serait donc possible de définir un type value contenant un byte, que l'on manipulerait comme un unsigned.
a++le 14/01/2015 à 14:49 -
thelvinModérateurHmmm. Bon ça a l'air de marcher. Ça va encore démultiplier les classes à charger en mémoire mais ça on n'y aurait pas coupé à un moment ou à un autre.
Ça va tout de même commencer à devenir compliqué, la lecture des JavaDoc des classes standard Java : "alors les méthodes remove(Object) et remove(int) elles existent que sur les instances où le E de List<E> est un type référence. Pour les types primitifs ça existe pas, utilisez removeItem(E) et removeIndex(int)"
Si ce truc passe je tire mon chapeau à ceux qui arrivent encore à décrocher des certifs Java.le 30/12/2014 à 1:52 -
ternelExpert éminent séniorJustement, pouvoir avoir des list<int> qui ne passent pas par une conversion vers Integer pourra fortement augmenter les performances de tout programme de calcul.
Quant à "je pense que c'est le meilleur langage parce qu'on peut tout faire", c'est aussi vrai pour tout un tas d'autres langages, comme le C++, le C#, le python, etc.
Le bon outil est celui adapté à la tache.
Personne ne dira "le marteau est le meilleur outil", pourtant, on peut tout faire avec... planter des vis (c'est pas idéal, mais ca marche), cirer des chaussures, démarrer un tracteur, et même coudre un bouton.le 31/12/2014 à 9:48 -
redcurveMembre extrêmement actifC# n'est pas la propriété de MS c'est une spec, pareil pour la CLR et la CLI.
Le .net Framework n'est qu'une implémentation de la CLR/CLI sous Windows.
Toutes les specs sont dispo librement sur le site de l'ECMAle 31/12/2014 à 9:49 -
gstrategeMembre actifÇa sert à quoi d'avoir des types primitifs ?le 31/12/2014 à 10:01
-
tomlevRédacteur/ModérateurC'est plus tout à fait vrai :
- d'une part, le langage est standardisé par l'ECMA (du moins jusqu'à la version 2 ; je ne sais pas s'ils ont l'intention de faire standardiser les versions suivantes)
- d'autre part, le compilateur est maintenant open-source et cross-platform, et la communauté est fortement impliquée dans sa conception
Certes, c'est toujours Microsoft qui pilote l'évolution du langage, mais je ne vois pas en quoi c'est pire que Java qui est maintenant piloté par Oracle...
Pour des questions de performance, principalement. Les types primitifs correspondent directement à des types de données supportés par le processeur (int, double, etc), qui peuvent donc être traités très efficacement.
En tous cas c'est une très bonne chose qu'ils se penchent enfin sur l'amélioration des génériques; c'est vraiment la feature la plus bancale de Java... Le design actuel partait d'un bon sentiment (garder la compatibilité avec le code déjà compilé existant), mais ça introduit tellement de limitations qu'à mon avis les inconvénients surpassent largement les bénéfices.le 31/12/2014 à 10:35 -
oallouchFutur Membre du ClubBrian Goetz et son équipe avancent vraiment bien côté langage.
Les lambdas de Java 8 sont une merveille (regardez le source de la classe utilitaire Collectors).
Ils ont pris le temps mais c'est du bon !
Avec ces maj dans Java 9, ils vont résoudre le truc le plus chiant avec les lambdas pour les Collections:
ils vont pouvoir ajouter une méthode stream() à l'interface Iterator...le bonheur !
Maintenant, il faudrait vraiment qu'ils se penchent (je crois qu'ils le font) sur la pauvreté du Hot Swap standard et la nécessité pour tous d'avoir en standard (et gratuit) un truc équivalent à JRebel. Il existe déjà des produits open source (moyennement supportés) comme DCEVM.
Mais, je peux vous dire que quand vous passez de Javascript (quand vous codez votre UI) à Java (pour le serveur), vous respirez !
Olivier Allouch
http://www.illicotravel.comle 31/12/2014 à 15:23 -
tomlevRédacteur/ModérateurEn C# c'est pas vraiment un système de template (dans le sens des templates C++). Le compilateur produit un seul type générique ouvert, et c'est au runtime que les types fermés sont créés (éventuellement dynamiquement par réflexion). Alors qu'en C++, toutes les instanciations d'un template sont générées directement à la compilation ; ça a certains avantages (performance, plus de souplesse puisqu'on peut faire n'importe quelle opération dans un template du moment que le type utilisé lors de l’instanciation le supporte), mais ça a l'inconvénient d'être complètement statique...
Oui mais la variance des génériques en Java n'est pas type-safe... Petit exemple de quelque chose qui va compiler sans problème mais échouer à l'exécution :
Code Java : 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import java.util.List; import java.util.ArrayList; public class GenericTypeErasure { public static void main(String[] args) { List<Foo> fooList = new ArrayList<Foo>(); List<?> list = fooList; List<Bar> barList = (List<Bar>)list; barList.add(new Bar()); // no error Foo foo = fooList.get(0); // java.lang.ClassCastException: HelloWorld$Bar cannot be cast to HelloWorld$Foo } static class Foo { } static class Bar { } }
En C#, ce serait impossible. Les classes génériques ne sont pas variantes ; seules les interfaces (et les delegates) peuvent l'être, mais il y a des contraintes. Par exemple, l'interface IList<T> n'est pas covariante, parce que T apparait à la fois en sortie et en entrée (un IList<String> ne peut donc pas être affecté à un IList<Object>). Par contre, IEnumerable<T> est covariante, car T n'apparait qu'en sortie (un IEnumerable<String> peut être affecté à un IEnumerable<Object>)
Après, je reconnais volontiers que l'approche de Java a certains avantages... Il m'arrive occasionnellement de regretter que les wildcards n'existent pas en C#, même s'ils présentent certains risques (comme l'exemple ci-dessus)le 31/12/2014 à 18:31