String
, StringBuffer
et StringBuilder
.
La classe
String
est immuable, alors que StringBuffer
et StringBuilder
sont modifiables.
La classe String
UneString
est un objet, que l'on peut donc créer en utilisant l'opérateur new
:
String s1 = new String("abc");Remarquer l'utilisation des doubles quotes pour créer une
String
(alors qu'on utilise les simples quotes pour créer un char
).
Java permet d'utiliser d'utiliser une syntaxe plus compacte : les string litterals :
String s1 = "abc";Depuis java 15, on peut initialiser une chaîne sur plusieurs lignes :
class MultiLine { public static void main(String[] args){ String html = """ <html> <body> <h1>Hello, world</h1> </body> </html> """; System.out.println(html); } }(code dans MultiLine.java)
Les 3 premiers guillemets (""") doivent être suivis d'un retour à la ligne.
Dans le string litteral, si une ligne se termine par antislash (\), la String ne contient pas de newline (\n).
(pratique pour les lignes très longues).
String singleLine = """ Hello \ World """;
Tableau de caractères
En interne, une String est représentée par un tableau dechar
(16 bits) ; depuis java 9, si une String
ne contient que des caractères ISO-8859-1, la représentation est optimisée pour utiliser des byte
(8 bits) ; on parle de compact strings.
String s1 = "abc";est équivalent à :
char[] data = {'a', 'b', 'c'}; String str = new String(data); // noter constructeur par recopieMais on ne peut pas directement manipuler une chaîne comme un tableau :
// ne passe pas à la compilation String s2 = "une chaîne de caractères"; for(int i=0; i < s2.length; i++){ System.out.println(s2[i]); }On doit utiliser les méthodes
length()
et charAt()
:
String s2 = "une chaîne de caractères"; for(int i=0; i < s2.length(); i++){ System.out.println(s2.charAt(i)); }(code dans CharAt.java)
Comparaison de chaînes
Comme pour les autres objets, l'opérateur==
teste l'égalité des références et ne renverra pas true
si le contenu de deux chaînes est identique (ce qui est le résultat auquel on s'attend intuitivement).
Dans le cas général,
Object.equals()
revient au même que ==
, mais equals()
est overridé dans String
pour faire une comparaison caractère par caractère et renvoyer un résultat conforme à l'intuition (voir la page java.lang.Object pour la comparaison des objets en général).
Il faut donc toujours utiliser la méthode
equals()
pour comparer deux chaînes.public class Equals1{ public static void main(String[] args){ String s1 = "abc", s2 = "def"; // noter initialisations multiples pour des variables de même type String s3 = s1 + s2; String s4 = "abcdef"; System.out.println("s3 == s4 : " + (s3 == s4) ); System.out.println("s3.equals(s4) : " + s3.equals(s4) ); System.out.println("s4.equals(s3) : " + s4.equals(s3) ); } }(code dans Equals1.java)
java Equals1
s3 == s4 : false s3.equals(s4) : true s4.equals(s3) : trueIllustre bien le comportement de
String
:
==
teste l'égalité entre références, et renvoie false
même si deux chaînes ont même contenu ;
String.equals()
a été overridé, et renvoie true
lorsque deux chaînes ont même contenu.
Optimisation du compilateur
Mais==
peut parfois renvoyer true
, suite à une optimisation du compilateur.
public class Equals2{ public static void main(String[] args){ String s1 = "azerty"; String s2 = "azerty"; System.out.println("s1 == s2 : " + (s1 == s2) ); } }(code dans Equals2.java)
java Equals2
s1 == s2 : trueD'après le test dans
Equals1
, on s'attend à ce que s1 == s2
soit false
.
Mais ici, le compilateur a repéré que le contenu de
s1
et s2
est exactement identique, et les deux références pointent vers la même adresse mémoire, donc ==
renvoie true
.
Concaténation
La classeString
contient une méthode concat()
, pas très pratique pour une opération si fréquente.
Java permet donc la concaténation de chaînes avec l'opérateur
+
.
Une fonctionnalité très utile est qu'avec l'opérateur
+
, une String
peut être concaténée avec un objet de n'importe quel type (référence ou primitif), ce qui n'est pas le cas avec concat()
.
En effet, lorsque l'opérateur
+
est utilisé, la méthode toString()
est implicitement appelée sur chacun des opérandes, précédée d'un autoboxing si nécessaire.
public class Concat{ public static void main(String[] args){ String s1 = "abc", s2 = "def" String s3; s3 = s1.concat(s2); System.out.println("Avec concat : s3 = " + s3); s3 = s1 + s2; System.out.println("Avec + : s3 = " + s3); int i = 4; String s4; // s4 = s3.concat(i); // pas possible s4 = s3 + i; System.out.println("s4 = " + s4); } }(code dans Concat.java)
java Concat
Avec concat : s3 = abcdef Avec + : s3 = abcdef s4 = abcdef4
Performance
On a vu qu'uneString
est immuable. Donc lorsqu'on fait :
s3 = s1 + s2;ce n'est pas le contenu de la zone mémoire référencé par
s3
qui change. Une autre String
est fabriquée, et s3
est modifiée pour pointer vers une autre zone de la mémoire. Le compilateur fait une opération du genre :
s3 = new StringBuilder(s1).append(s2).toString();On voit que ça implique la création de 2 objets supplémentaires :
- une
StringBuilder
,
- une autre
String
, avec toString()
.
s1
pointera vers cette nouvelle String
.
De plus, le contenu de la chaîne est copié deux fois en mémoire :
- Une fois lorsqu'on fait
s3 = new StringBuilder(s1)
- Une fois lors du
toString()
Dans une boucle, la différence peut devenir très importante :
public class BenchConcat{ private final static int N = 100000; private final static String test = "A"; public static void main(String[] args){ long t1, t2, dt; t1 = System.currentTimeMillis(); concatString(); t2 = System.currentTimeMillis(); dt = t2 - t1; System.out.println("concat String : dt = " + dt); t1 = System.currentTimeMillis(); concatStringBuilder(); t2 = System.currentTimeMillis(); dt = t2 - t1; System.out.println("concat StringBuilder : dt = " + dt); } private static void concatString(){ String res = ""; for(int i = 0; i < N; i++){ res += test; } } private static void concatStringBuilder(){ StringBuilder res = new StringBuilder(""); for(int i = 0; i < N; i++){ res.append(test); } } }(code dans BenchConcat.java)
java BenchConcat
concat String : dt = 911 concat StringBuilder : dt = 8
Méthodes fréquentes
La classeString
contient de nombreuses méthodes pour manipuler les chaînes ; voir la liste complète dans la documentation de l'API java.
- Constructeurs, pour fabriquer des
String
à partir dechar[]
,byte[]
,StringBuilder
,StringBuffer
. - Fonctions
valueOf()
pour construire desString
à partir d'autres types - Méthodes pour travailler sur des chaînes :
equals() equalsIgnoreCase() startsWith() endsWith() match() compareTo() // pour comparer des chaînes, attention à i18n, voir class Collator trim() charAt() indexOf() substring() replace() replaceFirst() replaceAll() split() join() toUpperCase() toLowerCase()
Classes StringBuilder et StringBuffer
Ces deux classes représentent aussi des chaînes de caractères, mais sont mutables.Il est donc conseillé de les utiliser pour des raisons de performance lorsqu'on a du code qui manipule des chaînes.
Les deux méthodes les plus utiles sont
append()
et insert()
.
StringBuffer
a été introduite en java 1, et elle est "thread safe" : lorsqu'on utilise une méthode de StringBuffer
, on a la garantie qu'elle s'exécutera jusqu'au bout, sans qu'un autre thread ne puisse venir perturber son exécution.
Cet aspect thread safe n'étant pas utile dans de nombreuses situations, une nouvelle classe a été introduite en java 5 :
StringBuilder
, dont l'API est compatible avec StringBuffer
.
Il est donc conseillé d'utiliser en général
StringBuilder
lorsqu'on n'a pas besoin de l'aspect "thread safe".
Exercice : ToString.
Exercice : Palindrome.
String formating
Un équivalent deprintf()
existe en java :
System.out.printf("format-string" [, arg1, arg2, ... ]);La même syntaxe peut être utilisée avec
String.format()
.
String str = "toto"; System.out.printf("str = %s%n", str); // str = toto System.out.printf("str = %S%n", str); // str = TOTO double dbl = 12.0; System.out.printf("dbl = %f%n", dbl); // dbl = 12,000000 System.out.printf("dbl = % f%n", dbl); // dbl = 12,000000 System.out.printf("dbl = %+f%n", dbl); // dbl = +12,000000 System.out.printf("dbl = %.2f%n", dbl); // dbl = 12,00 var locale = new Locale("en"); System.out.printf(locale, "dbl = %f%n", dbl); // dbl = 12.000000 int i = 4500; System.out.printf("i = %d%n", i); // i = 4500 System.out.printf("i = %,d%n", i); // i = 4 500 System.out.printf(locale, "i = %,d%n", i); // i = 4,500 String str2; str2 = String.format("%s", str); System.out.println(str2); // toto(code dans Format.java)
La référence complète se trouve dans le javadoc de la classe
java.util.Formatter