2017-12-18 160 views
76

Poniższy kod jest kompilowany zarówno w języku Java 8 & 9, ale zachowuje się inaczej.Dlaczego R zachowuje się inaczej w wyrażeniach regularnych między Java 8 i Java 9?

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); 
     } 
    } 
} 

Kiedy uruchamiam go z Java 8 zwraca:

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

Ale gdy uruchamiam go z Java 9 wyjście jest inna:

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

Dlaczego?

+3

Wygląda jak w Javie 8 '\ R' jest chciwy, podczas gdy w 9 nie jest. – doublep

+0

Jaki napis otrzymasz z 'System.getProperty (" line.separator ")'? – dasblinkenlight

+2

@dasblinkenlight: To nie powinno mieć znaczenia; '\ R' to [the linebreak matcher] (https://docs.oracle.com/javase/9/docs/api/java/util/regex/Pattern.html). Dopasuje to wszystko, co ma tam OP. – Makoto

Odpowiedz

46

Java documentation jest z zgodne ze standardem Unicode. Javadoc zamienia to, co powinno pasować do \R. To brzmi:

\R Każda sekwencja LINEBREAK Unicode, jest odpowiednikiem \u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]

Dokumentacja Java jest wadliwy. W swojej section on R1.6 Line Breaks, Unicode Technical Standard #18 on Regular Expressions wyraźnie stwierdza:

Zaleca się, że nie będzie wyrażenie regularne meta-znaków, takich jak „\ r”, do dopasowania wszystkich linię kończącą postacie i sekwencje wymienione powyżej (na przykład w # 1). Odpowiada to czemuś odpowiadającemu poniższemu wyrażeniu. Wyrażenie to jest nieco skomplikowane z powodu konieczności uniknięcia tworzenia kopii zapasowych.

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

Innymi słowy, tylko może być dopasowana do dwóch kodów punkt CR + LF (znak powrotu karetki + o wiersz) sekwencja albo pojedynczy kod punktu z tego zbioru, pod warunkiem że to jest tylko powrót karetki, po którym następuje wierszowanie. Dzieje się tak dlatego, że nie może wykonać kopii zapasowej. CRLF musi być atomowe, aby \R działał poprawnie.

Tak więc Java 9 nie jest już zgodna z zaleceniami R1.6. Co więcej, robi to teraz coś, czego NIE wolno było robić, a czego nie robił, w Javie 8.

Wygląda na to, że nadszedł czas, aby dać Shermanowi (czytaj: Xueming Shen) jeszcze raz. Pracowałem z nim wcześniej nad tymi drobnymi sprawami formalnej zgodności.

+1

Aby obejść, należy użyć '(?> \\ R)' lub '\\ R {1} +' zamiast '\\ R' lub w konkretnym przypadku OP, użyj' \\ R {2 } + 'zamiast' \\ R \\ R'. Co ciekawe, nawet '\\ R {1} \\ R {1}' lub '\\ R {2}' daje pożądany wynik w Javie 9, co jest niespójne, ponieważ nie-zaborcze '{n}' nie powinno wyłączyć śledzenie wstecz. – Holger

+0

Może to naprawić za pomocą [JDK-8176983] (https://bugs.openjdk.java.net/browse/JDK-8176983)? – nullpointer

63
+7

Ciekawe, dla mnie zachowanie Java 8 wygląda bardziej. Chociaż możliwe jest interpretowanie "\ r \ n" jako dwóch następujących po sobie linii, to nie ma większego sensu, jak widzę. Jeśli chodziło Ci o dwie linie podziału, napisałbyś "\ n \ n" lub "\ r \ n \ r \ n" itd., Tj. Dwa * same * linebreaks. "\ r \ n" powinno oznaczać tylko jedno. – doublep

+2

To ma sens! Ale java 8 miała takie zachowanie, jakiego potrzebowałem. mmmh. –

+3

@ GermánBouzas: Chyba najpierw trzeba znormalizować linie łamania, np. z 'replaceAll (" \\ R "," \\ n ")' (nie testowałem, ale domyślam się, że zmiany w pętli nie będą tu odgrywać żadnej roli). – doublep