Warum verhält sich \ R in regulären Ausdrücken zwischen Java 8 und Java 9 anders?

78

Der folgende Code wird sowohl in Java 8 als auch in Java kompiliert, verhält sich jedoch unterschiedlich.

class Simple {
    static String sample = "\nEn un lugar\r\nde la Mancha\nde cuyo nombre\r\nno quiero acordarme";

    public static void main(String args[]){
        String[] chunks = sample.split("\\R\\R");
        for (String chunk: chunks) {
            System.out.println("Chunk : "+chunk);
        }
    }
}

Wenn ich es mit Java 8 ausführe, wird Folgendes zurückgegeben:

Chunk : 
En un lugar
de la Mancha
de cuyo nombre
no quiero acordarme

Aber wenn ich es mit Java 9 ausführe, ist die Ausgabe anders:

Chunk : 
En un lugar
Chunk : de la Mancha
de cuyo nombre
Chunk : no quiero acordarme

Warum?

Germán Bouzas
quelle
4
Sieht so aus, als wäre Java 8 \Rgierig, in 9 nicht.
Doublep
Von welcher Saite bekommst du System.getProperty("line.separator")?
Sergey Kalinichenko
2
@dasblinkenlight: Das sollte keine Rolle spielen; \Rist der Linebreak Matcher . Es wird mit allem übereinstimmen, was das OP dort hat.
Makoto
2
Wenn Sie diese Art von Frage stellen, sollten Sie die JDK-Versionsnummern angeben, da dies manchmal Fehler sind, die in Punktversionen behoben wurden und die Benutzer dann nicht replizieren können usw.
Sled
2
@doublep Ich bin mir nicht sicher, ob du es als gierig bezeichnen würdest, aber es ist nicht erlaubt, eine einzelne CR-LF-Sequenz beim Abgleich zurückzuverfolgen und in zwei Teile zu teilen \R, da es verboten ist, nur eine CR abzugleichen, wenn LF folgt. Eine andere Möglichkeit, dies auszudrücken, besteht darin, dass es nicht zurückverfolgt werden kann. Java 8 war korrekt; Soweit ich das beurteilen kann, stimmt Java 9 jetzt nicht mehr mit tr18 überein.
tchrist

Antworten:

48

Die Java-Dokumentation stimmt nicht mit dem Unicode-Standard überein. Der Javadoc nebelt, was \Rpassen soll. Es liest:

\R Jede Unicode-Zeilenumbruchsequenz entspricht \u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]

Diese Java-Dokumentation ist fehlerhaft. In seinem Abschnitt zu Zeilenumbrüchen R1.6 stellt der Unicode Technical Standard Nr. 18 für reguläre Ausdrücke klar fest:

Es wird dringend empfohlen, ein Metazeichen mit regulären Ausdrücken wie "\ R" zu verwenden, um alle oben aufgeführten Zeichen und Sequenzen am Zeilenende abzugleichen (z. B. in # 1). Dies würde etwas entsprechen, das dem folgenden Ausdruck entspricht. Dieser Ausdruck wird durch die Notwendigkeit, eine Sicherung zu vermeiden, etwas kompliziert.

 (?:\u{D A}|(?!\u{D A})[\u{A}-\u{D}\u{85}\u{2028}\u{2029}]

Mit anderen Worten, es kann nur eine CR + LF-Sequenz (Wagenrücklauf + Zeilenvorschub) mit zwei Codepunkten oder ein einzelner Codepunkt aus dieser Menge übereinstimmen , vorausgesetzt, es handelt sich nicht nur um einen Wagenrücklauf allein, dem dann ein Zeilenvorschub folgt . Das liegt daran, dass es nicht erlaubt ist, zu sichern . CRLF muss atomar sein, damit \Res richtig funktioniert.

Java 9 entspricht also nicht mehr den Empfehlungen von R1.6. Außerdem macht es jetzt etwas, was es in Java 8 NICHT tun sollte und nicht tat.

Es scheint an der Zeit zu sein, dass ich Sherman (sprich: Xueming Shen) wieder einen Schrei gebe. Ich habe schon früher mit ihm an diesen wichtigen Fragen der formalen Konformität gearbeitet.

tchrist
quelle
2
Eine Problemumgehung wäre also, entweder (?>\\R)oder \\R{1}+anstelle von \\Roder im speziellen Fall des OP \\R{2}+anstelle von zu verwenden \\R\\R. Interessanterweise sogar \\R{1}\\R{1}oder \\R{2}geben Sie das gewünschte Ergebnis unter Java 9, was inkonsistent ist, da der Nicht-Possessiv {n}das Back-Tracking nicht deaktivieren sollte.
Holger
Vielleicht kann dies mit JDK-8176983 behoben werden ?
Naman
@nullpointer kann mir jemand sagen, ob dies in Java 10 behoben wurde? Es sieht so aus, als ob der Javadoc immer noch das falsche "äquivalente" Muster hat, also ist zumindest das Dokument falsch, wenn nicht die Implementierung.
Patrick Parker
63

Es war ein Fehler in Java 8 und wurde behoben: JDK-8176029: "Linebreak Matcher entspricht nicht dem in Javadoc angegebenen Muster" .

Siehe auch: Negatives Aussehen von Java-8-Regex mit `\ R`

user158037
quelle
7
Interessant, für mich sieht Java 8-Verhalten vernünftiger aus. Obwohl es möglich ist, "\ r \ n" als zwei aufeinanderfolgende Zeilenumbrüche zu interpretieren, macht es meines Erachtens wenig Sinn. Wenn Sie zwei Zeilenumbrüche meinten, würden Sie "\ n \ n" oder "\ r \ n \ r \ n" usw. schreiben, dh zwei gleiche Zeilenumbrüche. "\ r \ n" sollte eigentlich nur eine bedeuten.
Doublep
2
Es ergibt Sinn!. Aber Java 8 hatte das Verhalten, das ich brauchte. mmmh.
Germán Bouzas
3
@ GermánBouzas: Ich denke, Sie müssten zuerst Zeilenumbrüche normalisieren, z. B. mit replaceAll ("\\R", "\\n")(nicht getestet, aber ich würde vermuten, dass Änderungen beim Zurückverfolgen hier keine Rolle spielen).
Doublep
9
Ich bin mir ziemlich sicher, dass dies ein Fehler ist. \Rsoll nicht zurückverfolgt werden können; Dafür gibt es gute Gründe. Ich werde sehen, was ich finden kann: Sie dürfen eine CRLF niemals in zwei Instanzen aufteilen oder \R.
Tchrist