Чому \ R поводиться по-різному у регулярних виразах між Java 8 та Java 9?


78

Наступний код компілюється як в Java 8, так і в 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);
        }
    }
}

Коли я запускаю його з Java 8, він повертає:

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

Але коли я запускаю його з Java 9, результат виходить іншим:

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

Чому?


4
Схоже, у Java 8 \Rжадібний, тоді як у 9 - ні.
дубль

З якої струни ви отримуєте System.getProperty("line.separator")?
Сергій Калініченко

2
@dasblinkenlight: Це не повинно мати значення; \R- це збіг рядків . Це буде відповідати тому, що там є ОП.
Макото

2
При розміщенні такого запитання варто включати номери версій JDK, тому що іноді це помилки, виправлені в точкових випусках, і тоді люди не можуть копіювати і т. Д.
Sled

2
@doublep Я не впевнений, що ти назвав би це ненажерливим, але забороняється робити зворотний шлях і розбивати одну послідовність CR LF при надходженні \R, оскільки це забороняє збігати лише CR, якщо є LF. Інший спосіб це виразити - це те, що він не може відступити. Java 8 була правильною; Java 9 зараз не відповідає tr18, наскільки я можу зрозуміти.
christ

Відповіді:


48

Документація Java не відповідає стандарту Unicode. Javadoc помилково приймає те, що \Rмає відповідати. Це говорить:

\R Будь-яка послідовність розбиття рядків Unicode еквівалентна \u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]

Ця документація Java є помилковою. У своєму розділі про розриви рядків R1.6 Технічний стандарт Unicode № 18 про регулярні вирази чітко зазначає:

Настійно рекомендується мати метасимвол регулярного виразу, такий як "\ R", для узгодження всіх символів, що закінчуються, і послідовностей, перелічених вище (наприклад, у №1). Це відповідало б чомусь еквівалентному наступному виразу. Цей вираз дещо ускладнюється необхідністю уникати резервного копіювання.

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

Іншими словами, він може співпадати лише з двома послідовностями CR + LF (повернення каретки + подача лінії) з кодовою точкою, або ж окрема кодова точка з цього набору за умови, що це не просто повернення каретки, за якою слідує подача лінії . Це тому, що це так резервне копіювання заборонено . CRLF повинен бути атомним,\Rщоб нормально функціонувати.

Отже, Java 9 більше не відповідає тому, що настійно рекомендує R1.6. Більше того, зараз він робить те, що НЕ повинен був робити і не робив, у Java 8.

Здається, мені пора знову дати Шерману (читай: Сюемін Шену) крик. Я вже працював із ним над цими дрібницями, що стосуються офіційної відповідності.


2
Отже, обхідним шляхом було б використання (?>\\R)або \\R{1}+замість \\R, або у конкретному випадку OP, використання \\R{2}+замість \\R\\R. Цікаво, що навіть \\R{1}\\R{1}або \\R{2}дайте бажаний результат під Java 9, що є непослідовним, оскільки неналежні {n}не повинні відключати зворотне відстеження.
Holger

Можливо, це можна виправити за допомогою JDK-8176983 ?
Naman,

@nullpointer може хто-небудь сказати мені, чи це було виправлено в Java 10? Схоже, javadoc все ще має неправильний "еквівалентний" шаблон, тому принаймні doc помилковий, якщо не імплементація.
Патрік Паркер,

63

7
Цікаво, що для мене поведінка Java 8 виглядає більш розумно. Незважаючи на те, що можна інтерпретувати "\ r \ n" як два послідовних розриви рядків, як я бачу, це мало сенсу. Якщо ви мали на увазі два розриви рядків, ви б написали "\ n \ n" або "\ r \ n \ r \ n" тощо, тобто два однакові розриви рядків. "\ r \ n" насправді має означати лише одне.
дубль

2
Це має сенс!. Але Java 8 мав поведінку, яка мені потрібна. ммм.
Герман Бузас,

3
@ GermánBouzas: Думаю, вам спочатку потрібно було б нормалізувати розриви рядків, наприклад, за допомогою replaceAll ("\\R", "\\n")(не тестували, але я вважаю, що зміни зворотного відстеження тут не зіграють жодної ролі).
дублер

9
Я майже впевнений, що це помилка. \Rне передбачається можливість повернення назад; на це є вагомі причини. Я побачу, що зможу знайти: ви ніколи не повинні розділяти CRLF на два екземпляри або \R.
christ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.