เมื่อพิจารณาการเริ่มต้นคลาสเราทุกคนรู้ว่าเมื่อทำการเริ่มต้น subclass หากคลาสแม่ไม่ได้เริ่มต้นจะเริ่มต้นคลาสย่อยจะต้องเริ่มต้นก่อน อย่างไรก็ตามสิ่งต่าง ๆ ไม่ง่ายเหมือนประโยคเดียว
ก่อนอื่นมาดูเงื่อนไขสำหรับการเริ่มต้นที่ทริกเกอร์ใน Java:
(1) เมื่อใช้ใหม่เพื่อสร้างอินสแตนซ์วัตถุและการเข้าถึงข้อมูลและวิธีการคงที่นั่นคือเมื่อพบคำแนะนำ: ใหม่, getstatic/putstatic และ invokestatic;
(2) เมื่อใช้การสะท้อนเพื่อเรียกคลาส
(3) เมื่อเริ่มต้นคลาสหากคลาสแม่ไม่ได้เริ่มต้นการเริ่มต้นของคลาสแม่จะถูกเรียกใช้ก่อน
(4) ชั้นเรียนที่มีการดำเนินการหลักทางเข้าอยู่
(5) คลาสที่มีการจัดการวิธีการอยู่ในการสนับสนุนภาษาแบบไดนามิกของ JDK1.7 หากการเริ่มต้นไม่ได้ถูกทริกเกอร์
หลังจากการรวบรวมวิธี A <linit> จะถูกสร้างขึ้นและการเริ่มต้นชั้นเรียนจะดำเนินการในวิธีนี้ วิธีนี้จะดำเนินการเท่านั้นและ JVM รับรองสิ่งนี้และดำเนินการควบคุมการซิงโครไนซ์
ในหมู่พวกเขาเงื่อนไข (3) จากมุมมองของการโทรวิธีคือ subclass <linit> ที่เรียกว่าชั้นผู้ปกครอง <คลินิก> ที่เริ่มต้นซึ่งคล้ายกับความจริงที่ว่าเราต้องโทรเข้าสร้างตัวสร้างคลาสแม่ในคอนสตรัคเตอร์คลาสย่อย
แต่ควรสังเกตว่า "ทริกเกอร์" ไม่ได้เริ่มต้นอย่างสมบูรณ์ซึ่งหมายความว่าเป็นไปได้ว่าการเริ่มต้นของคลาสย่อยจะสิ้นสุดล่วงหน้าก่อนการเริ่มต้นของคลาสแม่ซึ่งเป็นที่ "อันตราย" อยู่
1. ตัวอย่างของการเริ่มต้นคลาส:
ในตัวอย่างนี้ฉันใช้คลาสอุปกรณ์ต่อพ่วงเพื่อมีคลาสสมาชิกคงที่ 2 คลาสที่มีความสัมพันธ์ในการสืบทอด เนื่องจากการเริ่มต้นของคลาสอุปกรณ์ต่อพ่วงและคลาสสมาชิกคงที่ไม่มีความสัมพันธ์เชิงสาเหตุจึงปลอดภัยและสะดวกในการแสดงเช่นนี้
คลาส Parent Class A และ Child Class B ตามลำดับมีฟังก์ชั่นหลัก จากเงื่อนไขการทริกเกอร์ข้างต้น (4) จะเห็นได้ว่าเส้นทางการเริ่มต้นคลาสที่แตกต่างกันจะถูกเรียกใช้โดยการเรียกใช้ฟังก์ชันหลักทั้งสองตามลำดับ
ปัญหาของตัวอย่างนี้คือคลาสแม่มีการอ้างอิงแบบคงที่ของคลาสเด็กและเริ่มต้นตามคำจำกัดความ:
Wrapperclass คลาสสาธารณะ {คลาสคงที่คลาส A {Static {System.out.println ("เริ่มต้นคลาส A เริ่มต้น ... "); } // คลาสแม่มีการอ้างอิงแบบคงที่ของชั้นเรียนเด็กคงที่ B B = ใหม่ b (); ได้รับการป้องกัน int int aint = 9; Static {System.out.println ("การเริ่มต้นคลาส A สิ้นสุด ... "); } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {}} คลาสสแตติกส่วนตัวส่วนตัวขยาย {คงที่ {system.out.println ("เริ่มต้นคลาส B เริ่มต้น ... "); } // โดเมนของคลาสย่อยขึ้นอยู่กับโดเมนของคลาสแม่เอกชนคงที่ int bint = 9 + A.aint; สาธารณะ b () {// โดเมนคงที่ของตัวสร้างขึ้นอยู่กับระบบคลาส. เอาต์ println ("constructor call สำหรับคลาส B" + "ค่าของ bint" + bint); } คงที่ {System.out.println ("การเริ่มต้นคลาส B สิ้นสุดลง ... " + "ค่าของ AINT:" + BINT); } โมฆะคงที่สาธารณะหลัก (สตริง [] args) {}}}} สถานการณ์ที่ 1: ผลลัพธ์ผลลัพธ์เมื่อรายการเป็นฟังก์ชั่นหลักของคลาส B:
/** * เริ่มต้นคลาส A เริ่มต้น ... * ตัวสร้างของคลาส B เรียกค่าของ bint 0 * คลาส A การเริ่มต้นสิ้นสุดลง ... * การเริ่มต้นคลาส B เริ่มต้น ... * การเริ่มต้นคลาส B สิ้นสุดลง ... ค่า AINT: 18 */
การวิเคราะห์: จะเห็นได้ว่าการเรียกใช้ฟังก์ชั่นหลักทำให้เกิดการเริ่มต้นของคลาส B และเข้าสู่วิธี <linit> ของคลาส B Class A เป็นคลาสแม่เริ่มเริ่มต้นก่อนและเข้าสู่วิธี <คลินิก> ของ A และมีคำสั่งใหม่ B (); ในเวลานี้ B จะได้รับการสร้างอินสแตนซ์ซึ่งอยู่ใน <คลินิก> ของคลาส B เธรดหลักได้รับการล็อคและเริ่มดำเนินการ <linit> ของคลาส B เรากล่าวว่าในตอนแรกว่า JVM จะมั่นใจได้ว่าวิธีการเริ่มต้นของชั้นเรียนจะถูกดำเนินการเพียงครั้งเดียว หลังจากได้รับคำแนะนำใหม่ JVM จะไม่ป้อนวิธี <linit> ของคลาส B อีกครั้ง แต่จะถูกสร้างอินสแตนซ์โดยตรง อย่างไรก็ตามในเวลานี้คลาส B ยังไม่เสร็จสิ้นการเริ่มต้นคลาสดังนั้นคุณจะเห็นได้ว่าค่าของ Bint คือ 0 (0 นี้เป็นศูนย์เริ่มต้นที่ดำเนินการหลังจากจัดสรรหน่วยความจำของพื้นที่วิธีการในระหว่างขั้นตอนการเตรียมการของการโหลดคลาส);
ดังนั้นจึงสามารถสรุปได้ว่าคลาสแม่มีโดเมนแบบคงที่ของประเภทเด็กและดำเนินการที่ได้รับมอบหมายซึ่งอาจทำให้เกิดการสร้างอินสแตนซ์ subclass ที่จะดำเนินการก่อนที่การเริ่มต้นคลาสจะเสร็จสิ้น
สถานการณ์ที่ 2: ผลลัพธ์ผลลัพธ์เมื่อรายการเป็นฟังก์ชั่นหลักของคลาส A:
/** * การเริ่มต้นคลาส A เริ่มต้น ... * การเริ่มต้นคลาส B เริ่มต้น ...
การวิเคราะห์: หลังจากการวิเคราะห์สถานการณ์ที่ 1 เรารู้ว่าการเริ่มต้นการเริ่มต้นของคลาส A โดยการเริ่มต้นของคลาส B จะทำให้เกิดการสร้างอินสแตนซ์ของตัวแปรคลาส B ในคลาส A ที่จะดำเนินการก่อนที่การเริ่มต้นของคลาส B จะเสร็จสมบูรณ์ ดังนั้นหากคลาส A เริ่มต้นก่อนคลาส B จะถูกเรียกใช้ก่อนเมื่อการสร้างอินสแตนซ์ของตัวแปรคลาสเพื่อให้การเริ่มต้นทำก่อนการสร้างอินสแตนซ์หรือไม่? คำตอบคือใช่ แต่ยังมีปัญหาอยู่
ตามเอาท์พุทเราจะเห็นว่าการเริ่มต้นของคลาส B ดำเนินการก่อนการเริ่มต้นของคลาส A เสร็จสมบูรณ์ซึ่งทำให้ตัวแปรเช่นตัวแปรคลาส AINT เริ่มต้นได้เฉพาะหลังจากคลาส B เริ่มต้นดังนั้นค่าของ AINT ที่ได้จากโดเมน Bint ในคลาส B คือ "0" แทนที่จะเป็น "18"
สรุป: โดยสรุปแล้วสามารถสรุปได้ว่ามันเป็นอันตรายมากที่จะรวมตัวแปรคลาสของประเภท subclass ในชั้นเรียนหลักและสร้างอินสแตนซ์พวกเขาเมื่อกำหนดพวกเขา สถานการณ์เฉพาะอาจไม่ตรงไปตรงมาเหมือนตัวอย่าง วิธีการโทรเพื่อกำหนดค่าที่นิยามก็เป็นอันตรายเช่นกัน แม้ว่าคุณต้องการรวมโดเมนแบบคงที่ของประเภท subclass คุณควรกำหนดค่าผ่านวิธีการคงที่เนื่องจาก JVM สามารถมั่นใจได้ว่าการดำเนินการเริ่มต้นทั้งหมดเสร็จสมบูรณ์ก่อนที่วิธีการคงที่เรียกว่า (แน่นอนการรับประกันนี้คือคุณไม่ควรรวม b b = ใหม่ b ()
2. ตัวอย่างอินสแตนซ์:
ก่อนอื่นคุณต้องรู้กระบวนการสร้างวัตถุ:
(1) เมื่อพบคำสั่งใหม่ให้ตรวจสอบว่าชั้นเรียนเสร็จสิ้นการโหลดการตรวจสอบการเตรียมการแยกวิเคราะห์และการเริ่มต้น (กระบวนการแยกวิเคราะห์คือการแยกวิเคราะห์การอ้างอิงสัญลักษณ์ลงในการอ้างอิงโดยตรงเช่นชื่อวิธีการอ้างอิงสัญลักษณ์ซึ่งสามารถดำเนินการได้ก่อนที่จะใช้การอ้างอิงสัญลักษณ์นี้
(2) จัดสรรหน่วยความจำใช้รายการฟรีหรือวิธีการชนของตัวชี้และ "ศูนย์" หน่วยความจำที่จัดสรรใหม่ ดังนั้นตัวแปรอินสแตนซ์ทั้งหมดจะเริ่มต้นเป็น 0 โดยค่าเริ่มต้น (อ้างอิงเป็น null) ในลิงค์นี้;
(3) ดำเนินการวิธี <init> รวมถึงการตรวจสอบการโทรไปยังวิธี <init> (ตัวสร้าง) ของคลาสพาเรนต์การกระทำที่กำหนดโดยตัวแปรอินสแตนซ์อินสแตนติสเตอร์จะดำเนินการในอินเดียนา
ตัวอย่างนี้อาจเป็นที่รู้จักกันดีกว่านั่นคือการละเมิด "อย่าเรียกวิธีการที่ใช้งานได้ในตัวสร้างวิธีการโคลนและวิธี readobject" เหตุผลก็คือ polymorphism ใน Java นั่นคือการเชื่อมโยงแบบไดนามิก
ตัวสร้างของคลาสแม่คลาส A มีวิธีการที่ได้รับการป้องกันและคลาส B เป็นคลาสย่อย
ชั้นเรียนสาธารณะผิด stantiation {private static class a {public a () {dosomething (); } void dosomething () {system.out.println ("Dosomething" A "); }} คลาสสแตติกส่วนตัวขยายตัวขยาย {ส่วนตัว int bint = 9; @Override ป้องกันโมฆะ dosomething () {system.out.println ("Dosomething ของ B, bint:" + bint); }} โมฆะคงที่สาธารณะหลัก (สตริง [] args) {b b = ใหม่ b (); -ผลลัพธ์ผลลัพธ์:
/** * Dosomething ของ B, Bint: 0 */
การวิเคราะห์: ก่อนอื่นคุณต้องรู้ว่าเมื่อไม่มีการแสดงผลคอมไพเลอร์ Java จะสร้างตัวสร้างเริ่มต้นและเรียกตัวสร้างของคลาสแม่ที่จุดเริ่มต้น ดังนั้นตัวสร้างของคลาส B จะเรียกตัวสร้างของคลาส A เป็นครั้งแรกที่จุดเริ่มต้น
วิธีการที่ได้รับการป้องกัน dosomething เรียกว่าในคลาส A จากผลลัพธ์ผลลัพธ์เราจะเห็นว่าการใช้วิธีการของคลาสย่อยนั้นเรียกจริงและการสร้างอินสแตนซ์ของคลาสย่อยยังไม่ได้เริ่มต้นดังนั้น Bint จึงไม่ใช่ 9 เป็น "คาดว่า" แต่ 0;
นี่เป็นเพราะการเชื่อมโยงแบบไดนามิก Dosomething เป็นวิธีการที่ได้รับการป้องกันดังนั้นจึงถูกเรียกผ่านคำสั่ง invokevirtual ซึ่งค้นหาการใช้วิธีการที่สอดคล้องกันตามประเภทของอินสแตนซ์ของวัตถุ (นี่คือวัตถุอินสแตนซ์ของ B และวิธีการที่สอดคล้องกันคือการใช้วิธีการของคลาส B) ดังนั้นผลลัพธ์นี้คือ
สรุป: ดังที่ได้กล่าวไว้ก่อนหน้านี้ "อย่าเรียกวิธีการที่สามารถใช้งานได้ในตัวสร้างวิธีการโคลนและวิธีการอ่าน"
ข้างต้นเป็น "ทุ่นระเบิด" สองแห่งในการเริ่มต้นคลาส Java และการสร้างอินสแตนซ์ที่แนะนำให้คุณรู้จัก ฉันหวังว่ามันจะเป็นประโยชน์กับการเรียนรู้ของทุกคน