บล็อกแบบคงที่ใน Java ไม่ได้ดำเนินการ


87
class Test {
    public static void main(String arg[]) {    
        System.out.println("**MAIN METHOD");
        System.out.println(Mno.VAL); // SOP(9090);
        System.out.println(Mno.VAL + 100); // SOP(9190);
    }

}

class Mno {
    final static int VAL = 9090;
    static {
        System.out.println("**STATIC BLOCK OF Mno\t: " + VAL);
    }
}

ฉันรู้ว่าstaticบล็อกทำงานเมื่อโหลดคลาส แต่ในกรณีนี้ตัวแปรอินสแตนซ์ภายในคลาสMnoคือfinalเนื่องจากว่าstaticบล็อกไม่ทำงาน

เหตุผลที่เป็นเช่นนั้น? แล้วถ้าจะเอาออกfinalมันจะใช้ได้ไหม

หน่วยความจำใดจะถูกจัดสรรก่อนstatic finalตัวแปรหรือstaticบล็อก?

หากเนื่องจากfinalตัวแก้ไขการเข้าถึงคลาสไม่ได้รับการโหลดตัวแปรจะรับหน่วยความจำได้อย่างไร?


1
ข้อผิดพลาดและข้อความที่คุณได้รับคืออะไร?
Patashu

@ Patashu ไม่มีข้อผิดพลาดมีข้อสงสัย
Sthita

คำตอบ:


135
  1. static final intฟิลด์เป็นค่าคงที่รวบรวมเวลาและค่าของมันจะ hardcoded ในชั้นเรียนปลายทางโดยไม่ต้องมีการอ้างอิงถึงแหล่งกำเนิดของตน
  2. ดังนั้นคลาสหลักของคุณจะไม่เรียกการโหลดคลาสที่มีฟิลด์
  3. ดังนั้นตัวเริ่มต้นแบบคงที่ในคลาสนั้นจะไม่ถูกเรียกใช้งาน

ในรายละเอียดเฉพาะ bytecode ที่คอมไพล์สอดคล้องกับสิ่งนี้:

public static void main(String arg[]){    
    System.out.println("**MAIN METHOD");
    System.out.println(9090)
    System.out.println(9190)
}

ทันทีที่คุณลบค่าfinalนั้นจะไม่ใช่ค่าคงที่เวลาคอมไพล์อีกต่อไปและจะใช้พฤติกรรมพิเศษที่อธิบายไว้ข้างต้นไม่ได้อีกต่อไป Mnoชั้นจะโหลดตามที่คุณคาดหวังและคงที่ initializer รัน


1
แต่แล้วจะประเมินค่าของตัวแปรสุดท้ายในคลาสโดยไม่โหลดคลาสได้อย่างไร?
Sumit Desai

18
การประเมินทั้งหมดเกิดขึ้นในเวลาคอมไพล์และผลลัพธ์สุดท้ายจะถูกเข้ารหัสในทุกตำแหน่งที่อ้างอิงตัวแปร
Marko Topolnik

1
ดังนั้นหากแทนที่จะเป็นตัวแปรดั้งเดิมมันเป็น Object บางตัวการเข้ารหัสดังกล่าวจะไม่สามารถทำได้ ไม่ใช่เหรอ? ดังนั้นในกรณีนี้คลาสนั้นจะถูกโหลดและบล็อกแบบคงที่จะถูกเรียกใช้งานหรือไม่?
Sumit Desai

2
Marko ข้อสงสัยของ Sumit ก็ถูกเช่นกันถ้าแทนที่จะเป็นแบบดั้งเดิมมันเป็น Object บางอย่างการเข้ารหัสจะไม่สามารถทำได้ ไม่ใช่เหรอ? ดังนั้นในกรณีนี้คลาสนั้นจะถูกโหลดและบล็อกแบบคงที่จะถูกเรียกใช้งานหรือไม่?
Sthita

8
@SumitDesai แน่นอนสิ่งนี้ใช้ได้กับค่าดั้งเดิมและตัวอักษรสตริงเท่านั้น สำหรับรายละเอียดทั้งหมดอ่านบทที่เกี่ยวข้องในข้อกำหนดภาษา Java
Marko Topolnik

8

เหตุผลที่ว่าทำไมชั้นจะไม่โหลดก็คือว่าVALเป็นfinal และมันจะเริ่มต้นใช้กับการแสดงออกคงที่ (9090) หากตรงตามเงื่อนไขทั้งสองนี้ค่าคงที่จะถูกประเมิน ณ เวลาคอมไพล์และ "ฮาร์ดโค้ด" เมื่อจำเป็น

เพื่อป้องกันไม่ให้นิพจน์ถูกประเมินในเวลาคอมไพล์ (และเพื่อให้ JVM โหลดคลาสของคุณ) คุณสามารถ:

  • ลบคำหลักสุดท้าย:

    static int VAL = 9090; //not a constant variable any more
    
  • หรือเปลี่ยนนิพจน์ด้านขวามือเป็นค่าที่ไม่คงที่ (แม้ว่าตัวแปรจะยังคงเป็นขั้นสุดท้าย):

    final static int VAL = getInt(); //not a constant expression any more
    static int getInt() { return 9090; }
    

5

หากคุณเห็นรหัส bytecode ที่สร้างขึ้นโดยใช้javap -v Test.classmain () จะเป็นดังนี้:

public static void main(java.lang.String[]) throws java.lang.Exception;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String **MAIN METHOD
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: sipush        9090
        14: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        17: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        20: sipush        9190
        23: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
        26: return        

คุณสามารถเห็นได้อย่างชัดเจนใน " 11: sipush 9090" ว่าค่าสุดท้ายแบบคงที่ถูกใช้โดยตรงเนื่องจาก Mno.VAL เป็นค่าคงที่ของเวลาคอมไพล์ ดังนั้นจึงไม่จำเป็นต้องโหลดคลาส Mno ดังนั้นบล็อกแบบคงที่ของ Mno จะไม่ถูกดำเนินการ

คุณสามารถดำเนินการบล็อกแบบคงที่โดยการโหลด Mno ด้วยตนเองดังต่อไปนี้:

class Test{
    public static void main(String arg[]) throws Exception {
        System.out.println("**MAIN METHOD");
        Class.forName("Mno");                 // Load Mno
        System.out.println(Mno.VAL);
        System.out.println(Mno.VAL+100);
    }

}

class Mno{
    final static int VAL=9090;
    static{
        System.out.println("**STATIC BLOCK OF Mno\t:"+VAL);
    }
}

1
  1. จริงๆแล้วคุณไม่ได้ขยายคลาส Mno นั้นดังนั้นเมื่อเริ่มการคอมไพล์มันจะสร้างค่าคงที่ของตัวแปร VAL และเมื่อการดำเนินการเริ่มต้นเมื่อตัวแปรนั้นต้องการโหลดจากหน่วยความจำ ดังนั้นจึงไม่จำเป็นต้องมีการอ้างอิงคลาสของคุณเพื่อที่จะไม่ดำเนินการ bock แบบคงที่

  2. ถ้าคลาสAขยายคลาสMnoบล็อกแบบคงที่จะรวมอยู่ในคลาสAถ้าคุณทำเช่นนี้บล็อกคงที่จะถูกเรียกใช้ ตัวอย่างเช่น..

    public class A extends Mno {
    
        public static void main(String arg[]){    
            System.out.println("**MAIN METHOD");
            System.out.println(Mno.VAL);//SOP(9090);
            System.out.println(Mno.VAL+100);//SOP(9190);
        }
    
    }
    
    class Mno {
        final static int VAL=9090;
        static {
            System.out.println("**STATIC BLOCK OF Mno\t:"+VAL);
        }
    }
    

0

เท่าที่ฉันรู้มันจะถูกดำเนินการตามลำดับที่ปรากฏ ตัวอย่างเช่น:

 public class Statique {
     public static final String value1 = init1();

     static {
         System.out.println("trace middle");
     }
     public static final String value2 = init2();


     public static String init1() {
         System.out.println("trace init1");
         return "1";
     }
     public static String init2() {
         System.out.println("trace init2");
         return "2";
     }
 }

จะพิมพ์

  trace init1
  trace middle
  trace init2

ฉันเพิ่งทดสอบและค่าสถิตถูกเริ่มต้น (=> พิมพ์) เมื่อคลาส "Statique" ถูกใช้จริงและ "ดำเนินการ" ในส่วนของโค้ดอื่น (กรณีของฉันฉันทำ "Statique ใหม่ ()"


2
คุณจะได้รับการส่งออกนี้เพราะคุณกำลังโหลดระดับโดยการทำStatique new Statique()ขณะอยู่ในคำถามที่ถามMnoคลาสจะไม่โหลดเลย
RAS

@ Fabyen ถ้าฉันกำลังสร้างวัตถุของ Mno ในคลาสทดสอบเช่นนี้: Mno Anc = New Mno (); ก็ดี แต่สถานการณ์ปัจจุบันฉันไม่ได้ทำอย่างนั้นข้อสงสัยของฉันคือถ้าฉันลบขั้นสุดท้ายบล็อกแบบคงที่จะดำเนินการได้ดีมิฉะนั้นจะไม่ทำงานทำไมเป็นอย่างนั้น ??
Sthita

1
ใช่คำตอบด้านล่างนี้สมบูรณ์แบบ ใน bytecode ของ Main.class (โดยใช้ Mno.VAL) พบว่า 9090 มีรหัสยาก ลบขั้นสุดท้ายคอมไพล์แล้วใช้ javap Main คุณจะเห็นgetstatic # 16; // สนาม Statique.VAL:ฉัน นำกลับมาสุดท้ายรวบรวมแล้วใช้ javap หลักคุณจะเห็นsipush 9090
Fabyen

1
เนื่องจากเป็นฮาร์ดโค้ดใน Main.class จึงไม่มีเหตุผลที่จะโหลดคลาส MNO ดังนั้นจึงไม่มีการกำหนดค่าเริ่มต้นแบบคงที่
Fabyen

สิ่งนี้ตอบคำถามที่สอง: "หน่วยความจำใดจะถูกจัดสรรก่อนตัวแปรสุดท้ายแบบคงที่หรือบล็อกคงที่" (ลำดับศัพท์)
Hauke ​​Ingmar Schmidt
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.