ความแตกต่างระหว่างบล็อกรหัสเริ่มต้นคงที่และไม่คงที่คืออะไร


357

คำถามของฉันเกี่ยวกับการใช้คำหลักคงที่หนึ่งรายการ เป็นไปได้ที่จะใช้staticคีย์เวิร์ดเพื่อปกปิด code block ภายในคลาสที่ไม่ได้เป็นของฟังก์ชันใด ๆ ตัวอย่างเช่นการรวบรวมรหัสต่อไปนี้:

public class Test {
    private static final int a;    
    static {
        a = 5;
        doSomething(a);
    }
    private static int doSomething(int x) {
        return (x+5);
    }
}

หากคุณลบstaticคำหลักมันบ่นเพราะตัวแปรคือa finalอย่างไรก็ตามมันเป็นไปได้ที่จะลบทั้งสองfinalและstaticคำหลักและทำให้มันรวบรวม

มันทำให้ฉันสับสนทั้งสองทาง ฉันควรจะมีส่วนรหัสที่ไม่ได้เป็นของวิธีการใด ๆ ? เป็นไปได้อย่างไรที่จะเรียกใช้มันได้? โดยทั่วไปแล้วจุดประสงค์ของการใช้งานนี้คืออะไร? หรือดีกว่าฉันจะหาเอกสารเกี่ยวกับเรื่องนี้ได้จากที่ไหน

คำตอบ:


403

บล็อกรหัสที่มีตัวแก้ไขแบบคงที่หมายถึงการเริ่มต้นเรียน ; การบล็อกโค้ดเป็นอินสแตนซ์ของเครื่องมือเริ่มต้น

คลาส initializers จะถูกดำเนินการตามลำดับที่กำหนดไว้ (จากบนลงล่างเช่นเดียวกับตัวแปรเริ่มต้นอย่างง่าย) เมื่อโหลดคลาส (จริง ๆ แล้วเมื่อแก้ไขแล้ว แต่เป็นลักษณะเชิงเทคนิค)

เริ่มต้นอินสแตนซ์จะดำเนินการตามลำดับที่กำหนดเมื่อชั้นเรียนเป็นอินสแตนซ์ทันทีก่อนที่รหัสคอนสตรัคจะถูกดำเนินการทันทีหลังจากการภาวนาของตัวสร้าง super

ถ้าคุณเอาstaticจากint aมันจะกลายเป็นตัวแปรเช่นที่คุณจะไม่สามารถเข้าถึงได้จากแบบคงที่การเริ่มต้นบล็อก สิ่งนี้จะล้มเหลวในการคอมไพล์ด้วยข้อผิดพลาด "ตัวแปรไม่คงที่และไม่สามารถอ้างอิงได้จากบริบทคงที่"

หากคุณลบออกstaticจากบล็อกการเริ่มต้นมันก็จะกลายเป็นอินสแตนซ์เริ่มต้นและเพื่อint aเริ่มต้นที่การก่อสร้าง


static initializer ถูกเรียกใช้จริงในภายหลังเมื่อคลาสเริ่มต้นหลังจากโหลดและเชื่อมโยงแล้ว ที่เกิดขึ้นเมื่อคุณยกตัวอย่างวัตถุของคลาสหรือเข้าถึงตัวแปรหรือวิธีการคงที่ในชั้นเรียน ในความเป็นจริงถ้าคุณมีระดับกับการเริ่มต้นคงที่และวิธีการที่ถ้าคุณดำเนินการpublic static void staticMethod(){} TestStatic.class.getMethod("staticMethod");ตัวเริ่มต้นคงที่จะไม่ถูกเรียกใช้ ข้อมูลเพิ่มเติมที่นี่docs.oracle.com/javase/specs/jvms/se10/html/…
Totò

@ Totò: ใช่นั่นคือสิ่งที่ความละเอียดของชั้นเรียน (อย่างน้อยพวกเขาเคยอ้างถึงว่าเป็นลิงค์ + init เป็น "การแก้ปัญหา" ย้อนกลับไปเมื่อไหร่) ฉันไม่แปลกใจที่คุณสามารถใช้การไตร่ตรองเพื่อค้นหาสิ่งต่าง ๆเกี่ยวกับชั้นเรียนโดยไม่ได้รับการแก้ไข
Lawrence Dol

166

uff! static initializer คืออะไร

static initializer เป็นstatic {}บล็อกของรหัสภายในคลาส java และเรียกใช้เพียงครั้งเดียวก่อนที่ตัวสร้างหรือวิธีการหลักที่เรียกว่า

ตกลง! บอกรายละเอียดฉันเพิ่มเตืม...

  • เป็นบล็อคของรหัสstatic { ... }ในคลาส Java ใด ๆ และดำเนินการโดยเครื่องเสมือนเมื่อมีการเรียกคลาส
  • ไม่มีreturnคำสั่งที่รองรับ
  • ไม่สนับสนุนข้อโต้แย้ง
  • ไม่มีthisหรือsuperได้รับการสนับสนุน

อืมฉันจะใช้มันได้ที่ไหน?

สามารถใช้ได้ทุกที่ที่คุณรู้สึกว่าโอเค :) แต่ฉันเห็นเวลาส่วนใหญ่ที่ใช้เมื่อทำการเชื่อมต่อฐานข้อมูล, API init, การบันทึกและอื่น ๆ

อย่าเห่าเหรอ! ตัวอย่างอยู่ที่ไหน

package com.example.learnjava;

import java.util.ArrayList;

public class Fruit {

    static {
        System.out.println("Inside Static Initializer.");

        // fruits array
        ArrayList<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Orange");
        fruits.add("Pear");

        // print fruits
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
        System.out.println("End Static Initializer.\n");
    }

    public static void main(String[] args) {
        System.out.println("Inside Main Method.");
    }
}

เอาท์พุท ???

Inside Initializer แบบคงที่

แอปเปิ้ล

ส้ม

ลูกแพร์

สิ้นสุด Initializer เริ่มต้น

ภายในวิธีการหลัก

หวังว่านี่จะช่วยได้!


ขอบคุณ Madan! สามารถบล็อกคงนำมาใช้แทนafterPropertiesSet()ของInitializingBean?
Alexander Suraphel

3
ใช่คุณสามารถ! Static initializer จะถูกเรียกใช้เมื่อคลาสได้รับการโหลดโดย jvm ดังนั้นมันจึงเป็นเฟสแรกที่โค้ดถูกเรียกใช้งาน หากคุณมีคอนสตรัคเตอร์เช่นกันคำสั่งจะเป็น: initializer แบบคงที่, ตัวสร้าง, afterPropertiesSet
Martin Baumgartner

57

staticบล็อกคือ "การเริ่มต้นคงที่"

มันถูกเรียกใช้โดยอัตโนมัติเมื่อโหลดคลาสและไม่มีวิธีอื่นในการเรียกใช้ (ไม่ใช่แม้แต่ผ่าน Reflection)

ฉันใช้เป็นการส่วนตัวเมื่อเขียนรหัส JNI:

class JNIGlue {
    static {
        System.loadLibrary("foo");
    }
}

6
ไม่, ไม่มีวิธีที่ชัดเจนในการเรียกมัน, initializer คลาสไม่เคยถูกแสดงด้วยMethodอินสแตนซ์ แต่ถูกเรียกใช้โดยเครื่องเสมือน Java เท่านั้น
Rafael Winterhalter

46

นี่คือโดยตรงจากhttp://www.programcreek.com/2011/10/java-class-instance-initializers/

1. คำสั่งดำเนินการ

ดูคลาสต่อไปนี้คุณรู้หรือไม่ว่าอันไหนที่ถูกประหารชีวิตก่อน?

public class Foo {

    //instance variable initializer
    String s = "abc";

    //constructor
    public Foo() {
        System.out.println("constructor called");
    }

    //static initializer
    static {
        System.out.println("static initializer called");
    }

    //instance initializer
    {
        System.out.println("instance initializer called");
    }

    public static void main(String[] args) {
        new Foo();
        new Foo();
    }
}

เอาท์พุท:

initializer คงที่เรียกว่า

เริ่มต้นอินสแตนซ์ที่เรียกว่า

คอนสตรัคที่เรียกว่า

เริ่มต้นอินสแตนซ์ที่เรียกว่า

คอนสตรัคที่เรียกว่า

2. initializer ของอินสแตนซ์ Java ทำงานอย่างไร

initializer อินสแตนซ์ด้านบนมีคำสั่ง println b = 0เพื่อให้เข้าใจถึงวิธีการทำงานเราสามารถรักษามันเป็นคำสั่งที่กำหนดตัวแปรเช่น สิ่งนี้สามารถทำให้เข้าใจได้ชัดเจนยิ่งขึ้น

แทน

int b = 0คุณสามารถเขียน

int b;
b = 0;

ดังนั้นการเริ่มต้นอินสแตนซ์และตัวแปรเริ่มต้นของอินสแตนซ์จะเหมือนกันมาก

3. initializers อินสแตนซ์มีประโยชน์เมื่อใด

การใช้อินสแตนซ์ initializers นั้นหายาก แต่ก็ยังเป็นทางเลือกที่มีประโยชน์สำหรับอินสแตนซ์ตัวแปร initializers หาก:

  1. รหัส Initializer ต้องจัดการกับข้อยกเว้น
  2. ทำการคำนวณที่ไม่สามารถแสดงพร้อมกับ initializer ตัวแปรอินสแตนซ์

แน่นอนรหัสดังกล่าวสามารถเขียนในการก่อสร้าง แต่ถ้าคลาสมีคอนสตรัคเตอร์หลายตัวคุณจะต้องทำซ้ำรหัสในคอนสตรัคเตอร์แต่ละตัว

ด้วย initializer อินสแตนซ์คุณสามารถเขียนโค้ดเพียงครั้งเดียวและมันจะถูกดำเนินการไม่ว่าตัวสร้างใดที่จะใช้ในการสร้างวัตถุ (ฉันเดาว่านี่เป็นเพียงแนวคิดและไม่ได้ใช้บ่อย)

อีกกรณีที่อินสแตนซ์ initializers นั้นมีประโยชน์คือคลาสภายในที่ไม่ระบุชื่อซึ่งไม่สามารถประกาศตัวสร้างใด ๆ ได้เลย (นี่จะเป็นสถานที่ที่ดีในการวางฟังก์ชั่นการบันทึกหรือไม่)

ขอบคุณ Derhein

โปรดทราบด้วยว่าคลาส Anonymous ที่ใช้อินเตอร์เฟส [1] ไม่มี Constructor ดังนั้นจำเป็นต้องมี initializers อินสแตนซ์เพื่อดำเนินการนิพจน์ทุกชนิดที่เวลาก่อสร้าง


12

"final" รับประกันว่าตัวแปรจะต้องเริ่มต้นก่อนที่จะสิ้นสุดรหัส initializer วัตถุ ในทำนองเดียวกัน "คงสุดท้าย" รับประกันว่าตัวแปรจะเริ่มต้นได้เมื่อสิ้นสุดการเริ่มต้นรหัสชั้นเรียน การข้าม "คงที่" จากรหัสการเริ่มต้นของคุณจะเปลี่ยนเป็นรหัสการเริ่มต้นวัตถุ ดังนั้นตัวแปรของคุณจึงไม่เป็นที่น่าพอใจอีกต่อไป


8

คุณจะไม่เขียนโค้ดลงในบล็อกแบบสแตติกซึ่งจำเป็นต้องเรียกใช้จากที่ใดก็ได้ในโปรแกรมของคุณ หากวัตถุประสงค์ของรหัสคือการเรียกใช้จากนั้นคุณจะต้องวางไว้ในวิธีการ

คุณสามารถเขียนบล็อก initializer แบบสแตติกเพื่อเริ่มต้นตัวแปรแบบสแตติกเมื่อโหลดคลาส แต่รหัสนี้อาจมีความซับซ้อนมากขึ้น ..

บล็อก initializer แบบสแตติกดูเหมือนว่าวิธีที่ไม่มีชื่อไม่มีข้อโต้แย้งและไม่มีชนิดส่งคืน เนื่องจากคุณไม่เคยเรียกมันไม่จำเป็นต้องมีชื่อ ครั้งเดียวที่เรียกว่าคือเมื่อเครื่องเสมือนโหลดคลาส


6

เมื่อนักพัฒนาใช้บล็อก initializer, Java Compiler คัดลอก initializer ลงในแต่ละคอนสตรัคเตอร์ของคลาสปัจจุบัน

ตัวอย่าง:

รหัสต่อไปนี้:

class MyClass {

    private int myField = 3;
    {
        myField = myField + 2;
        //myField is worth 5 for all instance
    }

    public MyClass() {
        myField = myField * 4;
        //myField is worth 20 for all instance initialized with this construtor
    }

    public MyClass(int _myParam) {
        if (_myParam > 0) {
            myField = myField * 4;
            //myField is worth 20 for all instance initialized with this construtor
            //if _myParam is greater than 0
        } else {
            myField = myField + 5;
            //myField is worth 10 for all instance initialized with this construtor
            //if _myParam is lower than 0 or if _myParam is worth 0
        }
    }

    public void setMyField(int _myField) {
        myField = _myField;
    }


    public int getMyField() {
        return myField;
    }
}

public class MainClass{

    public static void main(String[] args) {
        MyClass myFirstInstance_ = new MyClass();
        System.out.println(myFirstInstance_.getMyField());//20
        MyClass mySecondInstance_ = new MyClass(1);
        System.out.println(mySecondInstance_.getMyField());//20
        MyClass myThirdInstance_ = new MyClass(-1);
        System.out.println(myThirdInstance_.getMyField());//10
    }
}

เทียบเท่ากับ:

class MyClass {

    private int myField = 3;

    public MyClass() {
        myField = myField + 2;
        myField = myField * 4;
        //myField is worth 20 for all instance initialized with this construtor
    }

    public MyClass(int _myParam) {
        myField = myField + 2;
        if (_myParam > 0) {
            myField = myField * 4;
            //myField is worth 20 for all instance initialized with this construtor
            //if _myParam is greater than 0
        } else {
            myField = myField + 5;
            //myField is worth 10 for all instance initialized with this construtor
            //if _myParam is lower than 0 or if _myParam is worth 0
        }
    }

    public void setMyField(int _myField) {
        myField = _myField;
    }


    public int getMyField() {
        return myField;
    }
}

public class MainClass{

    public static void main(String[] args) {
        MyClass myFirstInstance_ = new MyClass();
        System.out.println(myFirstInstance_.getMyField());//20
        MyClass mySecondInstance_ = new MyClass(1);
        System.out.println(mySecondInstance_.getMyField());//20
        MyClass myThirdInstance_ = new MyClass(-1);
        System.out.println(myThirdInstance_.getMyField());//10
    }
}

ฉันหวังว่านักพัฒนาเข้าใจตัวอย่างของฉัน


4

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


เป็นการติดตามถ้าฉันไม่ได้สร้างอินสแตนซ์ของวัตถุ แต่ฉันเรียกฟังก์ชันคงที่สาธารณะแทน มันแปลว่าบล็อคนี้รับประกันว่าจะทำงานก่อนการเรียกฟังก์ชั่นสาธารณะหรือไม่? ขอบคุณ
Szere Dyeri

หากคุณเรียกใช้ฟังก์ชันสแตติกสาธารณะของคลาสคลาสนั้นจะต้องโหลดก่อนดังนั้นใช่ตัวเริ่มต้นสแตติกจะดำเนินการก่อน
พอลทอมบลิน

ยกเว้นว่าเป็นคลาสเริ่มต้นที่ (ทางอ้อม) เรียกว่ารหัสที่พยายามจะใช้ IFYSWIM การพึ่งพาแบบวงกลมและทั้งหมดนั้น
Tom Hawtin - tackline

1
@ Tom ถูกต้อง - เป็นไปได้ที่จะเขียนสิ่งที่ initializer แบบคงที่หนึ่งเรียกวิธี static ก่อนที่ initializer แบบ static อีกอันจะถูกเรียก แต่จิตใจของฉันกลับมาคิดใหม่
พอลทอมบลิน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.