Por qué Cuerda.replaceAll () en java requiere 4 barras "\\" en expresiones regulares para realmente reemplazar ""?


Recientemente me di cuenta de que, Cadena.replaceAll(regex,replacement) se comporta muy extrañamente cuando se trata del carácter de escape "\"(slash)

Por ejemplo, considere que hay una cadena con filepath - String text = "E:\\dummypath" y queremos reemplazar el "\\" con "/".

text.replace("\\","/") da la salida "E:/dummypath" mientras que text.replaceAll("\\","/") plantea la excepción java.util.regex.PatternSyntaxException.

Si queremos implementar la misma funcionalidad con replaceAll() necesitamos escribirla como, text.replaceAll("\\\\","/")

Una diferencia notable es replaceAll() tiene sus argumentos como reg-ex mientras que replace() tiene argumentos secuencia de caracteres!

Pero text.replaceAll("\n","/") funciona exactamente igual que su equivalente de secuencia de caracteres text.replace("\n","/")

Cavar más profundo: Incluso se pueden observar comportamientos más extraños cuando probamos algunas otras entradas.

Vamos a asignar text="Hello\nWorld\n"

Ahora, text.replaceAll("\n","/"), text.replaceAll("\\n","/"), text.replaceAll("\\\n","/") todos estos tres dan la misma salida Hello/World/

Java realmente había metido la pata con el reg-ex en su mejor manera posible me siento! Ningún otro idioma parece tener estos comportamientos lúdicos en reg-ex. ¿Alguna razón específica, por qué Java se equivocó así?

Author: shmosel, 2013-09-18

6 answers

La respuesta de@Peter Lawrey describe la mecánica. El "problema" es que la barra invertida es un carácter de escape en ambos literales de cadena Java, y en el mini-lenguaje de expresiones regulares. Así que cuando se utiliza un literal de cadena para representar una expresión regular, hay dos conjuntos de escape a considerar ... dependiendo de lo que quieras que signifique la expresión regular.

Pero, ¿por qué es así?

Es una cosa histórica. Java originalmente no tenía expresiones regulares en absoluto. Las reglas de sintaxis para los literales de cadena de Java fueron prestado de C / C++, que tampoco tenía soporte regex incorporado. La incomodidad del escape doble no se hizo evidente en Java hasta que agregaron soporte de expresiones regulares en la forma de la clase Pattern... en Java 1.4.

Entonces, ¿cómo se las arreglan otras lenguas para evitar esto?

Lo hacen proporcionando soporte sintáctico directo o indirecto para expresiones regulares en el propio lenguaje de programación. Por ejemplo, en Perl, Ruby, Javascript y muchos otros lenguajes, hay una sintaxis para patrones / expresiones regulares (por ejemplo, '/pattern/') donde no se aplican las reglas de escape literal de cadena. En C# y Python, proporcionan una sintaxis literal de cadena "raw" alternativa en la que las barras invertidas no son escapes. (Pero tenga en cuenta que si utiliza la sintaxis de cadena normal de C# / Python, tiene el problema de Java de doble escape.)


¿Por qué text.replaceAll("\n","/"), text.replaceAll("\\n","/"), y text.replaceAll("\\\n","/") todos dan la misma salida?

El primer caso es un carácter de nueva línea a nivel de cadena. La expresión regular de Java el lenguaje trata a todos los caracteres no especiales como coincidentes.

El segundo caso es una barra invertida seguida de una "n" a nivel de cadena. El lenguaje regex de Java interpreta una barra invertida seguida de una " n " como una nueva línea.

El último caso es una barra invertida seguida de un carácter de nueva línea a nivel de cadena. El lenguaje regex de Java no reconoce esto como una secuencia de escape específica (regex). Sin embargo, en el lenguaje regex, una barra invertida seguida de cualquier carácter no alfabético significa el último carácter. Por lo tanto, una barra invertida seguida de un carácter de nueva línea ... significa lo mismo que una nueva línea.

 22
Author: Stephen C,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-20 01:11:11

Necesita esacpe dos veces, una para Java, otra para la expresión regular.

El código Java es

"\\\\"

Hace una cadena regex de

"\\" - two chars

Pero la expresión regular también necesita un escape, por lo que se convierte en

\ - one symbol
 24
Author: Peter Lawrey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-18 15:07:54

1) Supongamos que desea reemplazar un único \ utilizando el método replaceAll de Java:

\
˪--- 1) the final backslash

2) El método replaceAll de Java toma una expresión regular como primer argumento. En un regex literal, \ tiene un significado especial, por ejemplo, en \d que es un atajo para [0-9] (cualquier dígito). La manera de escapar de un metacar en un regex literal es precederlo con un \, que conduce a:

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

3) En Java, no hay regex literal : escribes una regex en una cadena literal (a diferencia de JavaScript, por ejemplo, donde se puede escribir /\d+/). Pero en un literal de cadena , \ también tiene un significado especial, por ejemplo, en \n (una nueva línea) o \t (una pestaña). La forma de escapar de un metacar en una cadena literal es precederla con una \, que conduce a:

\\\\
|||˪--- 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
 3
Author: sp00m,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-30 09:36:01

Esto se debe a que Java intenta dar \ un significado especial en la cadena de reemplazo, de modo que \$ será un signo literal$, pero en el proceso parecen haber eliminado el significado especial real de \

Mientras que text.replaceAll("\\\\","/"), al menos se puede considerar que está bien en algún sentido (aunque en sí no es absolutamente correcto), las tres ejecuciones, text.replaceAll("\n","/"), text.replaceAll("\\n","/"), text.replaceAll("\\\n","/") dar la misma salida parece aún más divertido. Es simplemente contradictorio en cuanto a por qué han restringido el funcionamiento de text.replaceAll("\\","/") por la misma razón.

Java no se equivocó con las expresiones regulares. Es porque a Java le gusta meterse con los codificadores tratando de hacer algo único y diferente, cuando no es necesario.

 -1
Author: coder91,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-18 17:09:46

Una forma de evitar este problema es reemplazar la barra invertida con otro carácter, usar ese carácter suplente para reemplazos intermedios, luego convertirlo de nuevo en barra invertida al final. Por ejemplo, para convertir "\r\n" a "\n":

String out = in.replace('\\','@').replaceAll("@r@n","@n").replace('@','\\');

Por supuesto, eso no funcionará muy bien si elige un carácter de reemplazo que pueda ocurrir en la cadena de entrada.

 -2
Author: MTaylorEx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-29 18:12:49

Creo que java realmente se metió con la expresión regular en Cadena.replaceAll ();

Aparte de java, nunca he visto un lenguaje analizar expresiones regulares de esta manera. Te confundirás si has usado expresiones regulares en otros idiomas.

En caso de usar la "\\" en la cadena de reemplazo, puede usar java.util.regex.Matcher.quoteReplacement(String)

String.replaceAll("/", Matcher.quoteReplacement("\\"));

Usando esta clase Matcher puede obtener el resultado esperado.

 -3
Author: Rajagopal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-17 16:35:10