web-dev-qa-db-fra.com

Pourquoi String.replaceAll () dans Java nécessite 4 barres obliques "\\\\" dans regex pour réellement remplacer "\"?

J'ai récemment remarqué que String.replaceAll (regex, remplacement) se comporte très bizarrement en ce qui concerne le caractère d'échappement "\" (barre oblique)

Par exemple, considérez qu'il y a une chaîne avec chemin de fichier - String text = "E:\\dummypath" Et nous voulons remplacer le "\\" Par "/".

text.replace("\\","/") donne la sortie "E:/dummypath" tandis que text.replaceAll("\\","/") lève l'exception Java.util.regex.PatternSyntaxException.

Si nous voulons implémenter la même fonctionnalité avec replaceAll() nous devons l'écrire comme, text.replaceAll("\\\\","/")

Une différence notable est que replaceAll() a ses arguments comme reg-ex alors que replace() a des arguments séquence de caractères!

Mais text.replaceAll("\n","/") fonctionne exactement de la même manière que son équivalent de séquence de caractères text.replace("\n","/")

Digging Deeper: Encore plus de comportements étranges peuvent être observés lorsque nous essayons d'autres entrées.

Permet d'affecter text="Hello\nWorld\n"

Maintenant, text.replaceAll("\n","/"), text.replaceAll("\\n","/"), text.replaceAll("\\\n","/") tous ces trois donne la même sortie Hello/World/

Java avait vraiment foiré le reg-ex de la meilleure façon possible que je ressens! Aucun autre langage ne semble avoir ces comportements ludiques dans reg-ex. Pour une raison spécifique, pourquoi Java gâché comme ça?

27
Bharath

La réponse de @Peter Lawrey décrit la mécanique. Le "problème" est que la barre oblique inverse est un caractère d'échappement dans les deux Java littéraux de chaîne, et dans le mini-langage des expressions régulières. Ainsi, lorsque vous utilisez un littéral de chaîne pour représenter une expression régulière, il y a deux ensembles d'évasion à considérer ... en fonction de ce que vous voulez que l'expression régulière signifie.

Mais pourquoi est-ce comme ça?

C'est une chose historique. Java à l'origine n'avait pas du tout de regex. Les règles de syntaxe pour Java Les littéraux de chaîne ont été empruntés à C/C++, qui n'avait pas non plus de dans la prise en charge des expressions rationnelles. La maladresse du double échappement n'est pas apparue dans Java jusqu'à ce qu'ils ajoutent la prise en charge des expressions régulières sous la forme de la classe Pattern ... dans Java 1.4.

Alors, comment les autres langues parviennent-elles à éviter cela?

Ils le font en fournissant un support syntaxique direct ou indirect pour les expressions rationnelles dans le langage de programmation lui-même. Par exemple, en Perl, Ruby, Javascript et dans de nombreux autres langages, il existe une syntaxe pour les modèles/expressions régulières (par exemple, '/ pattern /') où les règles d'échappement littérales de chaîne ne s'appliquent pas. En C # et Python, ils fournissent une syntaxe alternative littérale de chaîne "brute" dans laquelle les barres obliques inverses ne sont pas des échappements. (Mais notez que si vous utilisez la syntaxe de chaîne normale C #/Python, vous avez le problème Java de double échappement).)


Pourquoi text.replaceAll("\n","/"), text.replaceAll("\\n","/") et text.replaceAll("\\\n","/") donnent-ils tous la même sortie?

Le premier cas est un caractère de nouvelle ligne au niveau de la chaîne. Le langage Java regex traite tous les caractères non spéciaux comme étant identiques.

Le deuxième cas est une barre oblique inverse suivie d'un "n" au niveau de la chaîne. Le langage Java regex interprète une barre oblique inverse suivie d'un "n" comme une nouvelle ligne.

Le dernier cas est une barre oblique inverse suivie d'un caractère de nouvelle ligne au niveau de la chaîne. Le langage Java regex ne reconnaît pas cela comme une séquence d'échappement spécifique (regex). Cependant, dans le langage regex, une barre oblique inverse suivie d'un caractère non alphabétique signifie le dernier caractère. Ainsi, un barre oblique inverse suivie d'un caractère de nouvelle ligne ... signifie la même chose qu'une nouvelle ligne.

23
Stephen C

Vous devez vous échapper deux fois, une fois pour Java, une fois pour l'expression régulière.

Le code Java est

"\\\\"

fait une chaîne d'expression régulière de

"\\" - two chars

mais le regex a aussi besoin d'une évasion pour se transformer en

\ - one symbol
26
Peter Lawrey

1) Supposons que vous souhaitiez remplacer un seul \ en utilisant la méthode replaceAll de Java:

   \
   ˪--- 1) the final backslash

2) La méthode replaceAll de Java prend une expression régulière comme premier argument. Dans un littéral regex, \ a une signification particulière, par exemple dans \d qui est un raccourci pour [0-9] (n'importe quel chiffre). La façon d'échapper à une métachar dans un littéral regex est de le précéder d'un \, qui conduit à:

 \ \
 | ˪--- 1) the final backslash
 |
 ˪----- 2) the backslash needed to escape 1) in a regex literal

3) En Java, il n'y a pas littéral regex: vous écrivez une expression régulière dans un littéral chaîne (contrairement à JavaScript par exemple, où vous pouvez écrire /\d+/). Mais dans un chaîne littérale, \ a également une signification particulière, par exemple dans \n (une nouvelle ligne) ou \t (un onglet). La façon d'échapper à une métachar dans un chaîne littérale est de la précéder d'un \, qui conduit à:

\\\\
|||˪--- 1) the final backslash
||˪---- 3) the backslash needed to escape 1) in a string literal
|˪----- 2) the backslash needed to escape 1) in a regex literal
˪------ 3) the backslash needed to escape 2) in a string literal
5
sp00m