tl; dr
สำหรับสาขา , int b = b + 1
เป็นสิ่งผิดกฎหมายเพราะคือการอ้างอิงไปข้างหน้าผิดกฎหมายb
b
คุณสามารถแก้ไขได้จริงโดยการเขียนint b = this.b + 1
ซึ่งรวบรวมโดยไม่มีข้อร้องเรียน
สำหรับตัวแปรท้องถิ่น , int d = d + 1
เป็นสิ่งผิดกฎหมายเพราะd
ไม่ได้เริ่มต้นก่อนการใช้งาน นี่ไม่ใช่กรณีสำหรับฟิลด์ซึ่งมักจะเริ่มต้นเป็นค่าเริ่มต้น
คุณสามารถเห็นความแตกต่างได้โดยพยายามรวบรวม
int x = (x = 1) + x;
เป็นการประกาศฟิลด์และเป็นการประกาศตัวแปรโลคัล อดีตจะล้มเหลว แต่หลังจะประสบความสำเร็จเนื่องจากความแตกต่างในความหมาย
บทนำ
ก่อนอื่นกฎสำหรับตัวเริ่มต้นของฟิลด์และตัวแปรโลคัลนั้นแตกต่างกันมาก ดังนั้นคำตอบนี้จะจัดการกับกฎเป็นสองส่วน
เราจะใช้โปรแกรมทดสอบนี้ตลอด:
public class test {
int a = a = 1;
int b = b + 1;
public static void Main(String[] args) {
int c = c = 1;
int d = d + 1;
}
}
การประกาศb
ไม่ถูกต้องและล้มเหลวด้วยillegal forward reference
ข้อผิดพลาด
การประกาศd
ไม่ถูกต้องและล้มเหลวด้วยvariable d might not have been initialized
ข้อผิดพลาด
ความจริงที่ว่าข้อผิดพลาดเหล่านี้แตกต่างกันควรบอกเป็นนัยว่าสาเหตุของข้อผิดพลาดก็แตกต่างกันเช่นกัน
ฟิลด์
ตัวเริ่มต้นฟิลด์ใน Java ถูกควบคุมโดยJLS §8.3.2การเริ่มต้นฟิลด์
ขอบเขตของเขตข้อมูลที่กำหนดไว้ในJLS §6.3 , ขอบเขตของการประกาศ
กฎที่เกี่ยวข้อง ได้แก่ :
- ขอบเขตของการประกาศของสมาชิกที่
m
ประกาศหรือสืบทอดโดยคลาสประเภท C (§8.1.6) คือเนื้อความทั้งหมดของ C รวมถึงการประกาศประเภทที่ซ้อนกัน
- นิพจน์การเริ่มต้นสำหรับตัวแปรอินสแตนซ์อาจใช้ชื่อง่าย ๆ ของตัวแปรคงที่ที่ประกาศในหรือสืบทอดโดยคลาสแม้กระทั่งคำประกาศที่เกิดขึ้นในภายหลัง
- การใช้ตัวแปรอินสแตนซ์ที่การประกาศปรากฏเป็นข้อความหลังจากการใช้งานบางครั้งถูก จำกัด แม้ว่าตัวแปรอินสแตนซ์เหล่านี้จะอยู่ในขอบเขตก็ตาม ดู§8.3.2.3สำหรับกฎที่แม่นยำซึ่งควบคุมการอ้างอิงไปข้างหน้ากับตัวแปรอินสแตนซ์
§8.3.2.3พูดว่า:
การประกาศสมาชิกจะต้องปรากฏเป็นข้อความก่อนที่จะใช้เฉพาะในกรณีที่สมาชิกเป็นฟิลด์อินสแตนซ์ (ตามลำดับคงที่) ของคลาสหรืออินเตอร์เฟส C และเงื่อนไขทั้งหมดต่อไปนี้มีไว้
- การใช้งานเกิดขึ้นในตัวเริ่มต้นตัวแปรอินสแตนซ์ (ตามลำดับคงที่) ของ C หรือในอินสแตนซ์ (ตามลำดับคงที่) ตัวเริ่มต้นของ C
- การใช้งานไม่ได้อยู่ทางด้านซ้ายมือของงาน
- การใช้งานผ่านชื่อธรรมดา
- C คือคลาสหรืออินเทอร์เฟซด้านในสุดที่ปิดล้อมการใช้งาน
จริงๆแล้วคุณสามารถอ้างถึงฟิลด์ก่อนที่จะประกาศยกเว้นในบางกรณี ข้อ จำกัด เหล่านี้มีไว้เพื่อป้องกันรหัสเช่น
int j = i;
int i = j;
จากการรวบรวม ข้อมูลจำเพาะของ Java ระบุว่า "ข้อ จำกัด ข้างต้นได้รับการออกแบบมาเพื่อตรวจจับในเวลาคอมไพล์การเริ่มต้นแบบวงกลมหรือผิดรูปแบบ"
กฎเหล่านี้มีผลต่ออะไร?
โดยทั่วไปแล้วกฎจะบอกว่าคุณต้องประกาศเขตข้อมูลล่วงหน้าของการอ้างอิงไปยังฟิลด์นั้นหาก (a) การอ้างอิงอยู่ในตัวเริ่มต้น (b) การอ้างอิงไม่ได้ถูกกำหนดให้ (c) การอ้างอิงคือ a ชื่อธรรมดา (ไม่มีคุณสมบัติเช่นthis.
) และ (d) ไม่สามารถเข้าถึงได้จากภายในคลาสภายใน ดังนั้นการอ้างอิงไปข้างหน้าที่ตรงตามเงื่อนไขทั้งสี่จึงผิดกฎหมาย แต่การอ้างอิงไปข้างหน้าที่ล้มเหลวอย่างน้อยหนึ่งเงื่อนไขก็ใช้ได้
int a = a = 1;
รวบรวมเนื่องจากละเมิด (b): a
มีการกำหนดข้อมูลอ้างอิงให้ดังนั้นจึงเป็นเรื่องถูกกฎหมายที่จะอ้างถึงa
ล่วงหน้าก่อนการa
ประกาศฉบับสมบูรณ์
int b = this.b + 1
ยังรวบรวมเพราะละเมิด (c): การอ้างอิงthis.b
ไม่ใช่ชื่อธรรมดา (มีคุณสมบัติด้วยthis.
) โครงสร้างแปลก ๆ นี้ยังคงมีการกำหนดไว้อย่างสมบูรณ์เนื่องจากthis.b
มีค่าเป็นศูนย์
ดังนั้นโดยพื้นฐานแล้วข้อ จำกัด ในการอ้างอิงฟิลด์ภายใน initializers ป้องกันไม่ให้int a = a + 1
คอมไพล์สำเร็จ
สังเกตว่าการประกาศเขตข้อมูลint b = (b = 1) + b
จะไม่สามารถรวบรวมได้เนื่องจากขั้นสุดท้ายb
ยังคงเป็นการอ้างอิงไปข้างหน้าที่ผิดกฎหมาย
ตัวแปรท้องถิ่น
การประกาศตัวแปรท้องถิ่นอยู่ภายใต้JLS §14.4คำชี้แจงการประกาศตัวแปรท้องถิ่น
ขอบเขตของตัวแปรท้องถิ่นถูกกำหนดไว้ในJLS §6.3ขอบเขตของประกาศ:
- ขอบเขตของการประกาศตัวแปรโลคัลในบล็อก (§14.4) คือส่วนที่เหลือของบล็อกที่การประกาศปรากฏขึ้นโดยเริ่มจากตัวเริ่มต้นของตัวเองและรวมถึงตัวประกาศอื่น ๆ ทางด้านขวาในคำสั่งการประกาศตัวแปรโลคัล
โปรดทราบว่าตัวเริ่มต้นอยู่ในขอบเขตของตัวแปรที่ประกาศ แล้วทำไมไม่int d = d + 1;
คอมไพล์ล่ะ?
สาเหตุเนื่องมาจากกฎของ Java เกี่ยวกับการกำหนดที่แน่นอน ( JLS §16 ) การกำหนดที่ชัดเจนโดยทั่วไปกล่าวว่าการเข้าถึงตัวแปรโลคัลทุกครั้งจะต้องมีการกำหนดก่อนหน้านี้ให้กับตัวแปรนั้นและคอมไพลเลอร์ Java จะตรวจสอบลูปและกิ่งก้านเพื่อให้แน่ใจว่าการกำหนดจะเกิดขึ้นก่อนการใช้งานใด ๆเสมอ (นี่คือเหตุผลที่การกำหนดที่แน่นอนมีส่วนข้อกำหนดเฉพาะทั้งหมด ไปเลย). กฎพื้นฐานคือ:
- สำหรับการเข้าถึงของตัวแปรท้องถิ่นหรือสนามสุดท้ายว่างเปล่าทุก
x
, x
ต้องกำหนดแน่นอนก่อนที่จะเข้าถึงหรือรวบรวมข้อผิดพลาดเกิดขึ้นเวลา
ในint d = d + 1;
การเข้าถึงd
ได้รับการแก้ไขให้กับตัวแปรโลคัลได้ดี แต่เนื่องจากd
ไม่ได้ถูกกำหนดก่อนที่d
จะเข้าถึงคอมไพลเลอร์จึงแสดงข้อผิดพลาด ในint c = c = 1
, c = 1
เกิดขึ้นครั้งแรกที่ได้รับมอบหมายc
และจากนั้นc
จะเริ่มต้นได้จากผลของการกำหนดว่า (ซึ่งก็คือ 1)
หมายเหตุว่าเพราะกฎการกำหนดแน่นอนการประกาศตัวแปรท้องถิ่นint d = (d = 1) + d;
จะรวบรวมประสบความสำเร็จ ( ซึ่งแตกต่างจากการประกาศเขตint b = (b = 1) + b
) เพราะd
ได้รับมอบหมายแน่นอนตามเวลาที่สุดท้ายd
จะมาถึง
static
ในตัวแปรขอบเขตคลาสstatic int x = x + 1;
คุณจะได้รับข้อผิดพลาดเดียวกันหรือไม่ เนื่องจากใน C # จะสร้างความแตกต่างหากเป็นแบบคงที่หรือไม่คงที่