mars
2011
Il y a quelques mois de cela naissait le projet Coin, qui avait pour objectif d’étudier les différentes propositions d’évolution du langage pour leurs éventuelles intégration dans la prochaine version de Java.
Le projet a eu un certains succès en recevant plus de 70 propositions, et c’est désormais l’heure du bilan : on connais enfin la liste des changements acceptés pour l’inclusion dans le JDK7 : Project Coin: The Final Five (Or So).
On retrouve donc 7 propositions (dont deux regroupent plusieurs propositions) proposant des modifications plus ou moins complexe sur 7 thèmes :
- La syntaxe en losange (Diamond syntax)
- Simplified Varargs Method Invocation
- Amélioration des valeurs numériques
- Support syntaxique des Collections
- Switch sur des chaines de caractères
- Gestion automatique des ressources (ARM)
- Support de la JSR 292 dans le langage
Voyons ceci d’un peu plus près…
L’héritage de Java 5.0
Commençons donc par ce qui pourrait considérer comme des correctifs suite à l’intégration des Generics dans Java 5.0. Les changements suivant sont en effet destinés à combler quelques lourdeurs hérités de l’intégration des Generics dans le langage :
La syntaxe en losange (Diamond syntax)
J’en ai déjà parlé il y a quelques jours : il s’agit tout simplement d’une syntaxe permettant d’éviter la duplication de la déclaration des types paramétrés, qui dans tous les cas doivent impérativement être identique.
La syntaxe en losange consiste tout simplement à laisser le paramétrage vide (le compilateur se charge alors de le déduire du contexte).
Ainsi, ceci :
Map<Integer, List<String>> map = new HashMap<Integer, List<String>>();
Pourra être remplacé par cela :
Map<Integer, List<String>> map = new HashMap<>();
Simplified Varargs Method Invocation
Soyons direct : cette modification risque de passer inaperçu pour 99% des développeurs !
En effet on retrouve ici un problème assez obscur qui interdit l’utilisation de tableau de type paramétré : pour diverses raisons il est impossible de créer des tableaux de type Generics, notamment car la vérification du typage n’est pas effectué au même moment (à la compilation pour les Generics, et à l’exécution pour les tableaux), ce qui peut entraîner des états totalement incohérent et difficile à résoudre…
Toutefois, l’ellipse (également introduite dans Java 5) utilise des tableaux en interne. De ce fait lorsqu’on utilise les Generics avec l’ellipse, on peut se retrouver implicitement à créer des tableaux de types paramétrés :
Comparator<String> c1 = ...
Comparator<String> c2 = ...
Comparator<String> c3 = ...
List<Comparator<String>> list = Arrays.asList(c1, c2, c3); // warning
Le compilateur produit alors un warning peu explicite pour prévenir du problème potentiel :
Main.java:14: warning: [unchecked] unchecked generic array creation
of type java.util.Comparator<java.lang.String>[] for varargs parameter
Mais le gros problème c’est qu’on ne peut rien faire contre ce warning (si ce n’est utiliser l’annotation @SuppressWarning
), et que le problème potentiel tant décrié dépend uniquement dans le cas où l’implémentation de la méthode modifierait son tableau contenant les varargs… ce qui est fortement déconseillé !
Le warning a donc été déplacé sur la définition de la méthode et non pas lors de chacune de ses utilisations, afin d’éviter de multiplier les warnings inutilement.
Le sucre syntaxique
Passons désormais à ce que l’on pourrait appeler du sucre syntaxique, c’est à dire de nouvelles syntaxes qui se contentent de générer un code similaire mais plus verbeux. Mais cela n’en demeure pas pour autant inutile !
Note : il s’agit ici de regroupement de plusieurs propositions, et de ce fait les spécifications exactes ne sont pas pas encore détaillés. Je présente ici les fonctionnalitésqui devrait normalement être prise en compte.
Amélioration des valeurs numériques
En plus de la forme décimales (par défaut), octal (avec le préfixe ‘0
‘) ou hexadécimale (préfixe ‘0x
‘), il sera possible d’utiliser directement une forme binaire avec le préfixe ‘0b
‘ :
int value = 0b10000000; // 128
L’intérêt de cela étant bien sûr de simplifier les opérations bit à bit sans avoir à convertir les valeurs numériques dans une autre base…
Il sera également possible d’utiliser le caractère underscore (‘_
‘) de manière totalement arbitraire entre deux chiffres de n’importe quelle valeurs numériques :
double amount = 1_000_000_000.00; // 1 milliard
int color = 0xff_ff_ff; // white
int binary = 0b11_1110_1000; // 1000 en binaire
Ceci n’aura bien sûr aucune incidence sur la valeur généré à la compilation ! Le seul et unique but de cela est simplifier la lecture du nombre par le développeur.
Support syntaxique des Collections
[edit] Attention : cette proposition n’a finalement pas été retenue…
L’intégration dans le langage se verra également améliorer pour les Collections, avec notamment un accès indexé pour les List
s et les Map
s (respectivement avec un entier et un type compatible). Par exemple on pourra utiliser quelque chose comme cela :
List<String> list = ...
Map<String, Integer> map = ...
String firstValue = list[0];
list[0] = "new-value";
Integer i = map[firstValue];
map[list[0]] = map["x"];
List<String> list = ...
Map<String, Integer> map = ...
String firstValue = list.get(0);
list.set(0, "new-value");
map.get(firstValue);
map.put( list.get(0), map.get("x"));
A noter également la possibilité d’écrire des collections très simplement, à la manière des tableaux :
// Les Lists sont déclarés via des crochets :
List<Integer> integers = [ 1, 2, 3, 4, 5, 6 ];
// Les Sets sont déclarés via des accolades :
Set<Double> doubles = { 1.25, 2.50, 3.75, 5.00, 6.25, 7.50 };
// Les Map sont déclarés via des accolades, avec deux-points séparant la clef de la valeur :
Map<String, String> strings = {
"French" : "Français",
"English" : "Anglais",
"Spanish" : "Espagnol"
};
Les collections ainsi créées sont immuables, mais étant donné que toutes les collections peuvent s’initialiser à partir des données d’une autre collection, on pourra écrire quelque chose comme cela pour obtenir une instance modifiable à souhait :
List<Integer> integers = new LinkedList<Integer>([ 1, 2, 3, 4, 5, 6 ]);
Set<Double> doubles = new HashSet<Double>({ 1.25, 2.50, 3.75, 5.00, 6.25, 7.50 });
Map<String, String> strings = new HashMap<String, String>({
"French" : "Français",
"English" : "Anglais",
"Spanish" : "Espagnol"
});
Les nouvelles structures de contrôle
On dénombre également deux (plus ou moins) nouvelles structures de contrôle, toujours dans un objectif de simplification du code.
Switch sur des chaines de caractères
Tout aussi bête que son nom l’indique, cette structure permettra donc d’utiliser des chaînes de caractères dans un switch :
String value = ...
switch (value) {
case "azerty":
doSomething();
break;
case "qwerty":
doAnotherThing();
break;
case "":
// do nothing
break;
default:
doDefaultAction();
break;
}
Bref cela permet d’éviter les fastidieuses successions de if/else…
Gestion automatique des ressources (ARM)
Nous voici dans un domaine déjà bien plus intérressant : la gestion des ressources. Ceux qui ont l’habitude de suivre mes interventions sur les forums savent sûrement qu’on touche là à un sujet qui m’est important, et qui figure d’ailleurs dans la FAQ Java : Comment libérer proprement les ressources ?
Le problème vient donc des ressources « externes »(fichiers, sockets, resultset/statement/connexion JDBC, etc.), non-gérables par le garbage-collector, et qui doivent donc être libérer manuellement et explicitement via un appel à la méthode close()
. Et pour faire cela efficacement on est obligé de passer par un bloc try/finally par ressources, ce qui alourdit considérablement le code.
L’objectif d’ARM est de simplifier tout cela en gérant automatiquement et proprement la fermeture du flux via une nouvelle syntaxe :
try ( /* declarations des ressources */ ) {
/* traitements sur les ressources */
}
La déclarations des ressources s’effectue à la suite du try
, et la fermeture est implicite dès la fin du bloc correspondant.
Pour reprendre l’exemple de la FAQ :
public static void writeToFile(URL url, File file) throws IOException {
// 1 - Création de la ressource (Fichier)
FileOutputStream fos = new FileOutputStream(file);
try {
// 2 - Utilisation de la ressource (Fichier)
// 1 - Création de la ressource (URL)
InputStream is = url.openStream();
try {
// 2 - Utilisation de la ressource (URL)
byte[] buf = new byte[2048];
int len;
while ((len = is.read(buf)) > 0) {
fos.write(buf, 0, len);
}
} finally {
// 3 - Libération de la ressource (URL)
is.close();
}
} finally {
// 3 - Libération de la ressource (Fichier)
fos.close();
}
}
On pourra alors utiliser directement le code suivant, et donc se concentrer sur le traitements :
public static void writeToFile(URL url, File file) throws IOException {
// 1 - Création des ressource (Fichier & URL) :
try ( FileOutputStream fos = new FileOutputStream(file);
InputStream is = url.openStream() ) {
// 2 Utilisation des ressources (Fichier & URL) :
byte[] buf = new byte[2048];
int len;
while ((len = is.read(buf)) > 0) {
fos.write(buf, 0, len);
}
}
}
On obtient en résultat un joli gain de visibilité tout en gérant proprement ses ressources !
A noter que cela devrait également gérer les problèmes d’occultation d’exceptions qui peuvent survenir en cas d’erreurs multiples lors du traitement (une exception remontée par un bloc try peut être cachée par une autre dans un bloc finally), ce qui n’est quasiment jamais fait manuellement car le code en deviendrait alors carrément indescriptible !
Un petit coté dynamique…
Commençons par un petit rappel : les programmes Java sont compilé en bytecode Java, mais ce dernier est plus étendus que le langage du même nom. De même il est possible d’utiliser d’autres langages de programmation pour générer du bytecode, et même si dans les fait cela reste peu usité (contrairement à l’environnement .NET), cela prend de plus en plus d’essort en particulier avec les langages dynamiques (Python, Ruby, Perl, Javascript, Groovy, etc.).
Malheureusement, le bytecode comporte une grosse limitation pour ces langages : il ne gère pas l’invocation dynamique de méthode. Je m’explique : les différents modes d’invocations (invokestatic, invokespecial, invokeinterface ou invokevirtual) permettent uniquement d’appeler une méthode dont la signature est connu. Et même si la véritable méthode a exécutée peut varier selon le type réel de la classe, on se base toujours sur une définition de méthode existante dans le type déclaré lors de l’appel. On parle de langage fortement typé qui assure un certain nombre de vérification dès la compilation.
A l’inverse, les langages dynamiques utilisent généralement des types dynamiques qui peuvent très bien ne pas être typé du tout à la compilation. On en revient donc a appeler une méthode totalement inconnu pour le compilateur, ce qui est impossible du point de vue du bytecode Java. Les implémentations de langages dynamiques se retrouvent donc à devoir jouer avec de la réflection pour simuler ce comportement.
La JSR 292 est censée résoudre ce problème en proposant un nouveau mode d’invocation de méthode : invokedynamic, qui se chargerait de vérifier l’existence de la méthode seulement lors de son exécution.
Support de la JSR 292 dans le langage
[edit] Attention : le contenu de cette JSR a été fortement remanié. Le contenu de cette section est donc obsolète.
A l’origine la JSR 292 et invokedynamic ne devait donc impacter que le bytecode, mais pas le langage Java. Cette proposition consiste donc à intégrer cela au sein du langage, afin de faciliter un peu plus l’implémentation et l’intéraction des langages dynamiques, le tout via un nouveau package : java.dyn
.
Note : je ne suis pas sûr de bien voir la séparation entre la JSR 292 et la proposition du projet Coin tant les deux semblent avoir évolué en même temps et sont intimement liés…
MethodHandle
Tout d’abord, la classe java.dyn.MethodHandle
permet de manipuler une référence vers le point d’entrée d’une méthode. C’est une sorte de pointeur vers une méthode qui permettra d’éviter toute la lourdeur de la réflection.
Cette classe comporte une méthode type() permettant d’obtenir des informations sur la méthode référencé (type de retour et nombre de paramètres), mais surtout une méthode invoke() un peu spéciale dans le sens où l’on peut l’utiliser avec n’importe quels paramètres, comme si la classe possédait un nombre infini de méthode invoke()…
Petit exemple :
JLabel label = new JLabel("Demo");
// On recherche la méthode d'instance 'setText()' de la classe JLabel :
MethodHandle handle = MethodHandles.lookup().findVirtual(JLabel.class, "setText",
MethodType.make(void.class, String.class)); // type de retour + paramètres
// Les handles sur une méthode d'instance se voit ajouter un paramètre initial,
// qui représente en fait la référence sur laquelle on appliquera la méthode :
handle.invoke(label, "Hello"); // equivalent de label.setText("Hello");
La classe MethodHandles propose un ensemble de méthode utilitaire permettant de créer et manipuler les MethodHandle.
Cela peut sembler proche de l’API de réflection, mais cela apporte un certain nombre de différence :
- Les vérifications d’accès sont uniquement vérifiées lors de la création de l’objet MethodHandle, et non à chaque appel de la méthode invoke().
- La JVM peut optimiser l’appel de la méthode invoke() en appelant directement la méthode cible comme si elle avait été appelée de manière normale.
Tout ceci implique des performances proche voir identique à un appel de méthode standard, contrairement à la réflection qui peut s’avérer assez « lourde »…
Le second avantage vient du fait que l’on peut manipuler la signature pointé par la MethodHandle pour jouer avec ses paramètres, par exemple en fixant la valeur d’un paramètre :
// On force la valeur du premier paramètre :
handle = handle.insertArgument(handle, 0, label);
// Désormais notre handle est lié à une instance précise :
handle.invoke("Hello"); // equivalent de label.setText("Hello");
A noter que l’on pourrait directement faire ceci :
JLabel label = new JLabel("Demo");
MethodHandle handle = MethodHandles.lookup().bind(label, "setText",
MethodType.make(void.class, String.class)); // type de retour + paramètres
handle.invoke("Hello"); // equivalent de label.setText("Hello");
Ce mécanisme se retrouvera à la base de l’invocation dynamique.
InvokeDynamic
Le package java.dyn comporte une seconde classe tout aussi particulière : InvokeDynamic
. Cette classe (final) ne comporte aucune méthode et peut donc sembler complètement inutile de prime abord. Il faut en réalité l’appréhender comme un marqueur syntaxique permettant d’effectuer des appels de méthodes dynamique. En fait on peut considérer qu’elle possèdent un nombre infini de méthodes statiques :
InvokeDynamic.uneMethode();
InvokeDynamic.uneAutreMethode(1);
int x = InvokeDynamic.<int>retourneUnInt("string", 2.0);
Object o = InvokeDynamic.<Date>retourneUneDate();
// etc. (tout ce que l'on veut en fait)
Tous ces appels indiqueront au compilateur qu’il doit effectuer une invocation dynamique via invokedynamic, c’est à dire que ces appels ne seront pas vérifiés par le compilateur, mais que le langage devra mettre en place un mécanisme de recherche de la méthode a exécuter à l’exécution.
Mais comment ça marche ?
En fait il faut définir une méthode « bootstrap » qui sera chargé d’associer un MethodHandle à la signature de méthode d’une invocation dynamique. Lors de la première exécution d’un appel dynamique, la JVM passera toutes les informations utiles sur l’appel de méthode dynamique à la méthode « bootstrap ». Cette dernière devra alors déterminer en effet retourner une instance de la classe callSite qui fournira toutes ces informations, dont un MethodHandle qui sera alors exécuté par la JVM.
Par contre, l’appel au « bootstrap » ne sera plus fait pour les appels suivant sur la même définition (même nom, type de retour et paramètres). La JVM réutilisera alors les informations obtenus lors de l’appel précédent…
Petit exemple :
public class Main {
// On enregistre la méthode de bootstrap pour cette classe :
static { Linkage.registerBootstrapMethod("bootstrap"); }
private static void show(String methodName) {
System.out.println("Hello " + methodName);
}
private static CallSite bootstrap(Class caller, String name, MethodType type) {
// On crée l'instance CallSite
// (ici on force une méthode sans paramètre et un retour de type void) :
CallSite site = new CallSite(caller, name, MethodType.make(void.class));
// Création d'un MethodHandle (en fonction des infos recus) :
MethodHandle handle = MethodHandles.lookup().findStatic(Main.class, "show",
MethodType.make(void.class, String.class));
// On force la valeur de l'argument (en forcant le nom de la méthode) :
handle = MethodHandles.insertArgument(handle, 0, name);
// On défini la cible à exécuter :
site.setTarget(handle);
return site;
}
public static void main(String...args) {
// Premier appel de 'method()' : exécute le bootstrap
InvokeDynamic.method(); // affiche : Hello method
InvokeDynamic.method(); // affiche : Hello method
// Premier appel de 'anotherMethod()' : exécute le bootstrap
InvokeDynamic.anotherMethod(); // affiche : Hello anotherMethod
InvokeDynamic.method(); // affiche : Hello method
InvokeDynamic.anotherMethod(); // affiche : Hello anotherMethod
}
}
Lors du premier appel dynamique sur method(), la méthode bootstrap() va être exécuté pour générer les informations utiles à la JVM, qui appelera alors la méthode show() avec un paramètre « « method ». Tous les autres appels sur la même méthode seront directement effectué sans réévalué la méthode bootstrap().
Bien sûr ici l’exemple est basique (et ne fonctionen qu’avec des méthodes sans paramètres), mais l’intérêt étant de déterminer dynamiquement une méthode à appeler par divers moyens…
Note : ceci peut déjà être testé de manière expérimental avec les derniers builds du JDK7. Il faut toutefois utiliser les options -XX:+EnableMethodHandles et -XX:+EnableInvokeDynamic respectivement pour activer le support des MethodHandles et de l’invocation dynamique au sein de la JVM. Ceci ne devrait plus être le cas lors de la version finale…
cast vers InvokeDynamic
La classe InvokeDynamic cache un autre secret : il s’agira d’un type un peu particulier dont les références pourront accepter tout type d’objet, a l’instar de la classe Object. En clair il sera possible d’affecter n’importe quel objet à une variable de type InvokeDynamic :
InvokeDynamic dynString = "I'm dynamic !";
InvokeDynamic dynDate = new Date();
InvokeDynamic dynLabel = new JLabel("I'm dynamic !");
L’intérêt vient du fait qu’on peut alors appeler une méthode de manière dynamique en la manipulant comme une référence :
dynString.uneMethode();
// est équivalent de :
InvokeDynamic.uneMethode(dynString);
Les mêmes règles d’exécutions seront ensuite respecté (appel du bootstrap pour déterminer la méthode à exécuter, etc.).
Note : A cette date, ceci ne semble pas être implémenter dans les derniers builds du JDK7.
Identifiant « exotique »
Comme je l’ai déjà dit, le bytecode Java est moins restrictif que le langage du même nom. En particulier il est possible d’utiliser une très large gamme de caractères dans tous les identifiants (nom de classes, de méthodes, d’attributs ou de variables). Ainsi un langage qui se compilerait en bytecode pourrait très bien utiliser des caractères « exotiques » pour le langage Java, ce qui rendrait alors ces éléments innacessible depuis un programme Java…
Pour pallier à cela on devrait avoir la possibilité de définir des identifiants « éxotiques », en utilisant le même format que pour les chaines de caractères, mais précédé d’un dièse (#).
int uneVariableStandard = 5;
int #"une Variable Exotique" = 10; // Le nom de la variable contient des espaces !
System.out.println( uneVariableStandard * #"une Variable Exotique"); // affiche 50
Cela peut également permettre d’utiliser des noms qui sont normalement réservés en Java :
int #"int" = 0; // La variable se nomme 'int' !
Bien sûr il est fortement déconseillé d’utiliser cela dans un programme Java pure !
L’intérêt étant de permettre d’utiliser des éléments compilés en bytecode depuis un autre langage.
Par exemple Ruby utilise des caractères spéciaux dans le nom de ses méthodes (comme les méthodes de JRuby qui peuvent contenir des ?
, =
ou !
), ce qui les rends inutilisable en Java. Les identifiants exotiques permettront de résoudre ce problème et autoriseront une interaction totale entre du code Java et d’autres langages, par exemple :
AnJRubyObject object = ...
object.#"method?"(); // Appel de la méthode 'method?'
Révolution ou évolution ?
C’est sûr : on est loin d’une révolution, et même loin des modifications apportées par Java 5.0. Malgré l’apport de l’invocation dynamique cela reste malgré tout assez conservateur.
Personnellement, je suis très heureux de voir enfin arriver une gestion automatique des ressources (surtout en l’absence de closures qui aurait pu l’implémenter cela), mais je suis par contre étonné de l’absence des améliorations de la gestion des exceptions (rethrows et multi-catch) ! Cela me semblait pourtant acquis de longue date…
Pour le reste je ne suis pas d’avis particulièrement tranché
Et vous qu’en pensez-vous ? Participez au débat sur le forum !
1 Commentaire + Ajouter un commentaire
Tutoriels
Discussions
- Possibilité d'accéder au type générique en runtime
- Définition exacte de @Override
- Recuperation du nom des parametres
- [REFLEXION] Connaitre toutes les classes qui implémentent une interface
- [ fuite ] memoire
- jre 1.5, tomcat 6.0 et multi processeurs
- Classes, méthodes private
- L'apparition du mot-clé const est-il prévu dans une version à venir du JDK?
- Difference de performances Unix/Windows d'un programme?
Merci pour ce récap parfait
Personnellement, j’adore la déclaration en losange, la déclaration des collections et le nouveau switch, mais par contre, je suis pas trop pour la nouvelle façon de gérer les ressources…
Par ailleurs, je trouve super la partie « dynamique », c’est vraiment un grand pas en avant