ทำไมอาร์เรย์ [idx ++] + =“ a” เพิ่ม idx หนึ่งครั้งใน Java 8 แต่เพิ่มเป็นสองเท่าใน Java 9 และ 10


751

สำหรับความท้าทายนักกอล์ฟรหัสเพื่อน เขียนรหัสต่อไปนี้ :

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

เมื่อรันโค้ดนี้ใน Java 8 เราจะได้ผลลัพธ์ดังต่อไปนี้:

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 

เมื่อรันโค้ดนี้ใน Java 10 เราจะได้ผลลัพธ์ดังต่อไปนี้:

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 

การกำหนดหมายเลขโดยใช้ Java 10 ทั้งหมดเกิดอะไรขึ้นที่นี่ มันเป็นข้อบกพร่องใน Java 10 หรือไม่?

ติดตามจากความคิดเห็น:

  • ปัญหาปรากฏขึ้นเมื่อคอมไพล์ด้วย Java 9 หรือใหม่กว่า (เราพบใน Java 10) รวบรวมรหัสนี้บน Java 8 จากนั้นเรียกใช้ใน Java 9 หรือรุ่นที่ใหม่กว่ารวมถึงการเข้าถึง Java 11 ก่อนจะให้ผลลัพธ์ที่คาดหวัง
  • รหัสประเภทนี้ไม่ได้มาตรฐาน แต่ใช้ได้ตามข้อกำหนด มันถูกพบโดยKevin Cruijssenในการอภิปรายในการแข่งขันกอล์ฟดังนั้นพบกรณีการใช้งานแปลก ๆ
  • Didier Lพบว่าปัญหาสามารถทำซ้ำได้ด้วยรหัสที่เล็กลงและเข้าใจได้ง่ายขึ้น:

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

    ผลลัพธ์เมื่อรวบรวมใน Java 8:

    evaluated

    ผลลัพธ์เมื่อคอมไพล์ใน Java 9 และ 10:

    evaluated
    evaluated
    
  • ปัญหาที่ดูเหมือนว่าจะถูก จำกัด ให้ concatenation สตริงและผู้ประกอบการที่ได้รับมอบหมาย ( +=) มีการแสดงออกที่มีผลข้างเคียง (s) เป็นตัวถูกดำเนินการทางซ้ายเหมือนในarray[test()]+="a", array[ix++]+="a", หรือtest()[index]+="a" เมื่อต้องการเปิดใช้สตริงอย่างน้อยหนึ่งด้านข้างต้องมีประเภทtest().field+="a" Stringการพยายามทำซ้ำสิ่งนี้ในประเภทอื่นหรือการสร้างล้มเหลว


5
ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
Samuel Liew

13
@JollyJoker มัน จำกัด ให้+=ใช้กับStringการอ้างอิงทางอ้อม String[]ดังนั้นก่อนอาร์เรย์ของคุณจะต้องเป็น ปัญหาไม่ได้เกิดขึ้นกับint[], long[]และเพื่อน ๆ แต่ใช่คุณพูดถูก!
Olivier Grégoire

2
@ String[]OlivierGrégoireอาร์เรย์ไม่จำเป็นต้องเป็น ถ้าเป็นObject[]และคุณทำarray[expression] += "foo";มันก็เหมือนกัน แต่ใช่ว่ามันใช้ไม่ได้กับอาร์เรย์ดั้งเดิมเช่นนั้นจะต้องสามารถที่จะถืออ้างอิงจากประเภทString( Object[], CharSequence[], Comparable[], ... ), การจัดเก็บผลของสตริงการ concatenation
Holger

30
นี้ได้รับมอบหมายข้อผิดพลาดรหัสJDK-8204322
สจวร์ตมาร์ค

1
@ StuartMarks ขอบคุณ! นั่นรวมอยู่ในคำตอบ: ฉันอยากเก็บคำถามไว้ไม่ว่าจะเป็นเรื่องปกติหรือเป็นบั๊ก แม้ว่าเราอาจมีความชัดเจนมากขึ้นเกี่ยวกับ ID ของข้อบกพร่องในคำตอบ ฉันจะปรับมันทันที
Olivier Grégoire

คำตอบ:


625

นี่คือข้อผิดพลาดในการjavacเริ่มต้นจาก JDK 9 (ซึ่งทำให้มีการเปลี่ยนแปลงบางอย่างเกี่ยวกับการ concatenation สตริงซึ่งผมสงสัยว่าเป็นส่วนหนึ่งของปัญหาด้วย) ได้รับการยืนยันจากjavacทีมงานภายใต้ข้อผิดพลาดรหัส JDK-8204322 หากคุณดูที่ bytecode ที่เกี่ยวข้องสำหรับบรรทัด:

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

มันคือ:

  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

โดยที่สุดท้ายaaloadคือการโหลดจริงจากอาร์เรย์ อย่างไรก็ตามส่วนหนึ่ง

  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

ซึ่งประมาณสอดคล้องกับการแสดงออกarray[i++%size](ลบโหลดและเก็บจริง) อยู่ในนั้นสองครั้ง สิ่งนี้ไม่ถูกต้องตามที่ระบุในjls-15.26.2 :

นิพจน์มอบหมายสารประกอบของรูปแบบE1 op= E2เทียบเท่ากับE1 = (T) ((E1) op (E2))ที่Tเป็นประเภทของE1, ยกเว้นว่าE1มีการประเมินเพียงครั้งเดียว

ดังนั้นสำหรับการแสดงออกarray[i++%size] += i + " ";ส่วนที่array[i++%size]ควรประเมินเพียงครั้งเดียว แต่จะถูกประเมินสองครั้ง (หนึ่งครั้งสำหรับการโหลดและอีกครั้งสำหรับการจัดเก็บ)

ใช่แล้วนี่เป็นข้อผิดพลาด


การปรับปรุงบางอย่าง:

ข้อผิดพลาดได้รับการแก้ไขใน JDK 11 และจะมีพอร์ตด้านหลังเป็น JDK 10 (แต่ไม่ใช่ JDK 9 เนื่องจากไม่ได้รับการอัพเดตสาธารณะอีกต่อไป )

Aleksey Shipilev กล่าวถึงในหน้า JBS (และ @DidierL ในความคิดเห็นที่นี่):

วิธีแก้ปัญหา: รวบรวมด้วย -XDstringConcat=inline

ที่จะกลับไปใช้StringBuilderเพื่อทำการต่อข้อมูลและไม่มีข้อผิดพลาด


34
โดยวิธีการนี้จะใช้กับการแสดงออกทางด้านซ้ายทั้งหมดไม่เพียง แต่ดัชนีที่ให้การแสดงออกย่อย การแสดงออกนี้อาจซับซ้อนโดยพลการ ดูตัวอย่างIntStream.range(0, 10) .peek(System.out::println).boxed().toArray()[0] += "";...
Holger

9
@Holger test().field += "sth"ด้านซ้ายมือไม่จำเป็นที่จะต้องเกี่ยวข้องกับอาร์เรย์ปัญหายังเกิดขึ้นกับการที่เรียบง่าย
Didier L

44
ไม่ว่าจะเป็นเรื่องสำคัญพฤติกรรมจะพังอย่างน่ากลัว แต่การประเมินครั้งแรกสำหรับร้านค้าและอันดับที่สองสำหรับการโหลดดังนั้นarray[index++] += "x";จะอ่านจากarray[index+1]และเขียนถึงarray[index]...
Holger

5
@TheCoder ใช่ฉันคิดอย่างนั้น JDK 9 ไม่ใช่การสนับสนุนระยะยาว (LTS) JDK 8 เคยเป็นและ LTS รุ่นถัดไปคือ JDK 11 ดูที่นี่: oracle.com/technetwork/java/javase/eol-135779.htmlโปรดทราบว่าการอัพเดตสาธารณะเป็น JDK 9 สิ้นสุดลงในเดือนมีนาคม
Jorn Vernee

15
บน JDK-8204322, Aleksey Shipilev แนะนำให้คอมไพล์ด้วย-XDstringConcat=inlineเพื่อเป็นวิธีแก้ปัญหาสำหรับผู้ที่ต้องการมัน
Didier L
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.