في برمجة Java، إذا تم تعديل عضو باستخدام الكلمة الأساسية الخاصة، فيمكن استخدام الفئة التي يوجد بها العضو وطرق هذه الفئة فقط، ولا يمكن لأي فئة أخرى الوصول إلى هذا العضو الخاص.
الوظائف الأساسية للمعدل الخاص موصوفة أعلاه. اليوم سوف ندرس فشل الوظيفة الخاصة.
جافا الطبقات الداخلية
أعتقد أن العديد من الأشخاص قد استخدموا الفئات الداخلية في Java، مما يسمح بتعريف فئة واحدة داخل فئة أخرى. يمكن نسخ تطبيق بسيط للفئة الداخلية على النحو التالي:
الطبقة الخارجية {
الطبقة الداخلية {
}
}
سؤال اليوم يتعلق بفئات Java الداخلية، ويتضمن فقط جزءًا من المعرفة المتعلقة بالفئات الداخلية المتعلقة بالبحث في هذه المقالة. سيتم تقديم مقالات لاحقة محددة حول فئات Java الداخلية.
فشل أول مرة؟
السيناريو الذي نستخدمه غالبًا في البرمجة هو الوصول إلى متغيرات الأعضاء الخاصة أو أساليب فئة خارجية في فئة داخلية، وهذا أمر جيد. تم تنفيذها كالرمز التالي.
انسخ رمز الكود كما يلي:
الطبقة العامة OuterClass {
لغة السلسلة الخاصة = "en"؛
منطقة السلسلة الخاصة = "الولايات المتحدة"؛
الطبقة العامة InnerClass {
طباعة باطلة عامةOuterClassPrivateFields() {
حقول السلسلة = "اللغة = " + اللغة + "؛ المنطقة = " + المنطقة؛
System.out.println(الحقول);
}
}
public static void main(String[] args) {
OuterClass External = new OuterClass();
OuterClass.InnerClass الداخلي = الخارجي.new InnerClass();
printOuterClassPrivateFields();
}
}
لماذا هذا أليس صحيحا أنه لا يمكن الوصول إلى الأعضاء المعدلين إلا من خلال الفئة التي يمثلونها؟ هل الخصوصية غير صالحة حقًا؟
هل المترجم يسبب مشكلة؟
دعونا نستخدم الأمر javap لعرض ملفي الفئة اللذين تم إنشاؤهما.
رمز نسخة نتيجة إلغاء الترجمة لـ OuterClass هو كما يلي:
15:30 $ javap -c OuterClass
تم تجميعها من "OuterClass.java"
الطبقة العامة OuterClass تمتد java.lang.Object{
الفئة الخارجية العامة () ؛
شفرة:
0: التحميل_0
1: استدعاء خاص #11; //الطريقة java/lang/Object."<init>":()V
4: تحميل_0
5: إل دي سي #13; //سلسلة أون
7: putfield #15; //لغة الحقل:Ljava/lang/String;
10: التحميل_0
11: إل دي سي #17؛ // سلسلة الولايات المتحدة
13: putfield #19; //منطقة الحقل:Ljava/lang/String;
16: العودة
public static void main(java.lang.String[]);
شفرة:
0: جديد #1؛ //الفئة الخارجية
3: مزدوج
4: استدعاء خاص #27; // الطريقة "<init>":()V
7:المتجر_1
8: جديد #28؛ //فئة OuterClass$InnerClass
11: دوب
12: التحميل_1
13 : دوب
14: استدعاء افتراضي #30; // الطريقة java/lang/Object.getClass:()Ljava/lang/Class;
17: البوب
18: invocespecial #34; //Method OuterClass$InnerClass."<init>":(LOuterClass;)V
21: أستور_2
22: التحميل_2
23: استدعاء افتراضي #37; //الطريقة OuterClass$InnerClass.printOuterClassPrivateFields:()V
26: العودة
static java.lang.String access$0(OuterClass);
شفرة:
0: التحميل_0
1: getfield #15; //لغة الحقل:Ljava/lang/String;
4: العودة
static java.lang.String access$1(OuterClass);
شفرة:
0: التحميل_0
1: getfield #19; //منطقة الحقل:Ljava/lang/String;
4: العودة
}
هاه؟ لا، لم نحدد هاتين الطريقتين في OuterClass
static java.lang.String access$0(OuterClass);
شفرة:
0: التحميل_0
1: getfield #15; //لغة الحقل:Ljava/lang/String;
4: العودة
static java.lang.String access$1(OuterClass);
شفرة:
0: التحميل_0
1: getfield #19; //منطقة الحقل:Ljava/lang/String;
4: العودة
}
انطلاقًا من التعليقات المقدمة، يُرجع الوصول $0 سمة اللغة الخاصة بالفئة الخارجية؛ ويعيد الوصول $1 سمة المنطقة الخاصة بالفئة الخارجية. وتقبل كلتا الطريقتين مثيلات OuterClass كمعلمات. لماذا يتم إنشاء هاتين الطريقتين وماذا يفعلون؟ دعونا نلقي نظرة على نتائج فك الطبقة الداخلية وسنعرف.
نتائج تفكيك OuterClass$InnerClass
انسخ رمز الكود كما يلي:
15:37 $ javap -c OuterClass/$InnerClass
تم تجميعها من "OuterClass.java"
الطبقة العامة OuterClass$InnerClass تمتد java.lang.Object{
الفئة الخارجية النهائية this$0;
public OuterClass$InnerClass(OuterClass);
شفرة:
0: التحميل_0
1: تحميل_1
2: putfield #10; //حقل هذا$0:LOuterClass;
5: تحميل_0
6: استدعاء خاص #12; //الطريقة java/lang/Object."<init>":()V
9: العودة
public void printOuterClassPrivateFields();
شفرة:
0: جديد #20; //class java/lang/StringBuilder
3: مزدوج
4: LDC #22; // لغة السلسلة =
6: استدعاء خاص #24; //الطريقة java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
9: التحميل_0
10: getfield #10; //حقل هذا$0:LOuterClass;
13: invocstatic #27; //Method OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
16: استدعاء افتراضي #33; //الطريقة java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: LDC #37; //سلسلة ;المنطقة=
21: استدعاء افتراضي #33; //الطريقة java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: التحميل_0
25: getfield #10; //حقل هذا$0:LOuterClass;
28: invocstatic #39; //Method OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
31: استدعاء افتراضي #33; //الطريقة java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
34: استدعاء افتراضي #42; // الطريقة java/lang/StringBuilder.toString:()Ljava/lang/String;
37: أستور_1
38: getstatic #46; //Field java/lang/System.out:Ljava/io/PrintStream;
41: تحميل_1
42: استدعاء افتراضي #52; //الطريقة java/io/PrintStream.println:(Ljava/lang/String;)V
45 : العودة
}
تستدعي التعليمة البرمجية التالية رمز الوصول $0، والغرض منه هو الحصول على سمة اللغة الخاصة لـ OuterClass.
انسخ رمز الكود كما يلي:
13: invocstatic #27; //Method OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
تستدعي التعليمة البرمجية التالية رمز الوصول $1، والغرض منه هو الحصول على سمة المنطقة الخاصة لـ OutherClass.
انسخ رمز الكود كما يلي:
28: invocstatic #39; //Method OuterClass.access$1:(LOuterClass;)Ljava/lang/String;
ملاحظة: عند إنشاء الفئة الداخلية، سيتم تمرير مرجع الفئة الخارجية واستخدامه كسمة للفئة الداخلية، وبالتالي فإن الفئة الداخلية ستحتفظ بمرجع إلى فئتها الخارجية.
هذا $0 هو مرجع الفئة الخارجية الذي تحتفظ به الفئة الداخلية، ويتم تمرير المرجع وتعيينه من خلال طريقة المنشئ.
انسخ رمز الكود كما يلي:
الفئة الخارجية النهائية this$0;
public OuterClass$InnerClass(OuterClass);
شفرة:
0: التحميل_0
1: تحميل_1
2: putfield #10; //حقل هذا$0:LOuterClass;
5: تحميل_0
6: استدعاء خاص #12; //الطريقة java/lang/Object."<init>":()V
9: العودة
ملخص
يبدو أن هذا الجزء الخاص غير صالح، لكنه في الواقع ليس صالحًا، لأنه عندما تستدعي الطبقة الداخلية الملكية الخاصة للفئة الخارجية، فإن تنفيذها الحقيقي هو استدعاء الطريقة الثابتة للخاصية التي أنشأها المترجم (أي الوصول $0، الوصول إلى $1، وما إلى ذلك) للحصول على قيم السمات هذه. هذه كلها معالجة خاصة بواسطة المترجم.
لا يعمل هذه المرة؟
إذا كانت طريقة الكتابة المذكورة أعلاه شائعة جدًا، فنادرًا ما يتم استخدام طريقة الكتابة هذه، ولكن يمكن تشغيلها.
انسخ رمز الكود كما يلي:
الطبقة العامة AnotherOuterClass {
public static void main(String[] args) {
InnerClass الداخلي = جديد AnotherOuterClass().new InnerClass();
System.out.println("InnerClass Filed = "+inner.x);
}
الطبقة الداخلية {
كثافة العمليات الخاصة س = 10؛
}
}
كما هو مذكور أعلاه، استخدم javap للفك وإلقاء نظرة. ولكن هذه المرة دعونا نلقي نظرة على نتائج InnerClass. انسخ الكود كما يلي:
16:03 $ javap -c AnotherOuterClass/$InnerClass
تم تجميعها من "AnotherOuterClass.java"
فئة AnotherOuterClass$InnerClass يمتد java.lang.Object{
Final AnotherOuterClass this$0;
AnotherOuterClass$InnerClass(AnotherOuterClass);
شفرة:
0: التحميل_0
1: تحميل_1
2: putfield #12; // الحقل هذا$0:LANotherOuterClass;
5: تحميل_0
6: استدعاء خاص #14; //الطريقة java/lang/Object."<init>":()V
9: التحميل_0
10: بيبوش 10
12: حقل #17; //الحقل x:I
15: العودة
static int access$0(AnotherOuterClass$InnerClass);
شفرة:
0: التحميل_0
1: getfield #17; //الحقل x:I
4: العودة
}
لقد حدث ذلك مرة أخرى، وقام المترجم تلقائيًا بإنشاء طريقة مستتر للوصول إلى $0 للحصول على السمة الخاصة مرة واحدة للحصول على قيمة x.
رمز نسخة نتيجة إلغاء الترجمة لـ AnotherOuterClass.class هو كما يلي:
16:08 $ javap -c AnotherOuterClass
تم تجميعها من "AnotherOuterClass.java"
الطبقة العامة AnotherOuterClass يمتد java.lang.Object{
public AnotherOuterClass();
شفرة:
0: التحميل_0
1: استدعاء خاص #8; //الطريقة java/lang/Object."<init>":()V
4: العودة
public static void main(java.lang.String[]);
شفرة:
0: جديد #16؛ //فئة AnotherOuterClass$InnerClass
3: مزدوج
4: جديد #1؛ // فئة أخرىOuterClass
7: دوب
8: استدعاء خاص #18; // الطريقة "<init>":()V
11: دوب
12: استدعاء افتراضي #19; // الطريقة java/lang/Object.getClass:()Ljava/lang/Class;
15: البوب
16: invocespecial #23; //Method AnotherOuterClass$InnerClass."<init>":(LANotherOuterClass;)V
19: أستور_1
20: getstatic #26; //Field java/lang/System.out:Ljava/io/PrintStream;
23: جديد #32؛ //class java/lang/StringBuilder
26: مكرر
27: ldc #34; // String InnerClass Filed =
29: استدعاء خاص #36; //الطريقة java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
32: aload_1
33: invocstatic #39; // طريقة AnotherOuterClass$InnerClass.access$0:(LANotherOuterClass$InnerClass;)I
36: استدعاء افتراضي #43; //الطريقة java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
39: استدعاء افتراضي #47; // الطريقة java/lang/StringBuilder.toString:()Ljava/lang/String;
42: استدعاء افتراضي #51; //الطريقة java/io/PrintStream.println:(Ljava/lang/String;)V
45 : العودة
}
هذا الاستدعاء هو عملية الطبقة الخارجية للحصول على الخاصية الخاصة x من خلال مثيل الفئة الداخلية، انسخ الكود كما يلي:
33: invocstatic #39; // طريقة AnotherOuterClass$InnerClass.access$0:(LANotherOuterClass$InnerClass;)I
ملخص آخر
من بينها، تحتوي وثائق Java الرسمية على هذه الجملة. انسخ الكود كما يلي:
إذا تم إعلان العضو أو المنشئ خاصًا، فسيتم السماح بالوصول فقط إذا حدث داخل نص فئة المستوى الأعلى (الفقرة 7.6) التي تتضمن إعلان العضو أو المنشئ.
هذا يعني أنه إذا تم تعيين الأعضاء والمنشئين (من الفئة الداخلية) على معدلات خاصة، فسيتم السماح بالوصول فقط إذا كانت الطبقة الخارجية الخاصة بهم.
كيفية منع الوصول إلى الأعضاء الخاصين في الطبقات الداخلية من قبل الغرباء
أعتقد أنه بعد قراءة الجزأين أعلاه، ستشعر أنه من الصعب جدًا الوصول إلى أعضاء الطبقة الداخلية من خلال الطبقة الخارجية. من يريد أن يكون المترجم "فضوليًا" في الواقع؟ منتهي. وهذا يعني استخدام الطبقات الداخلية المجهولة.
نظرًا لأن نوع كائن mRunnable هو Runnable، وليس نوع الفئة الداخلية المجهولة (والتي لا يمكننا الحصول عليها بشكل طبيعي)، ولا توجد سمة x في Runanble، فإن mRunnable.x غير مسموح به.
انسخ رمز الكود كما يلي:
الطبقة العامة PrivateToOuter {
Runnable mRunnable = جديد Runnable(){
كثافة العمليات الخاصة x=10;
@تجاوز
تشغيل الفراغ العام () {
System.out.println(x);
}
};
public static void main(String[] args){
PrivateToOuter p = new PrivateToOuter();
//System.out.println("فئة مجهولة خاصة قدمت = "+ p.mRunnable.x); // غير مسموح به
p.mRunnable.run(); // مسموح به
}
}
الملخص النهائي
في هذه المقالة، يبدو أن الخاصية غير صالحة ظاهريًا، ولكنها في الواقع ليست كذلك، وبدلاً من ذلك، يتم الحصول على الخصائص الخاصة من خلال طرق غير مباشرة عند الاتصال.
تحتوي فئات Java الداخلية على تطبيقات للفئات الخارجية عند إنشائها، لكن C++ لا تختلف عن C++.