عند النظر في تهيئة الفصل ، نعلم جميعًا أنه عند إجراء تهيئة الفئة الفرعية ، إذا لم تتم تهيئة الفئة الأصل ، فيجب تهيئة الفئة الفرعية أولاً. ومع ذلك ، فإن الأمور ليست بسيطة مثل جملة واحدة.
أولاً ، دعونا نلقي نظرة على شروط التهيئة التي تسبب في Java:
(1) عند استخدام كائنات جديدة إلى إنشاء بيانات وطرق ثابتة ، أي عند مواجهة التعليمات: جديدة ، getstatic/putstatic و voncestatic ؛
(2) عند استخدام انعكاس لاستدعاء الفصل ؛
(3) عند تهيئة فئة ، إذا لم تتم تهيئة الفئة الأصل ، فسيتم تشغيل تهيئة الفئة الأصل أولاً ؛
(4) تقع الفئة التي يتم فيها تنفيذ الطريقة الرئيسية للدخول ؛
(5) الفئة التي يوجد فيها مقبض الطريقة في الدعم اللغوي الديناميكي لـ JDK1.7 ، إذا لم يتم تشغيل التهيئة ؛
بعد التجميع ، يتم إنشاء طريقة <clinit> ، ويتم تنفيذ تهيئة الفصل في هذه الطريقة. يتم تنفيذ هذه الطريقة فقط ، ويضمن JVM هذا ويؤدي التحكم في التزامن ؛
من بينها ، الحالة (3) ، من وجهة نظر استدعاء الطريقة ، هي الفئة الفرعية <Clinit> التي تستدعي بشكل متكرر الفئة الأصل <Clinit> في البداية ، والتي تشبه حقيقة أنه يجب علينا أولاً استدعاء مُنشئ الفئة الأصل في مُنشئ الفئة الفرعية ؛
ولكن تجدر الإشارة إلى أن "التشغيل" لا يكتمل التهيئة ، مما يعني أنه من الممكن أن ينتهي تهيئة الفئة الفرعية مقدمًا قبل تهيئة الفئة الأصل ، وهو المكان الذي يكمن فيه "الخطر".
1. مثال على تهيئة الفصل:
في هذا المثال ، أستخدم فئة طرفية لاحتواء فئتين ثابتتين من الأعضاء مع علاقات الميراث. نظرًا لأن تهيئة الفئة المحيطية والفئات الأعضاء الثابتة ليس لها علاقة سببية ، فهي آمنة ومريحة لإظهارها مثل هذا ؛
تحتوي الفئة A و Child Class B على التوالي على وظائف رئيسية. من حالة التشغيل أعلاه (4) ، يمكن ملاحظة أن مسارات تهيئة الطبقة المختلفة يتم تشغيلها عن طريق استدعاء هاتين وظيفتين رئيسيتين على التوالي ؛
المشكلة في هذا المثال هي أن الفئة الأصل تحتوي على المرجع الثابت لفئة الطفل وتهيئته في التعريف:
الفئة العامة clospperClass {private static class a {static {system.out.println ("class a initialization start ...") ؛ } // تحتوي الفئة الأصل على مراجع ثابتة لفئة الطفل الثابتة B B = جديدة B () ؛ محمي ثابت int int = 9 ؛ static {system.out.println ("Class A Enlomization End ...") ؛ } MAIN Public Static Void Main (String [] args) {}} الفئة الثابتة الخاصة ب {static {system.out.println ("بدء تهيئة الفئة B ...") ؛ } // يعتمد مجال الفئة الفرعية على مجال الفئة الأصل الخاصة الثابتة int bint = 9 + A.aint ؛ Public B () {// يعتمد المجال الثابت للمُنشئ على نظام الفصل. } static {system.out.println ("تنتهي تهيئة الفئة B ..." + "قيمة AINT:" + bint) ؛ } الفراغ الثابت العام (سلسلة [] args) {}}} السيناريو 1: نتيجة الإخراج عندما يكون الإدخال وظيفة رئيسية للفئة ب:
/** * تبدأ التهيئة A Class A ... * يدعو مُنشئ الفئة B قيمة BINT 0 * CLASS A تنتهي التهيئة ... * يبدأ تهيئة الفئة B ...
التحليل: يمكن أن نرى أن نداء الوظيفة الرئيسية يؤدي إلى تهيئة الفئة B وتدخل في طريقة <clinit> من الفئة B. من الفئة A ، حيث أن فئة الأم ، تبدأ في التهيئة أولاً وتدخل في طريقة <clinit> لـ A ، وهناك عبارة جديدة B () ؛ في هذا الوقت ، سيتم إنشاء إنشاء B ، والذي يوجد بالفعل في <Clinit> من الفئة B. حصل الخيط الرئيسي على القفل وبدأ في تنفيذ <clinit> من الفئة B. في البداية أن JVM ستضمن تنفيذ طريقة التهيئة للفئة مرة واحدة فقط. بعد تلقي التعليمات الجديدة ، لن يدخل JVM طريقة <Clinit> للفئة B مرة أخرى ولكن سيتم إنشاء مثيل لها مباشرة. ومع ذلك ، في هذا الوقت ، لم يكمل الفئة B تهيئة الفصل ، بحيث يمكنك أن ترى أن قيمة BINT هي 0 (هذا 0 هو التهيئة الصفرية التي يتم تنفيذها بعد تخصيص ذاكرة منطقة الطريقة أثناء مرحلة التحضير لتحميل الفصل) ؛
لذلك ، يمكن أن نستنتج أن فئة الأصل تحتوي على المجال الثابت لنوع الطفل ويقوم بإجراء التخصيص ، مما قد يتسبب في تنفيذ تثبيت الفئة الفرعية قبل اكتمال تهيئة الفصل ؛
السيناريو 2: نتيجة الإخراج عندما يكون الإدخال وظيفة رئيسية للفئة A:
/** * تبدأ التهيئة A Class A ... * تبدأ التهيئة الفئة B ... * تنتهي التهيئة الفئة B ... قيمة AINT: 9 * يدعو مُنشئ الفئة B قيمة BINT 9 * CLASS A تنتهي التهيئة ... */
التحليل: بعد تحليل السيناريو 1 ، نعلم أن تهيئة تهيئة الفئة A عن طريق تهيئة الفئة B سيؤدي إلى إنشاء إنشاء متغير فئة B في الفئة A قبل اكتمال تهيئة الفئة B. لذلك إذا تمت تهيئة الفئة A أولاً ، فهل يمكن تشغيل الفئة B أولاً عند إنشاء إنشاء فئة متغيرة ، بحيث يتم التهيئة قبل الاستئصال؟ الجواب نعم ، ولكن لا تزال هناك مشاكل.
وفقًا للإخراج ، يمكننا أن نرى أن تهيئة الفئة B يتم تنفيذها قبل اكتمال تهيئة الفئة A ، والتي تسبب متغيرات مثل متغير الفئة الذي يتم تهيئته فقط بعد تهيئة الفئة B ، وبالتالي فإن قيمة AINT التي تم الحصول عليها بواسطة BINT المجال في الفئة B هي "0" ، بدلاً من "18" كما توقعنا ؛
الخلاصة: باختصار ، يمكن أن نستنتج أنه من الخطير للغاية تضمين متغيرات الطبقة لأنواع الفئات الفرعية في فئة الأصل وتثبيتها عند تحديدها. قد لا يكون الموقف المحدد واضحًا مثل مثال. استدعاء طرق تعيين القيم في التعريف أمر خطير أيضًا. حتى إذا كنت ترغب في تضمين مجالات ثابتة لأنواع الفئات الفرعية ، فيجب عليك أيضًا تعيين القيم من خلال طرق ثابتة ، لأن JVM يمكن أن يضمن اكتمال جميع إجراءات التهيئة قبل أن تسمى الطريقة الثابتة (بالطبع ، هذا الضمان هو أنه يجب ألا تتضمن ثابتًا B B = جديد B () ؛ سلوك التهيئة هذا) ؛
2. مثال على أساس:
أولاً ، تحتاج إلى معرفة عملية إنشاء الكائن:
(1) عند مواجهة تعليمات جديدة ، تحقق مما إذا كان الفصل قد أكمل التحميل والتحقق والتحضير والتحليل والتهيئة (عملية التحليل هي تحليل مرجع الرمز في مرجع مباشر ، مثل اسم الطريقة هي مرجع رمز ، والذي يمكن تنفيذه عند استخدام مرجع الرمز هذا بعد اكتمال التهيئة ، على وجه التحديد لدعم الديناميكي) ، يتم نقل هذه العمليات قبل الانتهاء منها ؛
(2) تخصيص الذاكرة ، واستخدم القائمة المجانية أو طريقة تصادم المؤشر ، و "صفر" الذاكرة المخصصة حديثًا. لذلك ، تتم تهيئة جميع متغيرات المثيل إلى 0 بشكل افتراضي (المشار إليها على أنها خالية) في هذا الرابط ؛
(3) تنفيذ طريقة <Ing> ، بما في ذلك التحقق من المكالمة إلى طريقة <Ing> (مُنشئ) للفئة الأصل ، وإجراءات المهمة المحددة بواسطة متغير المثيل ، يتم تنفيذ Instantiator في Instantiator ، وأخيراً استدعاء الإجراءات في المنشئ.
قد يكون هذا المثال أكثر شهرة ، أي أنه ينتهك "لا تسمي أساليب قابلة للتجاوز في المُنشئ وطريقة الاستنساخ وطريقة ReadObject". والسبب هو أن تعدد الأشكال في جافا ، أي الربط الديناميكي.
يحتوي مُنشئ الفئة A من الفئة A على طريقة محمية ، والفئة B هي فئة الفرعية.
الطبقة العامة kringInstantiation {private static class a {public a () {dosomething () ؛ } void void dosomething () {system.out.println ("a's dosomething") ؛ }} الفئة الثابتة الخاصة ب يمتد {private int bint = 9 ؛ Override محمي void dosomething () {system.out.println ("b's dosomething ، bint:" + bint) ؛ }} public static void main (string [] args) {b b = new b () ؛ }}نتيجة الإخراج:
/** * B's dosomething ، Bint: 0 */
التحليل: أولاً وقبل كل شيء ، تحتاج إلى معرفة أنه عندما لا يكون هناك عرض ، فإن برنامج التحويل البرمجي Java سيقوم بإنشاء المُنشئ الافتراضي واتصل بمنشئ الفئة الأم في البداية. لذلك ، سوف يدعو مُنشئ الفئة B إلى مُنشئ الفئة A أولاً في البداية.
تسمى الطريقة المحمية Dosomething في الفئة A. من نتيجة الإخراج ، نرى أن تنفيذ الطريقة للفئة الفرعية يسمى فعليًا ، ولم يبدأ إنشاء الفئة الفرعية بعد ، لذا فإن Bint ليس 9 كـ "متوقع" ولكن 0 ؛
هذا بسبب الربط الديناميكي ، فإن dosomething هو طريقة محمية ، لذلك يطلق عليها من خلال التوجيه invokevirtual ، الذي يجد تطبيق الطريقة المقابلة استنادًا إلى نوع مثيل الكائن (هنا كائن مثيل B ، والطريقة المقابلة هي تنفيذ الطريقة للفئة ب) ، لذلك هذه النتيجة هي.
الخلاصة: كما ذكرنا سابقًا ، "لا تتصل بالأساليب القابلة للتجاوز في مُنشئ ، طريقة الاستنساخ وطريقة ReadObject".
ما ورد أعلاه هما "حقول الألغام" في تهيئة فئة Java وتثبيتها المقدمة لك. آمل أن يكون ذلك مفيدًا لتعلم الجميع.