Neden Java 8'de split bazen sonuç dizisinin başlangıcında boş dizeleri kaldırır?


110

Java 8'den önce boş dizgeye böldüğümüzde

String[] tokens = "abc".split("");

bölünmüş mekanizma ile işaretlenmiş yerlerde bölünecekti |

|a|b|c|

çünkü ""her karakterden önce ve sonra boş alan vardır. Sonuç olarak, ilk başta bu diziyi üretecektir.

["", "a", "b", "c", ""]

ve daha sonra sondaki boş dizeleri kaldıracak (çünkü limitargümana açıkça negatif bir değer sağlamadık ), böylece sonunda geri dönecek

["", "a", "b", "c"]

Java 8'de bölünme mekanizması değişmiş gibi görünüyor. Şimdi kullandığımızda

"abc".split("")

["a", "b", "c"]Bunun yerine dizi alacağız , ["", "a", "b", "c"]böylece başlangıçta boş dizgeler kaldırılmış gibi görünüyor. Ancak bu teori başarısız olur çünkü örneğin

"abc".split("a")

Başlangıçta boş dizeyle dizi döndürür ["", "bc"].

Java 8'de burada neler olup bittiğini ve bölünme kurallarının nasıl değiştiğini birisi açıklayabilir mi?


Java8 bunu düzeltir gibi görünüyor. Bu arada s.split("(?!^)")işe yarıyor gibi görünüyor.
shkschneider

2
@shkschneider Sorumda açıklanan davranış, Java-8 öncesi sürümlerin bir hatası değildir. Bu davranış özellikle çok kullanışlı değildi, ancak yine de doğruydu (sorumda gösterildiği gibi), bu nedenle "düzeltildi" diyemeyiz. Kullandığımız bu yüzden ben daha çok gelişme gibi görüyorum split("")(normal ifadeler kullanabilirsiniz olmayan insanlar için) şifreli yerine split("(?!^)")veya split("(?<!^)")veya birkaç başka regexes.
Pshemo

1
Fedora 21'e yükseltildikten sonra aynı sorunla karşılaştım, fedora 21 JDK 1.8'e sahip gemiler ve bu nedenle IRC oyun uygulamam bozuldu.
LiuYan 刘 研

7
Bu soru, Java 8'deki bu kırılma değişikliğinin tek dokümantasyonu gibi görünüyor. Oracle, onu uyumsuzluklar listesinin dışında bıraktı .
Sean Van Gorder

4
JDK'daki bu değişiklik, neyin yanlış olduğunu bulmam için 2 saate mal oldu. Kod bilgisayarımda iyi çalışıyor (JDK8) ancak başka bir makinede (JDK7) gizemli bir şekilde başarısız oluyor. Oracle , en yaygın kullanım olduğundan, Pattern.split veya String.split (String regex, int limit) yerine String.split (String regex) belgelerini GERÇEKTEN güncellemelidir . Java, WORA olarak adlandırılan taşınabilirliği ile bilinir. Bu büyük bir geriye dönük değişikliktir ve hiç de iyi belgelenmemiştir.
PoweredByRice

Yanıtlar:


84

String.split(Hangi çağrıların Pattern.split) davranışı Java 7 ve Java 8 arasında değişir.

belgeleme

Belgelenmesi arasındaki karşılaştırılması Pattern.splitiçinde Java 7 ve Java 8 , aşağıdaki maddeyi gözlemlemek eklenmektedir:

Giriş dizisinin başlangıcında pozitif genişlikli bir eşleşme olduğunda, sonuç dizisinin başlangıcına boş bir ana alt dize eklenir. Başlangıçta sıfır genişlikli bir eşleşme, ancak hiçbir zaman bu kadar boş bir alt dize üretmez.

Aynı maddesi de eklenir String.splitolarak Java 8 ile karşılaştırıldığında, Java 7 .

Referans uygulaması

Pattern.splitJava 7 ve Java 8'deki referans uygulamasının kodunu karşılaştıralım. Kod, 7u40-b43 ve 8-b132 sürümleri için grepcode'dan alınır.

Java 7

public String[] split(CharSequence input, int limit) {
    int index = 0;
    boolean matchLimited = limit > 0;
    ArrayList<String> matchList = new ArrayList<>();
    Matcher m = matcher(input);

    // Add segments before each match found
    while(m.find()) {
        if (!matchLimited || matchList.size() < limit - 1) {
            String match = input.subSequence(index, m.start()).toString();
            matchList.add(match);
            index = m.end();
        } else if (matchList.size() == limit - 1) { // last one
            String match = input.subSequence(index,
                                             input.length()).toString();
            matchList.add(match);
            index = m.end();
        }
    }

    // If no match was found, return this
    if (index == 0)
        return new String[] {input.toString()};

    // Add remaining segment
    if (!matchLimited || matchList.size() < limit)
        matchList.add(input.subSequence(index, input.length()).toString());

    // Construct result
    int resultSize = matchList.size();
    if (limit == 0)
        while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
            resultSize--;
    String[] result = new String[resultSize];
    return matchList.subList(0, resultSize).toArray(result);
}

Java 8

public String[] split(CharSequence input, int limit) {
    int index = 0;
    boolean matchLimited = limit > 0;
    ArrayList<String> matchList = new ArrayList<>();
    Matcher m = matcher(input);

    // Add segments before each match found
    while(m.find()) {
        if (!matchLimited || matchList.size() < limit - 1) {
            if (index == 0 && index == m.start() && m.start() == m.end()) {
                // no empty leading substring included for zero-width match
                // at the beginning of the input char sequence.
                continue;
            }
            String match = input.subSequence(index, m.start()).toString();
            matchList.add(match);
            index = m.end();
        } else if (matchList.size() == limit - 1) { // last one
            String match = input.subSequence(index,
                                             input.length()).toString();
            matchList.add(match);
            index = m.end();
        }
    }

    // If no match was found, return this
    if (index == 0)
        return new String[] {input.toString()};

    // Add remaining segment
    if (!matchLimited || matchList.size() < limit)
        matchList.add(input.subSequence(index, input.length()).toString());

    // Construct result
    int resultSize = matchList.size();
    if (limit == 0)
        while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
            resultSize--;
    String[] result = new String[resultSize];
    return matchList.subList(0, resultSize).toArray(result);
}

Java 8'de aşağıdaki kodun eklenmesi, yukarıdaki davranışı açıklayan giriş dizesinin başlangıcındaki sıfır uzunluklu eşleşmeyi hariç tutar.

            if (index == 0 && index == m.start() && m.start() == m.end()) {
                // no empty leading substring included for zero-width match
                // at the beginning of the input char sequence.
                continue;
            }

Uyumluluğun korunması

Java 8 ve sonraki sürümlerde aşağıdaki davranış

splitSürümler arasında tutarlı ve Java 8'deki davranışla uyumlu davranmak için :

  1. Senin regex Eğer edebilirsiniz sıfır uzunluklu dize maç sadece eklemek (?!\A)de sonuna regex ve dışı yakalama grubundaki orijinal regex sarın (?:...)(gerekirse).
  2. Senin regex Eğer edemez sıfır uzunluklu dize maç hiçbir şey yapmanız gerekmez.
  3. Normal ifadenin sıfır uzunluklu dizeyle eşleşip eşleşemeyeceğini bilmiyorsanız, 1. adımdaki her iki işlemi de yapın.

(?!\A) dizenin dizenin başında bitmediğini kontrol eder, bu da eşleşmenin dizenin başında boş bir eşleşme olduğunu gösterir.

Java 7 ve önceki sürümlerde aşağıdaki davranış

splitJava 7 ile geriye dönük uyumlu hale getirmek için genel bir çözüm yoktur ve daha önce, splitkendi özel uygulamanıza işaret etmek için tüm örnekleri değiştirmenin kısası .


split("")Farklı java sürümlerinde tutarlı olması için kodu nasıl değiştirebileceğime dair bir fikriniz var mı?
Daniel

2
@Daniel: O ileriye uyumlu hale ekleyerek (Java 8 davranışını izleyin) mümkündür (?!^)için sonuna regex ve olmayan yakalayan grupta orijinal regex sarın (?:...)(gerekirse), ama herhangi bir düşünemiyorum geriye dönük uyumlu hale getirmenin yolu (Java 7 ve önceki sürümlerdeki eski davranışı izleyin).
nhahtdh

Açıklama için teşekkürler. Eğer tarif edebilir "(?!^)"? Hangi senaryolarda farklı olacak ""? (Normal ifadede berbatım!: - /).
Daniel

1
@Daniel: Anlamı Pattern.MULTILINEbayraktan etkilenirken \A, bayraklardan bağımsız olarak her zaman dizenin başında eşleşir.
nhahtdh

30

Bu, belgelerinde belirtilmiştir split(String regex, limit).

Bu dizenin başında pozitif genişlikli bir eşleşme olduğunda, sonuç dizisinin başına boş bir ana alt dize eklenir. Başlangıçta sıfır genişlikli bir eşleşme, ancak hiçbir zaman bu kadar boş bir alt dize üretmez.

İçinde "abc".split("") sıfır genişlikli bir eşleşme elde edersiniz, bu nedenle baştaki boş alt dize sonuç dizisine dahil edilmez.

Ancak ikinci pasajınızda bölündüğünüzde "a" pozitif bir genişlik eşleşmesi elde ettiniz (bu durumda 1), bu nedenle boş öndeki alt dize beklendiği gibi dahil edilir.

(Alakasız kaynak kodu kaldırıldı)


3
Bu sadece bir soru. JDK'dan bir kod parçası göndermek uygun mudur? Google - Harry Potter - Oracle ile ilgili telif hakkı sorununu hatırlıyor musunuz?
Paul Vargas

6
@PaulVargas Adil olmak gerekirse bilmiyorum ama JDK'yı indirebileceğiniz ve tüm kaynakları içeren src dosyasını açabileceğiniz için sorun olmadığını varsayıyorum. Yani teknik olarak herkes kaynağı görebiliyordu.
Alexis C.

12
@PaulVargas "Açık kaynak" daki "açık" bir şeyi ifade ediyor.
Marko Topolnik

2
@ZouZou: Herkesin görebilmesi onu yeniden yayınlayabileceğiniz anlamına gelmez
user102008

2
@Paul Vargas, IANAL, ancak diğer birçok durumda bu tür bir gönderi, alıntı / adil kullanım durumuna girer. Konuyla ilgili daha fazla bilgi burada: meta.stackexchange.com/questions/12527/…
Alex Pakka

14

split()Java 7'den Java 8'e ilişkin belgelerde küçük bir değişiklik oldu . Özellikle aşağıdaki ifade eklendi:

Bu dizenin başında pozitif genişlikli bir eşleşme olduğunda, sonuç dizisinin başına boş bir ana alt dize eklenir. Başlangıçta sıfır genişlikli eşleşme, ancak hiçbir zaman bu kadar boş bir alt dize üretmez.

(vurgu benim)

Boş dize bölünmesi, başlangıçta sıfır genişlikli bir eşleşme oluşturur, bu nedenle, yukarıda belirtilenlere uygun olarak sonuçtaki dizinin başlangıcına boş bir dize dahil edilmez. Buna karşılık, bölünen ikinci örneğiniz dizenin başlangıcında pozitif- genişlikte bir eşleşme "a"oluşturur , bu nedenle sonuçtaki dizinin başına aslında boş bir dize eklenir.


Birkaç saniye daha fark yarattı.
Paul Vargas

2
@PaulVargas aslında burada arshajii Zouzou önce cevap birkaç saniye yayınlanmıştır, ama ne yazık Zouzou önceki soruma cevap burada . Zaten bir cevabı bildiğim için bu soruyu sormalı mıyım diye merak ediyordum ama ilginç görünüyordu ve ZouZou daha önceki yorumuyla biraz ün hak ediyordu.
Pshemo

5
Yeni davranış daha mantıklı görünmesine rağmen , açıkça geriye dönük bir uyumluluk kırılmasıdır . Bu değişikliğin tek gerekçesi "some-string".split("")oldukça nadir bir durum olmasıdır.
ivstas

4
.split("")hiçbir şeyi eşleştirmeden bölmenin tek yolu değildir. Başlangıçta da eşleşen ve şimdi gitmiş olan boş bir kafa öğesi üreten jdk7'de pozitif bir önden okuma normal ifadesi kullandık. github.com/spray/spray/commit/…
jrudolph
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.