ตัวปรับคงที่มีผลต่อรหัสนี้อย่างไร?


109

นี่คือรหัสของฉัน:

class A {
    static A obj = new A();
    static int num1;
    static int num2=0;

    private A() {
        num1++;
        num2++;
    }
    public static A getInstance() {
        return obj;
    }
}

public class Main{
    public static void main(String[] arg) {
        A obj = A.getInstance();
        System.out.println(obj.num1);
        System.out.println(obj.num2);
    }
}

ผลลัพธ์คือ1 0แต่ฉันไม่เข้าใจ

ใครช่วยอธิบายให้ฉันฟังหน่อย


10
เป็นคำถามที่ดี! เราควรเรียนรู้อะไรจากสิ่งนี้อย่าทำ! ;)
isnot2bad

คำตอบ:


116

ใน Java มีสองขั้นตอน: 1. Identification, 2. Execution

  1. ในการระบุเฟสการตัวแปรคงที่ทั้งหมดจะถูกตรวจพบและเริ่มต้นด้วยค่าเริ่มต้น

    ตอนนี้ค่าคือ:
    A obj=null
    num1=0
    num2=0

  2. ขั้นตอนที่สองการดำเนินการเริ่มจากบนลงล่าง ใน Java การดำเนินการเริ่มต้นจากสมาชิกแบบคงที่ตัวแรก
    นี่คือตัวแปรคงที่ครั้งแรกของคุณเป็นstatic A obj = new A();อย่างแรกมันจะสร้างวัตถุของตัวแปรนั้นและเรียก constructor จึงค่าของnum1และจะกลายเป็นnum2 และจากนั้นอีกครั้งจะดำเนินการซึ่งจะทำให้1
    static int num2=0;num2 = 0;

ตอนนี้สมมติว่าตัวสร้างของคุณเป็นดังนี้:

 private A(){
    num1++;
    num2++;
    System.out.println(obj.toString());
 }

สิ่งนี้จะส่งผลให้NullPointerExceptionเนื่องจากobjยังไม่มีข้อมูลอ้างอิงของ class A.


11
ฉันจะขยาย: เลื่อนบรรทัดstatic A obj = new A();ด้านล่างstatic int num2=0;และคุณควรจะได้ 1 และ 1
โทมัส

2
สิ่งที่ยังทำให้ฉันสับสนคือความจริงที่ว่าแม้ว่า num1 จะไม่มีการเริ่มต้นอย่างชัดเจน แต่ก็เริ่มต้นด้วย 0 (โดยปริยาย) ไม่ควรมีความแตกต่างระหว่างการเริ่มต้นอย่างชัดเจนและโดยนัย ...
isnot2bad

@ isnot2bad "implicit initialization" เกิดขึ้นเป็นส่วนหนึ่งของการประกาศ ประกาศที่เกิดขึ้นก่อนที่จะได้รับมอบหมายไม่ว่าสิ่งที่สั่งซื้อที่คุณนำเสนอไว้ใน. จะกลายเป็นแบบนี้:A obj = new A(); int num1; int num2 = 0; A obj; int num1; int num2; obj = new A(); num2 = 0;Java num1, num2จะกำหนดตามเวลาที่คุณไปถึงคอนnew A()สตรัคเตอร์
Hans Z

31

ความstaticหมายของตัวปรับเปลี่ยนเมื่อนำไปใช้กับการประกาศตัวแปรคือตัวแปรเป็นตัวแปรคลาสแทนที่จะเป็นตัวแปรอินสแตนซ์ กล่าวอีกนัยหนึ่ง ... มีเพียงnum1ตัวแปรเดียวและมีเพียงnum2ตัวแปรเดียว

(นอกเหนือ: ตัวแปรคงเป็นเหมือนตัวแปรส่วนกลางในภาษาอื่น ๆ ยกเว้นชื่อของมันจะไม่ปรากฏในทุกที่แม้ว่าจะประกาศเป็นpublic staticแต่ชื่อที่ไม่มีเงื่อนไขจะมองเห็นได้ก็ต่อเมื่อมีการประกาศในคลาสปัจจุบันหรือซูเปอร์คลาส หรือหากนำเข้าโดยใช้การนำเข้าแบบคงที่นั่นคือความแตกต่างโลกที่แท้จริงสามารถมองเห็นได้โดยไม่ต้องมีคุณสมบัติที่ใดก็ได้)

ดังนั้นเมื่อคุณอ้างถึงobj.num1และobj.num2คุณเป็นจริงหมายถึงตัวแปรคงที่มีการกำหนดจริงและ และในทำนองเดียวกันเมื่อตัวสร้างเพิ่มขึ้นและมันกำลังเพิ่มตัวแปรเดียวกัน (ตามลำดับ)A.num1A.num2num1num2

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

static A obj = new A();
static int num1;
static int num2=0;

มันเกิดขึ้นเช่นนี้:

  1. สถิติเริ่มต้นด้วยค่าเริ่มต้นเริ่มต้น A.objเป็นnullและA.num1/ A.num2เป็นศูนย์

  2. การประกาศครั้งแรก ( A.obj) สร้างอินสแตนซ์ของA()และสร้างสำหรับAการเพิ่มขึ้นและA.num1 A.num2เมื่อการประกาศเสร็จสิ้นA.num1และA.num2เป็นทั้งสอง1อย่างและA.objอ้างถึงAอินสแตนซ์ที่สร้างขึ้นใหม่

  3. การประกาศครั้งที่สอง ( A.num1) ไม่มี initializer จึงA.num1ไม่เปลี่ยนแปลง

  4. ประกาศที่สาม ( A.num2) A.num2มีการเริ่มต้นที่กำหนดเพื่อเป็นศูนย์

ดังนั้นในตอนท้ายของการเริ่มต้นคลาสA.num1คือ1และA.num2คือ0... และนั่นคือสิ่งที่คำสั่งพิมพ์ของคุณแสดง

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


16

1,0 ถูกต้อง

เมื่อคลาสถูกโหลดข้อมูลแบบคงที่ทั้งหมดจะเริ่มต้นในอื่น ๆ พวกเขาจะประกาศ โดยค่าเริ่มต้น int คือ 0

  • A แรกถูกสร้างขึ้น num1 และ num2 กลายเป็น 1 และ 1
  • กว่าstatic int num1;ไม่ทำอะไรเลย
  • กว่าstatic int num2=0;นี้เขียน 0 ถึง num2

9

เป็นเพราะลำดับของตัวเริ่มต้นแบบคงที่ นิพจน์คงที่ในชั้นเรียนจะได้รับการประเมินตามลำดับจากบนลงล่าง

คนแรกที่ถูกเรียกคือตัวสร้างAซึ่งกำหนดnum1และnum2ทั้งสองเป็น 1:

static A obj = new A();

จากนั้น

static int num2=0;

ถูกเรียกและตั้งค่า num2 = 0 อีกครั้ง

นั่นคือสาเหตุที่num11 และnum2เป็น 0

ตามหมายเหตุด้านข้างตัวสร้างไม่ควรแก้ไขตัวแปรแบบคงที่นั่นเป็นการออกแบบที่ไม่ดีมาก ให้ลองใช้วิธีอื่นในการใช้ Singleton ใน Javaแทน


6

ส่วนใน JLS สามารถพบได้: §12.4.2

ขั้นตอนการเริ่มต้นโดยละเอียด:

9. จากนั้นเรียกใช้ตัวเริ่มต้นตัวแปรคลาสและตัวเริ่มต้นแบบคงที่ของคลาสหรือตัวเริ่มต้นฟิลด์ของอินเทอร์เฟซตามลำดับข้อความราวกับว่าเป็นบล็อกเดียวยกเว้นว่าตัวแปรคลาสสุดท้ายและฟิลด์ของอินเทอร์เฟซที่รวบรวมค่า - ค่าคงที่เวลาเริ่มต้นก่อน

ดังนั้นตัวแปรคงที่สามตัวจะเริ่มต้นทีละตัวตามลำดับข้อความ

ดังนั้น

static A obj = new A();
//num1 = 1, num2 = 1;
static int num1;
//this is initilized first, see below.
static int num2=0;
//num1 = 1, num2 = 0;

หากฉันเปลี่ยนคำสั่งเป็น:

static int num1;
static int num2=0;
static A obj = new A();

ผลลัพธ์จะเป็น1,1อย่างไร

โปรดทราบว่าstatic int num1;ไม่ใช่ตัวแปรเริ่มต้นเนื่องจาก ( §8.3.2 ):

หากตัวประกาศฟิลด์มีตัวแปรเริ่มต้นแสดงว่ามีความหมายของการกำหนด (§15.26) ให้กับตัวแปรที่ประกาศและ: ถ้าตัวประกาศเป็นตัวแปรคลาส (นั่นคือฟิลด์คงที่) ตัวเริ่มต้นตัวแปรจะเป็น การประเมินและการมอบหมายจะดำเนินการครั้งเดียวเมื่อเริ่มต้นชั้นเรียน

และตัวแปรคลาสนี้จะถูกเตรียมใช้งานเมื่อสร้างคลาส สิ่งนี้เกิดขึ้นก่อน ( §4.12.5 )

ตัวแปรทุกตัวในโปรแกรมต้องมีค่าก่อนที่จะใช้ค่า: ตัวแปรคลาสตัวแปรอินสแตนซ์หรือส่วนประกอบอาร์เรย์แต่ละตัวจะเริ่มต้นด้วยค่าเริ่มต้นเมื่อสร้าง (§15.9, §15.10): สำหรับประเภทไบต์ค่าเริ่มต้น เป็นศูนย์นั่นคือค่าของ (ไบต์) 0 สำหรับ type short ค่าเริ่มต้นคือศูนย์นั่นคือค่าของ (short) 0 สำหรับประเภท int ค่าดีฟอลต์คือศูนย์นั่นคือ 0 สำหรับประเภท long ค่าดีฟอลต์คือศูนย์นั่นคือ 0L สำหรับประเภท float ค่าเริ่มต้นคือศูนย์บวกนั่นคือ 0.0f สำหรับชนิด double ค่าเริ่มต้นคือศูนย์บวกนั่นคือ 0.0d สำหรับประเภท char ค่าเริ่มต้นคืออักขระ null นั่นคือ '\ u0000' สำหรับประเภทบูลีนค่าดีฟอลต์คือเท็จ สำหรับการอ้างอิงทุกประเภท (§4.3) ค่าดีฟอลต์คือ null


2

บางทีมันอาจจะช่วยคิดในแง่นี้

คลาสคือพิมพ์เขียวของออบเจ็กต์

ออบเจ็กต์สามารถมีตัวแปรได้เมื่อมีการสร้างอินสแตนซ์

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

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

นี่คือและตัวอย่างของคลาส "Dog" ที่ใช้ตัวแปรแบบคงที่เพื่อติดตามจำนวนอินสแตนซ์ที่สร้างขึ้น

คลาส "Dog" คือระบบคลาวด์ในขณะที่กล่องสีส้มเป็นอินสแตนซ์ "Dog"

คลาสสุนัข

อ่านเพิ่มเติม

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

หากคุณรู้สึกเหมือนเป็นเรื่องเล็กน้อยความคิดนี้ถูกนำมาใช้ครั้งแรกโดยเพลโต


1

คำหลักแบบคงที่ใช้ใน java เป็นหลักสำหรับการจัดการหน่วยความจำ เราอาจใช้คำสำคัญแบบคงที่กับตัวแปรวิธีการบล็อกและคลาสที่ซ้อนกัน คีย์เวิร์ดแบบคงที่เป็นของคลาสมากกว่าอินสแตนซ์ของคลาสสำหรับคำอธิบายสั้น ๆ เกี่ยวกับคีย์เวิร์ดแบบคงที่:

http://www.javatpoint.com/static-keyword-in-java


0

หลายคำตอบข้างต้นถูกต้อง แต่เพื่อแสดงให้เห็นถึงสิ่งที่เกิดขึ้นจริง ๆ ฉันได้ทำการปรับเปลี่ยนเล็กน้อยด้านล่าง

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

class A {
    static A obj = new A();
    static int num1;
    static int num2;
    static {
        System.out.println("Setting num2 to 0");
        num2 = 0;
    }

    private A() {
        System.out.println("Constructing singleton instance of A");
        num1++;
        num2++;
    }

    public static A getInstance() {
        return obj;
    }
}

public class Main {

    public static void main(String[] arg) {
        A obj = A.getInstance();
        System.out.println(obj.num1);
        System.out.println(obj.num2);
    }
}

เอาต์พุตคือ

Constructing singleton instance of A
Setting num2 to 0
1
0

0

java ไม่เริ่มต้นค่าของสมาชิกข้อมูลแบบคงที่หรือไม่คงที่จนกว่าจะไม่มีการเรียกใช้ แต่สร้างขึ้น

ดังนั้นที่นี่เมื่อจะเรียก num1 และ num2 เป็นหลักจากนั้นมันจะเริ่มต้นด้วยค่า

เลข 1 = 0 + 1; และ

เลข 2 = 0;

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