String.replaceAll barras invertidas únicas com barras invertidas duplas

122

Estou tentando converter o String \something\em String \\something\\uso replaceAll, mas continuo recebendo todos os tipos de erros. Eu pensei que esta era a solução:

theString.replaceAll("\\", "\\\\");

Mas isso dá a exceção abaixo:

java.util.regex.PatternSyntaxException: Unexpected internal error near index 1
Frank Groeneveld
fonte

Respostas:

204

O String#replaceAll()interpreta o argumento como uma expressão regular . O \é um caractere de escape em ambos String e regex. Você precisa escapar duas vezes para regex:

string.replaceAll("\\\\", "\\\\\\\\");

Mas você não precisa necessariamente de regex para isso, simplesmente porque deseja uma substituição exata de caractere por caractere e não precisa de padrões aqui. Então String#replace(), basta:

string.replace("\\", "\\\\");

Atualização : conforme os comentários, parece que você deseja usar a sequência no contexto JavaScript. Talvez seja melhor usar StringEscapeUtils#escapeEcmaScript()para cobrir mais personagens.

BalusC
fonte
Na verdade, ele é usado em um JavaScript AST que deve ser convertido novamente na origem. Sua solução funciona. Obrigado!
9139 Frank Groeneveld
2
Se você quiser usar de String#replaceAll()qualquer maneira, pode citar a sequência de substituição com o Matcher # quoteReplacement () :theString.replaceAll("\\", Matcher.quoteReplacement("\\\\"));
phse
Matcher.quoteReplacement (...) é uma boa maneira! Por favor, veja a resposta da Pshemo!
Hartmut P.
14

Para evitar esse tipo de problema, você pode usar replace(que usa uma string simples) em vez de replaceAll(que usa uma expressão regular). Você ainda precisará escapar de barras invertidas, mas não das formas selvagens necessárias com expressões regulares.

Fabian Steeg
fonte
10

TLDR: use em theString = theString.replace("\\", "\\\\");vez disso.


Problema

replaceAll(target, replacement)usa sintaxe de expressão regular (regex) para targete parcialmente para replacement.

O problema é que \é um caractere especial em regex (pode ser usado como \dpara representar dígito) e em literal de String (pode ser usado como "\n"para representar separador de linha ou \"para escapar do símbolo de aspas duplas que normalmente representaria o fim da literal de string).

Em ambos os casos, para criar um \símbolo, podemos escapá- lo (torná-lo literal em vez de um caractere especial) colocando mais \antes dele (como escapamos "em literais de strings via \").

Portanto, a targetexpressão regular que representa o \símbolo precisará ser mantida \\, e a string literal que representa esse texto precisará se parecer "\\\\".

Então, escapamos \duas vezes:

  • uma vez na regex \\
  • uma vez no literal String "\\\\"(cada um \é representado como "\\").

No caso de replacement \também é especial lá. Ele nos permite escapar de outro caractere especial $que, por meio de $xnotação, nos permite usar parte dos dados correspondidos por regex e mantidos capturando o grupo indexado x, como, por exemplo , "012".replaceAll("(\\d)", "$1$1")irá corresponder a cada dígito, colocá-lo no grupo de captura 1 e $1$1substituí-lo por suas duas cópias (duplicará) resultando em "001122".

Então, novamente, para deixar replacementrepresentar \literal, precisamos escapar dela com mais, o \que significa que:

  • a substituição deve conter dois caracteres de barra invertida \\
  • e String literal que representa \\parece"\\\\"

MAS, como queremos replacementmanter duas barras invertidas, precisaremos "\\\\\\\\"(cada uma \representada por uma "\\\\").

Então, a versão com replaceAllpode parecer

replaceAll("\\\\", "\\\\\\\\");

Maneira mais fácil

Para tornar a vida mais fácil Java fornece ferramentas para escapar automaticamente texto targete replacementpartes. Portanto, agora podemos focar apenas em strings e esquecer a sintaxe regex:

replaceAll(Pattern.quote(target), Matcher.quoteReplacement(replacement))

que no nosso caso pode parecer

replaceAll(Pattern.quote("\\"), Matcher.quoteReplacement("\\\\"))

Melhor ainda

Se realmente não precisamos de suporte à sintaxe regex, não vamos envolver replaceAllnada. Em vez disso, vamos usar replace. Ambos os métodos substituirão todos os target s, mas replacenão envolvem sintaxe regex. Então você poderia simplesmente escrever

theString = theString.replace("\\", "\\\\");
Pshemo
fonte
7

Você precisará escapar da barra invertida (escapada) no primeiro argumento, pois é uma expressão regular. Substituição (segundo argumento - consulte Matcher # replaceAll (String) ) também possui um significado especial de barras invertidas; portanto, você precisará substituí-las por:

theString.replaceAll("\\\\", "\\\\\\\\");
sfussenegger
fonte
3

Sim ... no momento em que o compilador regex vê o padrão que você deu, ele vê apenas uma barra invertida (desde que o lexer do Java transformou o backwhack duplo em um único). É necessário substituir "\\\\"com "\\\\", acredite ou não! Java realmente precisa de uma boa sintaxe de cadeia bruta.

Jonathan Feinberg
fonte