Concaténation en Java ? Oui, mais comment ?

La concaténation est une opération simple et récurrente en programmation et le java ne déroge pas a la règle. Cependant, en java, il est possible de concaténer deux chaînes (ou plus) de différentes façons. Toutes ont leurs avantages et leurs inconvénients, les connaître nous permettra de choisir la manière la plus adaptée à chaque utilisation.

La concaténation de chaine

String test = "this" + " " + "is" + " " + "a" + " " + "test";

Si vous avez quelque chose comme ça dans votre code, sachez que, lors de la génération de vos fichiers .class depuis le .java, ce genre de concaténation est optimisée dans la mesure du possible.
Cette affectation deviendra donc :

String test = "this is a test";

Ce genre de cas semble peu probable, en effet qui va déclarer une chaîne de caractères comme montré dans cet exemple ? Probablement personne, mais il arrive que ce fonctionnement soit utilisé, notamment pour la création de constantes telles que :

public static final String BASE_URL = "test.server.com";
public static final String APP_NAME = "app";
public static final String DELIMITER = "/";

public static final String APP_URL = BASE_URL + DELIMITER + APP_NAME;

Cette utilisation est très pratique, comme les chaînes seront concaténées à la compilation, il n’y aura pas de temps de traitement supplémentaire. Il faut cependant connaître la valeur de chaque chaîne à l’écriture du code pour profiter de ce comportement, si une opération doit être effectuée pour récupérer ou calculer une valeur, la concaténation se fera à l’exécution.

De plus, la concaténation a l’aide de l’opérateur + va caster en String les types primitifs, appeler la methode toString() des objets non null. dans le cas où un objet null est concaténé a l’aide de + la chaine « null » sera utilisée..

String str = null;

String test1 = "test : " + 123; // chaine -> "test : 123"
String test2 = "test : " + 1L + new Integer(2); // chaine -> "test : 12"
String test3 = "test : " + str; // chaine -> "test : null"

Il est intéressant de noter que si la concaténation doit se faire à l’exécution, la classe StringBuilder sera utilisée silencieusement et les deux lignes suivantes provoqueront l’exécution du même code une fois compilé :

String a = b + c + d;
String a = new StringBuilder(b).append(c).append(d).toString();

La classe String

La classe String contient une méthode concat(String s) qui permet d’ajouter une chaîne à une autre de la manière suivante :

String a = "test"
String b = "ing"
String c = a.concat(b);

Cette méthode ne permet pas de concaténer autre chose que des strings. Si vous voulez concaténer autre chose vous devrez le caster ou appeler explicitement la méthode toString() de l’objet a concaténer. De plus, la méthode concat(String s) utilisant s.length(), si la chaîne passée est null, une NullPointerException est jetée. Un autre point intéressant de cette méthode est qu’elle vérifie la longueur de la chaîne avant d’effectuer la concaténation et donc ne fait l’opération que si la chaîne n’est pas vide, faisant gagner du temps d’exécution.

Un des problèmes de cette méthode est que, comme la classe String est Immutable, pour chaque appel un nouveau tableau de caractères est alloué et rempli à deux reprises : une fois par l’ancienne chaîne puis par la nouvelle. Cette méthode est donc très coûteuse surtout si elle est appelée plusieurs fois de suite. Pour cette raison, certains outils d’analyse de code tel que FindBugs considèrent les appels à concat() à l’intérieur d’une boucle comme étant problématique.

StringBuilder & StringBuffer

Ces deux classes sont assez proches et font, à un détail près, la même chose. A savoir concaténer des String. « Pourquoi en avoir deux alors ? » vous entends-je dire.
La réponse est simple : l’une est thread safe (StringBuffer) et l’autre ne l’est pas (StringBuilder).

Elles s’utilisent de la manière suivante :

StringBuffer buffer = new StringBuffer();
buffer.append( "yet " );
buffer.append( "another " );
buffer.append( "test" );
buffer.toString();

StringBuilder builder = new StringBuilder();
builder.append( "and " );
builder.append( "one " );
builder.append( "more" );
builder.toString();

Historiquement, la classe StringBuffer est le pendant mutable de String et date de Java 1.0.

Cette classe a été pensée pour la concaténation de chaînes dans un contexte multi-thread et pratiquement toutes ses méthodes sont synchronisées afin d’éviter tout problème d’accès concurrents. Il n’est donc pas nécessaire et même inutile de mettre en place des blocs synchronized lors de l’utilisation de StringBuffer. Cependant, du fait de sa sécurisation cette classe perd en performance si elle est utilisée dans un contexte mono-thread.

La Classe StringBuilder, elle, est très proche de StringBuffer mais aucune de ses méthodes n’est synchronisée, du coup son exécution est légèrement plus rapide mais elle ne devrait pas être utilisée dans le cas d’une application ou d’un module faisant usage de plusieurs threads (sauf si elle est contenue dans un bloc synchronized mais dans ce cas là pourquoi ne pas utiliser StringBuffer…).
StringBuilder date de java 1.5 ce qui peut être à prendre en compte si l’application que vous développez doit pouvoir tourner sur d’anciennes JVM.

Vous allez me dire, « Mais si le fait de concaténer, comme dit dans la première partie, avec ‘+’ est transformé en StringBuilder à la compilation, pourquoi s’embêter avec ? »
La réponse est simple :
Premièrement, si vous développez une application multi-thread où vous risquez des problèmes de concurrences, l’utilisation explicite de StringBuffer est recommandée.
Deuxièmement, si vous laissez Java utiliser StringBuilder implicitement, vous ne pouvez pas paramétrer la taille initiale du tableau de caractères interne à StringBuilder ce qui peut avoir un impact non négligeable sur les performances de la concaténation.
En effet, si la taille initiale est trop petite et qu’un grand nombre de concaténations sont faites, il faudra redimensionner le tableau de caractères plus de fois (et donc allouer un nouveau tableau et copier le contenu de l’ancien dans le nouveau), au détriment des performances.

Sachez enfin que pour des performances optimales avec un StringBuilder ou un StringBuffer, la capacité initiale doit être une puissance de 2, à choisir en fonction de la taille de chaîne que vous pensez avoir à la sortie, le moins de redimensionnement de tableau entraînant la plus grande rapidité d’exécution. (source)

Très bien mais je choisis quoi ?

Pour conclure, le choix de la méthode de concaténation dépend vraiment du contexte et peut avoir des effets très importants sur les performances d’un programme.
Cependant, si vous avez un jour un doute sur la façon de procéder, l’utilisation de StringBuffer est recommandée. Elle est à la fois rapide et robuste et permettra d’éviter certains problèmes de concurrence lors de la montée en charge de l’application du fait de sa construction totalement thread-safe.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *