¿Por qué String.replaceAll () en java requiere 4 barras “\\\\” en la expresión regular para reemplazar realmente “\”?

Recientemente me di cuenta de que String.replaceAll (regex, reemplazo) se comporta de manera muy extraña cuando se trata del carácter de escape “\” (barra inclinada)

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

text.replace("\\","/") da el resultado "E:/dummypath" mientras que text.replaceAll("\\","/") genera 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 character-sequence!

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

Profundizando más: se pueden observar comportamientos aún más extraños cuando probamos otras entradas.

Permite asignar text="Hello\nWorld\n"

Ahora, text.replaceAll("\n","/") , text.replaceAll("\\n","/") , text.replaceAll("\\\n","/") todos estos tres dan el mismo resultado Hello/World/

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

La respuesta de Peter Lawrey describe la mecánica. El “problema” es que la barra diagonal inversa es un carácter de escape en ambos literales de cadena de Java, y en el mini-lenguaje de expresiones regulares. Entonces, cuando usa un literal de cadena para representar una expresión regular, hay que considerar dos conjuntos de escapes … dependiendo de lo que quiera que signifique la expresión regular.

Pero, ¿por qué es así?

Es algo histórico. Java originalmente no tenía expresiones regulares en absoluto. Las reglas de syntax para los literales de Java String fueron tomadas de C / C ++, que tampoco tenía soporte de expresiones regulares incorporado. La incomodidad del doble escape no se hizo evidente en Java hasta que agregaron soporte de expresiones regulares en forma de la clase Pattern … en Java 1.4.

Entonces, ¿cómo otros idiomas logran evitar esto?

Lo hacen al proporcionar soporte sintáctico directo o indirecto para las expresiones regulares en el lenguaje de progtwigción en sí . Por ejemplo, en Perl, Ruby, Javascript y muchos otros lenguajes, hay una syntax para patrones / expresiones regulares (por ejemplo, ‘/ patrón /’) donde no se aplican las reglas de escape de cadenas literales. En C # y Python, proporcionan una syntax literal de cadena “en bruto” alternativa en la que las barras diagonales inversas no son escapes. (Pero tenga en cuenta que si usa la syntax de cadena C # / Python normal, tiene el problema de Java de doble escape).


¿Por qué text.replaceAll("\n","/") , text.replaceAll("\\n","/") y text.replaceAll("\\\n","/") proporcionan el misma salida?

El primer caso es un carácter de nueva línea en el nivel de Cadena. El lenguaje de expresiones regulares de Java trata todos los caracteres no especiales para que coincidan.

El segundo caso es una barra invertida seguida de una “n” en el 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 línea nueva en el nivel de cadena. El lenguaje regex de Java no reconoce esto como una secuencia de escape específica (regex). Sin embargo, en el lenguaje de expresiones regulares, una barra invertida seguida de cualquier carácter no alfabético significa el último carácter. Entonces, una barra invertida seguida de un carácter de nueva línea … significa lo mismo que una nueva línea.

Necesitas tener esacpe dos veces, una vez para Java, una vez para la expresión regular.

El código de Java es

 "\\\\" 

hace una cadena de expresiones regulares de

 "\\" - two chars 

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

 \ - one symbol 

1) Supongamos que quiere 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 literal de expresión regular , \ tiene un significado especial, por ejemplo, en \d que es un atajo para [0-9] (cualquier dígito). La forma de escapar de un metachar en un literal de expresión regular es precederlo con un \ , lo que conduce a:

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

3) En Java, no hay literal regex : usted escribe una expresión regular en un literal de cadena (a diferencia de JavaScript, por ejemplo, donde 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 metachar en un literal de cadena es precederlo con un \ , lo 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

Esto se debe a que Java intenta darle un significado especial a la cadena de reemplazo, por lo que \ $ será un signo $ literal, pero en el proceso parece que han eliminado el significado especial real de \

Mientras text.replaceAll("\\\\","/") , al menos se puede considerar que está bien en algún sentido (aunque en sí mismo no es del todo correcto), todas las tres ejecuciones, text.replaceAll("\n","/") , text.replaceAll("\\n","/") , text.replaceAll("\\\n","/") dando el mismo resultado parece aún más divertido. Simplemente es contradictorio sobre por qué han restringido el funcionamiento del text.replaceAll("\\","/") por el mismo motivo.

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

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

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

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

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

Además de java, nunca he visto un lenguaje analizar la expresión regular de esta manera. Se confundirá si ha utilizado expresiones regulares en algunos otros idiomas.

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

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

Al usar esta clase de Matcher puede obtener el resultado esperado.