Hier soir, plongé dans du code de Xerces-J pour notre parseur XML j’ai vu plusieurs fois ce bout de code:
protected final static String A_CONSTANT = "constantTest".intern();
Quel est le fonctionnement de la méthode intern() de la class String ? Comme vous le savez en Java, il y a deux moyens de comparer deux objets. La méthode equals() et l’opérateur ==. La méthode equals() s’assure que les deux objets contiennent les mêmes données alors que l’opérateur == vérifie si les références de l’ojbet pointent sur le même objet. D’ailleurs une des premières leçons des débutants en Java est qu’il faut en général utiliser la méthode equals() pour comparer 2 String en Java.
En effet à votre avis quel est le résultat de l’exécution de ce code ?
System.out.println("Comparaison: "+ new String("Touilleur)==new String("Touilleur") );
Si vous avez répondu Comparaison: true vous vous êtes trompé. S’agissant de deux objets distints, ils ne sont pas égaux. Le code retourne false. Voyons maintenant avec l’opérateur equals de la class String:
System.out.println("Comparaison: "+ new String("Touilleur).equals(new String("Touilleur")) );
Dans ce cas précis le programme retournera Comparaison: true car la méthode equals compare les contenus des chaines. Cependant cette méthode qui itére les caractères peut être très lente. Alors comment Java optimise ce pattern en interne ? N’en déplaise à mes amis fan de C/C++ les gens qui codent la virtual machine de Java ne sont pas plus bêtes que d’autres, et bien souvent ils ont aussi de bonnes connaissances en C/C++.
L’opérateur == compare l’identité en mémoire des objets, et donc cet appel sera vraiment plus rapide que l’appel de la méthode equals. Si comme dans Xerces-J la comparaison de String est le coeur de votre moteur, il est indispensable de l’optimiser, les gains en terme de performances seront important.
Pour cela ce qu’il faut c’est une liste de Strings à comparer en mémoire dans laquelle nous viendrons piocher lorsque nous devrons comparer une String à notre liste de référence. D’autre part si la String comparée n’existe pas nous voudrons stocker sa référence en mémoire. Avec ce mécanisme il sera possible d’utiliser l’opérateur == pour comparer des instances. C’est le rôle de la méthode intern() de la class String qui gére cette liste et qui nous retourne la référence de la string en mémoire.
public static void main(String[] args) { System.out.println("Test 1 "+(new String("Touilleur")==new String("Touilleur"))); System.out.println("Test 2 "+( (new String("Touilleur").intern()) == (new String("Touilleur").intern()))); }
Attention aux parenthèses qui sont importantes ici pour que le test soit valide. L'execution de ce programme affichera:Test 1 false Test 2 true
La question qui reste en suspend est pourquoi alors une String qui est static et final est instancié avec un appel à intern() ? Nous sommes habitués à voir ce code là:
public static final String MY_PARAM="params";
moi ce qui m’a étonnné c’est
public static final String MY_PARAM="params".intern();
Il se trouve que Java fait en sorte que ce genre de pattern soit déjà optimisé. Toutes les Strings d’une class sont par défaut automatiquement « internés » de sorte que vous pouvez avoir quelques optimisations. Le code ci-dessous montre que les String qui peuvent être résolue à la compilation seront optimisées. Si par contre vous lancez le programme en passant test (java QuickTest test) vous verrez que les paramètres dynamiques ne peuvent pas être optimisé et que l’execution du code retournera false.
public class QuickTest
{
public static final String MY_PARAM="test";
public static void main(String[] args)
{
// Est-ce que test est identique a MY_PARAM ?
System.out.println("test" == MY_PARAM); // VA AFFICHER TRUE
// Et si nous concatenons la string
String s2="te"+"st";
System.out.println( s2 == MY_PARAM); // VA AFFICHER TRUE
// Si nous specifions test en tant que premier argument de ce programme
// afin de voir a l'execution si nous avons une difference
String s3=args[0];
System.out.println(s3 == MY_PARAM); // VA AFFICHER FALSE
// Maintenant si nous passons par l'appel a intern()
System.out.println(s3.intern() == MY_PARAM); // VA AFFICHER TRUE
}
}
Le 4eme appel dans le code ci-dessus est donc la raison pour laquelle nous pouvons tout à fait ajouter un call intern() sur une constante en Java. Il faut donc simplement que notre code force un appel à intern() pour pouvoir utiliser l’opérateur ==. D’autre part pour revenir au début de ce post, forcer un appel à intern() sur une constante permet de transformer cette constante résolue à la compilation en constante résolue à l’execution. (Voir ) bien que finalement ceci est un intérêt assez limité.
La méthode intern() de la class String est une méthode native. L’implémentation est donc déléguée à la JVM, ce qui fait qu’il y a aussi une difference en terme de performance entre BEA JRockIt et SUN JVM