Comment utiliser StringBuilder en Java?

Un petit programme peut créer beaucoup d’objets String très rapidement. Par exemple, combien d’objets pensez-vous que ce morceau de code crée ?

String alpha = "";
for(char current = 'a'; current <= 'z'; current++)
    alpha += current;
System.out.println(alpha);

La chaîne vide est instanciée, puis on ajoute un “a”. Cependant, comme l’objet String est immuable, un nouvel objet String est assigné à alpha, et l’objet “” devient éligible pour le ramasse-miettes. La prochaine fois dans la boucle, alpha est assigné à un nouvel objet String, “ab”, et l’objet “a” devient éligible pour le ramasse-miettes. L’itération suivante assigne alpha à “abc”, et l’objet “ab” devient éligible pour le ramasse-miettes, et ainsi de suite.

Cette séquence d’événements continue, et après 26 itérations dans la boucle, un total de 27 objets sont instanciés, la plupart devenant immédiatement éligibles pour le ramasse-miettes.

C’est très inefficace. Heureusement, Java a une solution. La classe StringBuilder crée une String sans stocker toutes ces valeurs String intermédiaires. Contrairement à la classe String, StringBuilder n’est pas immuable.

StringBuilder alpha = new StringBuilder();
for(char current = 'a'; current <= 'z'; current++)
    alpha.append(current);
System.out.println(alpha);

Une nouvelle instance de StringBuilder est créée. L’appel à append() ajoute un caractère à l’objet StringBuilder à chaque passage dans la boucle for, ajoutant la valeur de current à la fin de alpha. Ce code réutilise le même StringBuilder sans créer de String intermédiaire à chaque fois.

Dans du code ancien, vous pourriez voir des références à StringBuffer. Il fonctionne de la même manière, sauf qu’il supporte les threads, que vous découvrirez au chapitre sur la concurrence. StringBuffer est plus lent que StringBuilder, alors utilisez simplement StringBuilder.

Dans cette section, nous examinons comment créer un StringBuilder et utiliser ses méthodes courantes.

Mutabilité et Chaînage

Vous l’avez sûrement remarqué dans l’exemple précédent, mais StringBuilder n’est pas immuable. En fait, nous lui avons donné 27 valeurs différentes dans l’exemple (une vide plus l’ajout de chaque lettre de l’alphabet).

Le chaînage rend cela encore plus intéressant. Quand nous chaînons des appels de méthodes String, le résultat est une nouvelle String avec la réponse. Le chaînage des méthodes StringBuilder ne fonctionne pas ainsi. Au lieu de cela, le StringBuilder change son propre état et retourne une référence à lui-même. Examinons un exemple pour clarifier :

StringBuilder sb = new StringBuilder("debut");
sb.append("+milieu"); // sb = "debut+milieu"
StringBuilder meme = sb.append("+fin"); // "debut+milieu+fin"

La ligne 2 ajoute du texte à la fin de sb. Elle retourne également une référence à sb, qui est ignorée. La ligne 3 ajoute aussi du texte à la fin de sb et retourne une référence à sb. Cette fois, la référence est stockée dans meme. Cela signifie que sb et meme pointent vers le même objet et afficheraient la même valeur.

Le code n’est pas toujours facile à lire avec une seule méthode par ligne. Que pensez-vous que cet exemple affiche?

StringBuilder a = new StringBuilder("abc");
StringBuilder b = a.append("de");
b = b.append("f").append("g");
System.out.println("a=" + a);
System.out.println("b=" + b);

Avez-vous dit que les deux affichent “abcdefg”? Bien. Il n’y a qu’un seul objet StringBuilder ici. Nous le savons parce que new StringBuilder() n’est appelé qu’une seule fois. À la ligne 2, il y a deux variables faisant référence à cet objet, qui a une valeur de “abcde”. À la ligne 3, ces deux variables font toujours référence à ce même objet, qui a maintenant une valeur de “abcdefg”. D’ailleurs, la réaffectation à b ne fait absolument rien. b pointe déjà vers ce StringBuilder.

Création d’un StringBuilder

Il existe trois façons de construire un StringBuilder :

StringBuilder sb1 = new StringBuilder();
StringBuilder sb2 = new StringBuilder("animal");
StringBuilder sb3 = new StringBuilder(10);

La première crée un StringBuilder contenant une séquence vide de caractères et assigne sb1 pour y pointer. La seconde crée un StringBuilder contenant une valeur spécifique et assigne sb2 pour y pointer. Les deux premiers exemples disent à Java de gérer les détails d’implémentation. Le dernier exemple indique à Java que nous avons une idée de la taille éventuelle de la valeur et que nous aimerions que le StringBuilder réserve une certaine capacité, ou nombre d’emplacements, pour les caractères.

Méthodes importantes de StringBuilder

Comme avec String, nous n’allons pas couvrir toutes les méthodes de la classe StringBuilder. Voici celles que vous pourriez rencontrer.

Utilisation des méthodes communes

Ces quatre méthodes fonctionnent exactement comme dans la classe String. Assurez-vous de pouvoir identifier la sortie de cet exemple :

var sb = new StringBuilder("animaux");
String sousChaine = sb.substring(sb.indexOf("a"), sb.indexOf("al"));
int longueur = sb.length();
char ch = sb.charAt(6);
System.out.println(sousChaine + " " + longueur + " " + ch);

La réponse correcte est “anim 7 x”. Les appels à indexOf() retournent 0 et 4, respectivement. La méthode substring() retourne la String commençant à l’index 0 et se terminant juste avant l’index 4.

La méthode length() retourne 7 car c’est le nombre de caractères dans le StringBuilder plutôt qu’un index. Enfin, charAt() retourne le caractère à l’index 6. Ici, nous commençons bien à 0 car nous faisons référence aux index. Si cela ne vous semble pas familier, revenez lire la section sur String.

Notez que substring() retourne une String plutôt qu’un StringBuilder. C’est pourquoi sb n’est pas modifié. La méthode substring() est vraiment juste une méthode qui interroge l’état du StringBuilder.

Ajout de valeurs

La méthode append() est de loin la méthode la plus fréquemment utilisée dans StringBuilder. En fait, elle est si fréquemment utilisée que nous avons commencé à l’utiliser sans commentaire. Heureusement, cette méthode fait exactement ce qu’elle semble faire : elle ajoute le paramètre au StringBuilder et retourne une référence au StringBuilder actuel. Voici l’une des signatures de méthode :

public StringBuilder append(String str)

Notez que nous avons dit l’une des signatures de méthode. Il y a plus de 10 signatures de méthode qui se ressemblent mais prennent différents types de données comme paramètres, tels que int, char, etc. Toutes ces méthodes sont fournies pour que vous puissiez écrire du code comme ceci :

var sb = new StringBuilder().append(1).append('c');
sb.append("-").append(true);
System.out.println(sb); // 1c-true

Beau chaînage de méthodes, n’est-ce pas ? La méthode append() est appelée directement après le constructeur. En ayant toutes ces signatures de méthode, vous pouvez simplement appeler append() sans avoir à convertir votre paramètre en String d’abord.

Insertion de données

La méthode insert() ajoute des caractères au StringBuilder à l’index demandé et retourne une référence au StringBuilder actuel. Tout comme append(), il existe de nombreuses signatures de méthode pour différents types. En voici une :

public StringBuilder insert(int offset, String str)

Faites attention à l’offset dans ces exemples. C’est l’index où nous voulons insérer le paramètre demandé.

var sb = new StringBuilder("animaux");
sb.insert(7, "-");     // sb = animaux-
sb.insert(0, "-");     // sb = -animaux-
sb.insert(4, "-");     // sb = -ani-maux-
System.out.println(sb);

La ligne 2 dit d’insérer un tiret à l’index 7, qui se trouve être la fin de la séquence de caractères. La ligne 3 dit d’insérer un tiret à l’index 0, qui se trouve être le tout début. Enfin, la ligne 4 dit d’insérer un tiret juste avant l’index 4. Au fur et à mesure que nous ajoutons et supprimons des caractères, leurs index changent. Lorsque vous voyez une question traitant de telles opérations, dessinez ce qui se passe en utilisant les matériaux d’écriture disponibles afin de ne pas être confus.

Suppression de contenu

La méthode delete() est l’opposé de la méthode insert(). Elle supprime des caractères de la séquence et retourne une référence au StringBuilder actuel. La méthode deleteCharAt() est pratique lorsque vous voulez supprimer un seul caractère. Les signatures de méthode sont les suivantes :

public StringBuilder delete(int startIndex, int endIndex)
public StringBuilder deleteCharAt(int index)

Le code suivant montre comment utiliser ces méthodes :

var sb = new StringBuilder("abcdef");
sb.delete(1, 3);        // sb = adef
sb.deleteCharAt(5);     // exception

D’abord, nous supprimons les caractères commençant par l’index 1 et se terminant juste avant l’index 3. Cela nous donne adef. Ensuite, nous demandons à Java de supprimer le caractère à la position 5. Cependant, la valeur restante ne fait que quatre caractères de long, donc cela lance une StringIndexOutOfBoundsException.

La méthode delete() est plus flexible que d’autres en ce qui concerne les index de tableau. Si vous spécifiez un second paramètre qui dépasse la fin du StringBuilder, Java supposera que vous vouliez dire la fin. Cela signifie que ce code est légal :

var sb = new StringBuilder("abcdef");
sb.delete(1, 100);      // sb = a

Remplacement de portions

La méthode replace() fonctionne différemment pour StringBuilder que pour String. La signature de méthode est la suivante :

public StringBuilder replace(int startIndex, int endIndex, String newString)

Le code suivant montre comment utiliser cette méthode :

var constructeur = new StringBuilder("pigeon sale");
constructeur.replace(3, 6, "sty");
System.out.println(constructeur); // pigsty sale

D’abord, Java supprime les caractères commençant par l’index 3 et se terminant juste avant l’index 6. Cela nous donne “pig sale”. Puis Java insère la valeur “sty” à cette position.

Dans cet exemple, le nombre de caractères supprimés et insérés est le même. Cependant, il n’y a aucune raison pour qu’ils le soient. Que pensez-vous que cela fait ?

var constructeur = new StringBuilder("pigeon sale");
constructeur.replace(3, 100, "");
System.out.println(constructeur);

Cela affiche “pig”. Rappelez-vous, la méthode fait d’abord une suppression logique. La méthode replace() permet de spécifier un second paramètre qui dépasse la fin du StringBuilder. Cela signifie que seuls les trois premiers caractères restent.

Inversion

Après tout cela, il est temps pour une méthode facile et agréable. La méthode reverse() fait exactement ce qu’elle semble faire : elle inverse les caractères dans les séquences et retourne une référence au StringBuilder actuel. La signature de méthode est la suivante :

public StringBuilder reverse()

Le code suivant montre comment utiliser cette méthode :

var sb = new StringBuilder("ABC");
sb.reverse();
System.out.println(sb);

Comme prévu, cela affiche CBA. Cette méthode n’est pas si intéressante.

Travailler avec toString()

La classe Object contient une méthode toString() dont de nombreuses classes fournissent des implémentations personnalisées. La classe StringBuilder en fait partie.

Le code suivant montre comment utiliser cette méthode :

var sb = new StringBuilder("ABC");
String s = sb.toString();

Souvent, StringBuilder est utilisé en interne pour des raisons de performance, mais le résultat final doit être une String. Par exemple, peut-être qu’il doit être passé à une autre méthode qui attend une String.