ในการเขียนโปรแกรม Java สมาชิกจะถูกแก้ไขโดยใช้คำหลักส่วนตัว เฉพาะคลาสที่สมาชิกนี้ตั้งอยู่และวิธีการของคลาสนี้เท่านั้นที่สามารถใช้งานได้และคลาสอื่น ๆ ไม่สามารถเข้าถึงสมาชิกส่วนตัวนี้ได้
ข้างต้นอธิบายฟังก์ชั่นพื้นฐานของตัวดัดแปลงส่วนตัว วันนี้มาศึกษาสถานการณ์ของความล้มเหลวในการทำงานส่วนตัว
ชั้นเรียนภายใน Java
ในชวาฉันเชื่อว่าหลายคนใช้ชั้นเรียนภายใน Java อนุญาตให้กำหนดคลาสอื่นในคลาสหนึ่ง คลาสในชั้นเรียนเป็นคลาสภายในหรือที่เรียกว่าคลาสซ้อนกัน การใช้งานชั้นเรียนภายในอย่างง่ายอาจมีดังนี้
คลาส OuterClass {คลาส innerClass {}}ปัญหาของวันนี้เกี่ยวข้องกับชั้นเรียนภายใน Java และเกี่ยวข้องกับความรู้ภายในชั้นเรียนบางอย่างที่เกี่ยวข้องกับการวิจัยของบทความนี้ เราจะแนะนำบทความต่อไปนี้เกี่ยวกับชั้นเรียนภายใน Java
ครั้งแรกที่มันล้มเหลว?
สถานการณ์ที่เรามักใช้ในการเขียนโปรแกรมคือการเข้าถึงตัวแปรสมาชิกส่วนตัวหรือวิธีการของคลาสภายนอกในคลาสภายในซึ่งก็โอเค ตามที่ใช้ในรหัสต่อไปนี้
ชั้นเรียนสาธารณะ outerclass {ภาษาสตริงส่วนตัว = "en"; ภูมิภาคสตริงส่วนตัว = "เรา"; คลาสสาธารณะ innerclass {โมฆะสาธารณะ printouterclassPrivateFields () {String fields = "Language =" + Language + "; region =" + ภูมิภาค; System.out.println (ฟิลด์); }} โมฆะคงที่สาธารณะหลัก (สตริง [] args) {outerclass outer = new outerclass (); OUTERCLASS.InnerClass Inner = OUTER.NEW InnerClass (); inner.printouterclassPrivateFields (); -ทำไมถึงเป็นเช่นนี้? สมาชิกที่ได้รับการแก้ไขส่วนตัวไม่สามารถเข้าถึงได้โดยคลาสที่สมาชิกอธิบายไว้หรือไม่? ส่วนตัวไม่ถูกต้องจริงหรือ?
คอมไพเลอร์กำลังยุ่งอยู่กับ?
เราใช้คำสั่ง javap เพื่อดูไฟล์คลาสสองไฟล์ที่สร้างขึ้น
การสลายผลลัพธ์ของ OuterClass
15:30 $ JAVAP -C OUTERCLASSCOMPILED จาก "OUTERCLASS.JAVA" ระดับสาธารณะ OUTERCLASS ขยาย Java.lang.Object {Public OuterClass (); รหัส: 0: aload_0 1: Invokespecial #11; // วิธีการ java/lang/object. "<int>" :() v 4: aload_0 5: ldc #13; // สตริง EN 7: Putfield #15; // ภาษาฟิลด์: ljava/lang/string; 10: Aload_0 11: LDC #17; // String US 13: Putfield #19; // ภูมิภาคฟิลด์: ljava/lang/string; 16: returnpublic static void main (java.lang.string []); รหัส: 0: ใหม่ #1; // คลาส outerclass 3: DUP 4: Invokespecial #27; // วิธีการ "<init>" :() v 7: store_1 8: ใหม่ #28; // คลาส outerclass $ innerclass 11: dup 12: aload_1 13: dup 14: invokevirtual #30; // วิธีการ java/lang/object.getClass :() ljava/lang/คลาส; 17: ป๊อป 18: Invokespecial #34; // วิธี outerclass $ innerclass. "<init>" :( louterclass;) v 21: store_2 22: aload_2 23: invokevirtual #37; // วิธี outerclass $ innerclass.printouterclassprivatefields :() v 26: returnstatic java.lang.string Access $ 0 (outerclass); รหัส: 0: aload_0 1: getfield #15; // ภาษาฟิลด์: ljava/lang/string; 4: areturnstatic java.lang.string Access $ 1 (OuterClass); รหัส: 0: aload_0 1: getfield #19; // ภูมิภาคฟิลด์: ljava/lang/string; 4: Areturn}ฮะ? ไม่เราไม่ได้กำหนดวิธีการทั้งสองนี้ใน OuterClass
Java.lang.String Access $ 0 (OUTERCLASS); รหัส: 0: aload_0 1: getfield #15; // ภาษาฟิลด์: ljava/lang/string; 4: areturnstatic java.lang.string Access $ 1 (OuterClass); รหัส: 0: aload_0 1: getfield #19; // ภูมิภาคฟิลด์: ljava/lang/string; 4: Areturn}
ตัดสินจากความคิดเห็นที่ได้รับการเข้าถึง $ 0 ส่งคืนแอตทริบิวต์ภาษาของ OuterClass; เข้าถึง $ 1 ส่งคืนแอตทริบิวต์ภูมิภาคของ OuterClass และทั้งสองวิธียอมรับอินสแตนซ์ของ OuterClass เป็นพารามิเตอร์ เหตุใดทั้งสองวิธีนี้จึงถูกสร้างขึ้นและฟังก์ชั่นของพวกเขาคืออะไร? มาดูผลลัพธ์การสลายตัวของคลาสภายใน
ผลการสลายตัวของ outerclass $ innerclass
15:37 $ JAVAP -C OUTERCLASS/$ InnerClassCompiled จาก "Outerclass.java" คลาสสาธารณะ Outerclass $ innerclass ขยาย java.lang.Object {สุดท้าย OUTERCLASS $ 0; รหัส: 0: aload_0 1: aload_1 2: putfield #10; // ฟิลด์นี้ $ 0: louterclass; 5: Aload_0 6: Invokespecial #12; // วิธีการ java/lang/object. "<int>" :() v 9: returnpublic void printouterclassprivatefields (); รหัส: 0: ใหม่ #20; // คลาส Java/Lang/StringBuilder 3: DUP 4: LDC #22; // ภาษาสตริง = 6: invokespecial #24; // วิธีการ java/lang/stringbuilder. "<init>" :( ljava/lang/string;) v 9: aload_0 10: getfield #10; // ฟิลด์นี้ $ 0: louterclass; 13: Invokestatic #27; // วิธี outerclass.access $ 0: (louterclass;) ljava/lang/string; 16: invokevirtual #33; // วิธีการ java/lang/stringbuilder.append: (ljava/lang/string;) ljava/lang/stringbuilder; 19: LDC #37; // string; region = 21: invokevirtual #33; // วิธีการ java/lang/stringbuilder.append: (ljava/lang/string;) ljava/lang/stringbuilder; 24: Aload_0 25: Getfield #10; // ฟิลด์นี้ $ 0: louterclass; 28: Invokestatic #39; // วิธี outerclass.access $ 1: (louterclass;) ljava/lang/string; 31: invokevirtual #33; // วิธีการ java/lang/stringbuilder.append: (ljava/lang/string;) ljava/lang/stringbuilder; 34: invokevirtual #42; // วิธีการ java/lang/stringbuilder.toString :() ljava/lang/string; 37: store_1 38: getstatic #46; // ฟิลด์ java/lang/system.out: ljava/io/printstream; 41: Aload_1 42: Invokevirtual #52; // วิธีการ java/io/printstream.println: (ljava/lang/string;) v 45: return}รหัสต่อไปนี้เรียกรหัสการเข้าถึง $ 0 โดยมีวัตถุประสงค์เพื่อให้ได้คุณสมบัติส่วนตัวภาษาของ OuterClass
13: Invokestatic #27; // วิธี outerclass.access $ 0: (louterclass;) ljava/lang/string;
รหัสต่อไปนี้เรียกรหัสการเข้าถึง $ 1 โดยมีวัตถุประสงค์เพื่อให้ได้รับทรัพย์สินส่วนตัวในภูมิภาคของ OuterClass
28: Invokestatic #39; // วิธี outerclass.access $ 1: (louterclass;) ljava/lang/string;
หมายเหตุ: เมื่อสร้างคลาสด้านในการอ้างอิงไปยังคลาสด้านนอกจะถูกส่งผ่านและใช้เป็นคุณสมบัติของคลาสด้านในดังนั้นคลาสด้านในจะมีการอ้างอิงถึงชั้นนอกของมัน
$ 0 นี้เป็นการอ้างอิงคลาสภายนอกที่จัดขึ้นโดยคลาสด้านในซึ่งผ่านการอ้างอิงและกำหนดค่าผ่านตัวสร้าง
รอบชิงชนะเลิศ OUTERCLASS $ 0; outerclass สาธารณะ $ innerclass (OuterClass); รหัส: 0: aload_0 1: aload_1 2: putfield #10; // ฟิลด์นี้ $ 0: louterclass; 5: Aload_0 6: Invokespecial #12; // วิธีการ java/lang/object. "<init>" :() v 9: return
สรุป
ส่วนหนึ่งของส่วนตัวนี้ดูเหมือนจะไม่ถูกต้อง แต่ก็ไม่ถูกต้องเนื่องจากเมื่อชั้นเรียนภายในเรียกคุณสมบัติส่วนตัวของคลาสด้านนอกการดำเนินการจริงของมันคือการเรียกวิธีการคงที่ของแอตทริบิวต์ที่สร้างโดยคอมไพเลอร์ (เช่น Acess $ 0, เข้าถึง $ 1 ฯลฯ ) ทั้งหมดนี้เป็นการจัดการพิเศษของคอมไพเลอร์
คราวนี้ไม่ถูกต้อง?
หากใช้วิธีการเขียนข้างต้นโดยทั่วไปแล้ววิธีการเขียนนี้ไม่ค่อยเปิดเผย แต่สามารถทำงานได้
คลาสสาธารณะอีกคลาส {โมฆะคงที่สาธารณะหลัก (String [] args) {innerclass inner = ใหม่ ounderouterclass (). innerclass ใหม่ (); System.out.println ("innerclass ยื่น =" + inner.x); } คลาส innerClass {private int x = 10; -เช่นเดียวกับด้านบนใช้ Javap เพื่อสลายตัวและดู แต่ครั้งนี้เราดูผลลัพธ์ของ InnerClass เป็นครั้งแรก
16:03 $ JAVAP -C อีกคนหนึ่ง/$ innerClassCompiled จาก "ototherouterclass.java" คลาส ounderouterclass $ innerclass ขยาย java.lang.Object {รอบสุดท้ายอีกครั้งนี้ $ 0; อีก $ innerclass รหัส: 0: aload_0 1: aload_1 2: putfield #12; // ฟิลด์นี้ $ 0: lanotherouterclass; 5: Aload_0 6: Invokespecial #14; // วิธีการ java/lang/object. "<int>" :() v 9: aload_0 10: bipush 10 12: putfield #17; // ฟิลด์ x: i 15: returnstatic int access $ 0 (oundouterclass $ innerclass); รหัส: 0: aload_0 1: getfield #17; // ฟิลด์ x: i 4: ireturn}มันจะปรากฏขึ้นอีกครั้งและคอมไพเลอร์จะสร้างวิธีการลับๆโดยอัตโนมัติเพื่อให้ได้แอตทริบิวต์ส่วนตัวเข้าถึง $ 0 ครั้งเดียวเพื่อรับค่า x
อีกคนหนึ่งผลการสลายตัวของคลาส
16:08 $ JAVAP -C อีกคนหนึ่งที่มาจาก "อีกคนหนึ่งในระดับสาธารณะอีกระดับหนึ่งขยาย Java.lang.Object {สาธารณะอีกคนหนึ่ง (); รหัส: 0: Aload_0 1: Invokespecial #8; // วิธีการ java/lang/object. "<int>" :() v 4: returnpublic static main main (java.lang.string []); รหัส: 0: ใหม่ #16; // คลาส ounderouterclass $ innerclass 3: dup 4: ใหม่ #1; // คลาสอื่น ๆ อีก 7: DUP 8: Invokespecial #18; // วิธีการ "<init>" :() v 11: dup 12: invokevirtual #19; // วิธีการ java/lang/object.getClass :() ljava/lang/คลาส; 15: ป๊อป 16: Invokespecial #23; // วิธีการอื่นอีก $ innerclass. "<init>" :( lanotherouterclass;) v 19: store_1 20: getstatic #26; // ฟิลด์ java/lang/system.out: ljava/io/printstream; 23: ใหม่ #32; // คลาส Java/Lang/StringBuilder 26: DUP 27: LDC #34; // สตริง innerclass ยื่น = 29: Invokespecial #36; // วิธีการ java/lang/stringbuilder. "<init>" :( ljava/lang/string;) v 32: aload_1 33: invokestatic #39; // วิธีการอื่นอีก $ innerclass.access $ 0: (lanotherouterclass $ innerclass;) ฉัน 36: invokevirtual #43; // วิธีการ java/lang/stringbuilder.append: (i) ljava/lang/stringbuilder; 39: invokevirtual #47; // วิธีการ java/lang/stringbuilder.toString :() ljava/lang/string; 42: invokevirtual #51; // วิธีการ java/io/printstream.println: (ljava/lang/string;) v 45: return}การโทรนี้เป็นการดำเนินการของคลาสภายนอกเพื่อรับแอตทริบิวต์ส่วนตัว x ผ่านอินสแตนซ์ของคลาสภายใน
33: Invokestatic #39; // วิธีการอื่น ๆ $ innerclass.access $ 0: (lanotherouterclass $ innerclass;) i
มามีการสรุปอีกครั้ง
มีประโยคในเอกสาร Java อย่างเป็นทางการ
หากสมาชิกหรือคอนสตรัคเตอร์ถูกประกาศให้เป็นส่วนตัวการเข้าถึงจะได้รับอนุญาตหากเกิดขึ้นภายในเนื้อหาของชั้นเรียนระดับบน (§7.6) ที่แนบการประกาศของสมาชิกหรือคอนสตรัคเตอร์
ความหมายถ้าสมาชิกและผู้สร้างชั้นใน (ชั้นใน) ถูกตั้งค่าเป็นตัวดัดแปลงส่วนตัวซึ่งได้รับอนุญาตหากและเฉพาะในกรณีที่การเข้าถึงคลาสภายนอกของพวกเขา
วิธีป้องกันสมาชิกเอกชนของชั้นเรียนภายในจากการเข้าถึงโดยภายนอก
ฉันเชื่อว่าหลังจากอ่านสองส่วนข้างต้นคุณจะรู้สึกว่ามันเป็นเรื่องยากสำหรับสมาชิกส่วนตัวของชั้นเรียนภายในเพื่อหลีกเลี่ยงการเข้าถึงโดยชั้นเรียนภายนอก ใครสามารถทำให้คอมไพเลอร์ "Messing Nosy" ได้? มันสามารถทำได้จริง นั่นคือการใช้คลาสภายในที่ไม่ระบุชื่อ
เนื่องจากประเภทของวัตถุ mrunnable นั้นสามารถเรียกใช้งานได้ไม่ใช่ประเภทของคลาสภายในที่ไม่ระบุชื่อ (เราไม่สามารถรับได้ตามปกติ) และไม่มีคุณสมบัติ X ใน Runanble, mrunnable.x ไม่ได้รับอนุญาต
ชั้นเรียนสาธารณะ privatetoouter {runnable mrunnable = new runnable () {private int x = 10; @Override โมฆะสาธารณะ Run () {System.out.println (x); - โมฆะคงที่สาธารณะหลัก (สตริง [] args) {privatetoouter p = ใหม่ privatetoouter (); //system.out.println("anonymous คลาสส่วนตัวยื่น = "+ p.mrunnable.x); // ไม่ได้รับอนุญาต p.mrunnable.run (); // อนุญาต }}สรุปขั้นสุดท้าย
ในบทความนี้ส่วนตัวดูเหมือนจะไม่ถูกต้องบนพื้นผิว แต่ในความเป็นจริงมันไม่ได้ แต่จะได้รับคุณสมบัติส่วนตัวผ่านวิธีการทางอ้อมเมื่อเรียก
การก่อสร้างชั้นเรียนภายในของ Java ถือแอปพลิเคชันไปยังคลาสภายนอก แต่ C ++ ไม่ได้ซึ่งแตกต่างจาก C ++
หนังสือที่ลึกลงไปในรายละเอียด Java
แนวคิดการเขียนโปรแกรม Java
ซีรี่ส์เทคโนโลยีหลักของ Sun Company: Java Chinese เวอร์ชั่นที่มีประสิทธิภาพเข้าใจเครื่องเสมือน Java อย่างลึกซึ้ง: คุณสมบัติขั้นสูงและแนวทางปฏิบัติที่ดีที่สุดของ JVM
ข้างต้นเป็นการรวบรวมข้อมูลเกี่ยวกับตัวดัดแปลงส่วนตัวของ Java เราจะยังคงเพิ่มข้อมูลที่เกี่ยวข้องในอนาคต ขอบคุณสำหรับการสนับสนุนเว็บไซต์นี้!