[İdx ++] + = “a” dizisi neden IDx'i Java 8'de bir kez, Java 9 ve 10'da iki kez artırır?


751

Bir meydan okuma için, bir kod golfçüsü aşağıdaki kodu yazdı :

import java.util.*;
public class Main {
  public static void main(String[] args) {
    int size = 3;
    String[] array = new String[size];
    Arrays.fill(array, "");
    for(int i = 0; i <= 100; ) {
      array[i++%size] += i + " ";
    }
    for(String element: array) {
      System.out.println(element);
    }
  }
}

Bu kodu Java 8'de çalıştırdığınızda, aşağıdaki sonucu elde ederiz:

1 4 7 10 13 16 19 22 25 28 31 34 37 40 43 46 49 52 55 58 61 64 67 70 73 76 79 82 85 88 91 94 97 100 
2 5 8 11 14 17 20 23 26 29 32 35 38 41 44 47 50 53 56 59 62 65 68 71 74 77 80 83 86 89 92 95 98 101 
3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63 66 69 72 75 78 81 84 87 90 93 96 99 

Bu kodu Java 10'da çalıştırdığınızda, aşağıdaki sonucu elde ederiz:

2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 
2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 100 102 
2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 100 

Numaralandırma tamamen Java 10 kullanılarak kapalı. Peki burada neler oluyor? Java 10'da bir hata mı?

Yorumlardan takip edin:

  • Sorun, Java 9 veya üstü ile derlendiğinde ortaya çıkar (Java 10'da bulduk). Bu kodu Java 8'de derlemek, ardından Java 9'da veya Java 11 erken erişim de dahil olmak üzere daha sonraki bir sürümde çalıştırmak beklenen sonucu verir.
  • Bu tür bir kod standart değildir, ancak spesifikasyonlara göre geçerlidir. Bu tarafından bulundu Kevin Cruijssen bir de bir tartışma golf meydan , karşılaşılan dolayısıyla tuhaf kullanım durumunda.
  • Didier L , sorunun çok daha küçük ve daha anlaşılır bir kodla yeniden üretilebileceğini öğrendi:

    class Main {
      public static void main(String[] args) {
        String[] array = { "" };
        array[test()] += "a";
      }
      static int test() {
        System.out.println("evaluated");
        return 0;
      }
    }

    Java 8'de derlendiğinde sonuç:

    evaluated

    Java 9 ve 10'da derlendiğinde sonuç:

    evaluated
    evaluated
  • Sorun dizisi birleştirme ve atama operatörü (sınırlı gibi görünüyor +=gibi, sol işlenen olarak yan etki (s) sahip bir ifade ile) array[test()]+="a", array[ix++]+="a", test()[index]+="a", veya test().field+="a". Dize birleştirmeyi etkinleştirmek için kenarlardan en az birinin türü olmalıdır String. Bunu başka türlerde veya yapılarda çoğaltmaya çalışmak başarısız oldu.


5
Yorumlar uzun tartışmalar için değildir; bu sohbet sohbete taşındı .
Samuel Liew

13
@JollyJoker +=Dolaylı Stringreferanslara uygulanması sınırlıdır . Bu yüzden önce diziniz bir olmalıdır String[]. Sorun int[], long[]ve arkadaşlarıyla oluşmuyor . Ama evet, temelde haklısın!
Olivier Grégoire

2
@ OlivierGrégoire dizinin olması gerekmez String[]. Eğer öyleyse Object[]ve yaparsanız array[expression] += "foo";, aynıdır. Değil, yazın başvuruları tutmak gerekir Ama evet, bu, ilkel diziler için geçerli değildir String( Object[], CharSequence[], Comparable[], ...), dize birleştirme sonucunu depolamak için.
Holger

30
Buna JDK-8204322 hata kimliği atandı .
Stuart Marks

1
@StuartMarks teşekkürler! Bu, cevaba entegre edildi: Sorunun normal mi yoksa bir hata mı olduğu hakkında gerçekten bir soru tutmak istedim. Yine de, yanıttaki hatanın kimliği hakkında daha açık olabiliriz. Hemen uyarlayacağım.
Olivier Grégoire

Yanıtlar:


625

Bu, JDK-8204322 hata kimliği altında ekip tarafından onaylandığı gibijavac JDK 9'dan (sorunun bir parçası olduğundan şüphelendiğim dize birleştirme ile ilgili bazı değişiklikler yaptı ) başlayan bir hatadır . Satırın ilgili bayt koduna bakarsanız:javac

array[i++%size] += i + " ";

Bu:

  21: aload_2
  22: iload_3
  23: iinc          3, 1
  26: iload_1
  27: irem
  28: aload_2
  29: iload_3
  30: iinc          3, 1
  33: iload_1
  34: irem
  35: aaload
  36: iload_3
  37: invokedynamic #5,  0 // makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
  42: aastore

Sonuncusu aaloaddizideki gerçek yüktür. Ancak, bölüm

  21: aload_2             // load the array reference
  22: iload_3             // load 'i'
  23: iinc          3, 1  // increment 'i' (doesn't affect the loaded value)
  26: iload_1             // load 'size'
  27: irem                // compute the remainder

Kabaca ifadeye karşılık gelen array[i++%size](gerçek yük ve mağaza eksi), orada iki kez bulunur. Spesifikasyonun jls-15.26.2'de söylediği gibi bu yanlıştır :

Şeklinde olan bir bileşik atama ifade E1 op= E2eşdeğerdir E1 = (T) ((E1) op (E2)), burada Ttürüdür E1, dışında E1sadece bir kere elde edildi.

Dolayısıyla, ifade için array[i++%size] += i + " ";, parça array[i++%size]sadece bir kez değerlendirilmelidir. Ancak iki kez değerlendirilir (bir kez yük için ve bir kez mağaza için).

Evet, bu bir hata.


Bazı güncellemeler:

Hata JDK 11'de düzeltildi ve JDK 10 için bir arka bağlantı noktası olacak (ancak artık genel güncellemeleri almadığı için JDK 9 değil ).

Aleksey Shipilev, JBS sayfasında (ve buradaki yorumlarda @DidierL) bahsediyor :

Geçici çözüm: ile derleyin -XDstringConcat=inline

Bu StringBuilder, birleştirmeyi yapmak için kullanmaya geri döner ve hata yoktur.


34
Bu arada, bu yalnızca alt ifade sağlayan dizin için değil, tüm sol taraftaki ifade için geçerlidir. Bu ifade keyfi olarak karmaşık olabilir. Örneğin bakınızIntStream.range(0, 10) .peek(System.out::println).boxed().toArray()[0] += "";
Holger

9
@Holger Sol tarafın dizileri içermesine bile gerek yoktur, sorun aynı zamanda basit bir test().field += "sth" .
Didier L

44
Önemli değil, yine de davranış korkunç bir şekilde kırıldı, ancak ilk değerlendirme mağaza için ve ikincisi yük için, bu yüzden array[index++] += "x";okuyacak array[index+1]vearray[index]
Holger

5
@TheCoder Evet sanırım. JDK 9 uzun vadeli bir destek (LTS) sürümü değildir. JDK 8 idi ve bir sonraki LTS sürümü JDK 11'dir. Buraya bakın: oracle.com/technetwork/java/javase/eol-135779.html JDK 9'daki genel güncellemelerin Mart ayında sona erdiğine dikkat edin.
Jorn Vernee

15
JDK-8204322'de Aleksey Shipilev -XDstringConcat=inline, ihtiyaç duyanlar için geçici bir çözüm olarak derlenmesini önerdi .
Didier L
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.