juin
2008
La dernière version d’eclise, surnommé Ganymede, est sorti il y a à peine quelques jours, et apporte son lot de nouveautés et d’amélioration.
A première vue cette release semble être une très bonne cuvée, mais en consultant la liste des nouveautés des outils de développement Java, je suis tombé sur une nouvelle fonctionnalité qui m’a quelque peu irrité : un « quick assist » sur la concaténation de chaine de caractère…
Pour rappel, les « quick assist » permettent de modifier automatiquement certaines parties du code en utilisant des modèles basiques. Il suffit d’utiliser le raccourci-clavier Ctrl+1 pour que l’éditeur propose une liste d’action possible. Par exemple dans un bloc try/catch
Mais revenons en à la concaténation : dès lors qu’on utilise l’opérateur + pour concaténer deux éléments en une chaine de caractère, le raccourci Ctrl+1 nous propose deux nouveaux choix :
- Use ‘StringBuilder’ for string concatenation
- Use ‘MessageFormat’ for string concatenation
Le premier permet donc d’utiliser un objet StringBuilder pour générer la chaine, alors que le second se base sur la classe MessageFormat qui propose des options de formatage plus évoluées.
Comme un exemple s’avère plus parlant, prenons le cas suivant :
String str = arg1 + " chaine " + arg2;
- Avec Use ‘StringBuilder’ for string concatenation, on obtient ceci :
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(arg1);
stringBuilder.append(" chaine ");
stringBuilder.append(arg2);
String str = stringBuilder.toString(); - Avec Use ‘MessageFormat’ for string concatenation, on obtient ceci :
String str = MessageFormat.format("{0} chaine {1}", arg1, arg2);
Mais cela ne se révèle pas très adapté…
- Le code utilisant un StringBuilder est censé être plus performant, mais il est surtout illisible et pas plus performant pour un sous !
En effet : les compilateurs modernes effectuent déjà ce genre d’optimisation, et les deux codes sont généralement totalement identique. On se retrouve donc avec un code inutilement illisible !
Pire, si les éléments concaténés sont des constantes, on peut même se retrouver avec des performances encore plus médiocre. En effet avec l’opérateur + le compilateur va concaténer les constantes dès la compilation et fournir donc une chaine complète lors de l’exécution, ce qui permet d’éviter pas mal de concaténation…
Le code est donc non seulement nettement moins lisible mais également potentiellement moins performant ! Un comble…
- MessageFormat permet quand à lui des options de formatage plus évolué, mais est également plus couteux pour une simple concaténation (et personnellement je lui aurais préféré String.format() avec sa syntaxe à-la-printf() – mais c’est un autre débat).
Bref pour moi ces propositions portent à confusion car elles peuvent faire penser que ces solutions sont préférable à l’opérateur +, et c’est loin d’être le cas…
C’est d’autant plus dommageable qu’en fait il n’y a qu’un seul cas où l’opérateur + doit être remplacé par un StringBuilder : lorsqu’il est utilisé à plusieurs reprises sur la même chaine dans une boucle, par exemple :
String str = "";
for (int i=0; i<size; i++) {
str += "ligne " + i;
}
Le compilateur génère en effet implicitement un nouvel StringBuilder à chaque itération, puis le transforme en String, ce qui génère un grand nombre d’objet temporaire et peut s’avérer fort couteux si le nombre d’itération est important…
L’utilisation directe du StringBuilder est alors nécessaire afin d’éviter d’instancier de multiple objets temporaires à chaque itération. Un code correct ressemblerait donc à ceci :
StringBuilder sb = new StringBuilder();
for (int i=0; i<size; i++) {
sb.append("ligne ").append(i);
}
String str = sb.toString();
Cela permet d’instancier un seul et unique objet StringBuilder qui est mis à jour au fil des itération. A noter qu’on pourrait encore légèrement optimiser ce code en spécifiant une taille d’initialisation au constructeur de StringBuilder, afin d’éviter quelques redimensionnements.
Malheureusement, eclipse n’arrive pas à générer ce code et produit dans ce cas précis les mêmes erreurs que le compilateur :
String str = "";
for (int i=0; i<size; i++) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("ligne ");
stringBuilder.append(i);
str += stringBuilder.toString();
}
On instancie en effet un nouvel objet StringBuilder à chaque itération, mais le comble venant du fait que l’opérateur + reste présent dans le code (via le +=) et qu’il y aura donc un second StringBuilder généré par le compilateur : au lieu de corriger le problème on l’amplifie !
Bien entendu, cela ne remet pas en cause les nombreuses qualités d’eclipse, mais il faut avouer que le choix de ce « quick assist » est plutôt étrange…
1 Commentaire + Ajouter un commentaire
Tutoriels
Discussions
- jre 1.5, tomcat 6.0 et multi processeurs
- Difference de performances Unix/Windows d'un programme?
- L'apparition du mot-clé const est-il prévu dans une version à venir du JDK?
- Possibilité d'accéder au type générique en runtime
- Classes, méthodes private
- [REFLEXION] Connaitre toutes les classes qui implémentent une interface
- Recuperation du nom des parametres
- Définition exacte de @Override
- [ fuite ] memoire
hum, je suis tombé sur ton post par hasard, intéressant.
Si je comprends bien, le compilateur remplace qqchose comme String foo = « bar » + getText(); par des append de StringBuilder de bon aloi.
Petite question toutefois : comment déterme t il alors la taille initiale du StringBuilder en question ?
En effet, lorsqu’on fait ça manuellement, on peut mettre une taille qui aura peu de chance d’être dépassée, d’où un gain en perf vu qu’on évite alors des recopies de chaines (qui se font normalement lorsque la taille de la chaine dépasse 32, 64 puis 128 et ainsi de suite => ainsi mettre 128 épargne déjà qq recopies, pour peu que la chaine effective soit comprise entre 32 et 128 ;)).
Si le compilateur fait des new StringBuilder() sans spécifier de taille ou sans prendre en compte les éléments de taille variable, l’intérêt de son action devient alors plus limitées. Qu’en dis tu ?
Enfin, quant à l’intérêt de cette petite optimisation (éviter des recopies inutiles), je suis d’accord qu’elle est vraiment fonction du chemin critique de l’application considérée.
++