ما هي الطريقة الافتراضية؟
بعد إصدار Java 8، يمكن إضافة أساليب جديدة إلى الواجهة، لكن الواجهة ستظل متوافقة مع فئة التنفيذ الخاصة بها. يعد هذا أمرًا مهمًا لأن المكتبة التي تقوم بتطويرها قد يتم استخدامها على نطاق واسع بواسطة العديد من المطورين. قبل Java 8، بعد نشر الواجهة في مكتبة الفصل، إذا تمت إضافة طريقة جديدة إلى الواجهة، فإن التطبيقات التي نفذت هذه الواجهة ستكون معرضة لخطر التعطل باستخدام الإصدار الجديد من الواجهة.
مع Java 8، ألا يوجد مثل هذا الخطر؟ الجواب هو لا.
قد تؤدي إضافة الأساليب الافتراضية إلى الواجهة إلى جعل بعض فئات التنفيذ غير متاحة.
أولاً، دعونا نلقي نظرة على تفاصيل الطريقة الافتراضية.
في Java 8، يمكن تنفيذ الأساليب في الواجهات (يمكن أيضًا تنفيذ الأساليب الثابتة في Java 8 في الواجهات، ولكن هذا موضوع آخر). تسمى الطريقة المطبقة في الواجهة الطريقة الافتراضية، والتي يتم تحديدها باستخدام الكلمة الأساسية الافتراضية كمعدل. عندما يقوم الفصل بتنفيذ واجهة، يمكنه تنفيذ الأساليب التي تم تنفيذها بالفعل في الواجهة، ولكن هذا غير مطلوب. سوف ترث هذه الفئة الطريقة الافتراضية. ولهذا السبب، عندما تتغير الواجهة، لا يلزم تغيير فئة التنفيذ.
ماذا عن الميراث المتعدد؟
عندما يطبق الفصل أكثر من واجهة واحدة (مثل اثنتين)، وتكون هذه الواجهات لها نفس الطريقة الافتراضية، تصبح الأمور معقدة للغاية. ما هي الطريقة الافتراضية التي يرثها الفصل؟ لا! في هذه الحالة، يجب على الفئة نفسها (إما بشكل مباشر أو فئة أعلى في شجرة الميراث) تنفيذ الطريقة الافتراضية.
وينطبق الشيء نفسه عندما تقوم إحدى الواجهات بتنفيذ الطريقة الافتراضية وتعلن واجهة أخرى أن الطريقة الافتراضية مجردة. يحاول Java 8 تجنب الغموض والحفاظ على الدقة. إذا تم الإعلان عن طريقة ما في واجهات متعددة، فلن يتم توريث أي من التطبيقات الافتراضية وستحصل على خطأ في وقت الترجمة.
ومع ذلك، إذا قمت بتجميع الفصل الدراسي الخاص بك، فلن تكون هناك أخطاء في وقت الترجمة. في هذه المرحلة، Java 8 غير متناسق. لها أسبابها الخاصة، وهناك أسباب مختلفة لا أريد أن أشرحها بالتفصيل أو أناقشها بعمق هنا (لأنه: تم إصدار النسخة، ووقت المناقشة طويل جدًا، ولم يسبق لهذا النظام الأساسي أن حدث ذلك من قبل). مثل هذه المناقشة).
1. افترض أن لديك واجهتين وفئة تنفيذ.
2. تطبق إحدى الواجهات الطريقة الافتراضية m().
3. قم بتجميع الواجهة وفئة التنفيذ معًا.
4. قم بتعديل الواجهة التي لا تحتوي على طريقة m() وأعلن أن طريقة m() مجردة.
5. أعد ترجمة الواجهة المعدلة بشكل منفصل.
6. قم بتشغيل فئة التنفيذ.
1. قم بتعديل الواجهة التي تحتوي على الطريقة المجردة m() وإنشاء تطبيق افتراضي.
2. قم بتجميع الواجهة المعدلة
3. تشغيل الفصل: فشل.
عندما توفر واجهتان تطبيقًا افتراضيًا لنفس الطريقة، لا يمكن استدعاء هذه الطريقة إلا إذا قامت فئة التنفيذ أيضًا بتنفيذ الطريقة الافتراضية (إما بشكل مباشر أو عن طريق فئة ذات مستوى أعلى في شجرة الميراث).
رمز المثال:
من أجل توضيح المثال أعلاه، قمت بإنشاء دليل اختبار لـ C.java، ويوجد تحته 3 أدلة فرعية لتخزين I1.java وI2.java. يحتوي دليل الاختبار على الكود المصدري C.java للفئة C. يحتوي الدليل الأساسي على إصدار الواجهة التي يمكن تجميعها وتشغيلها. يحتوي I1 على الأسلوب m() مع التنفيذ الافتراضي، ولا يحتوي I2 على أية أساليب.
تحتوي فئة التنفيذ على الطريقة الرئيسية حتى نتمكن من تنفيذها في اختباراتنا. سوف يتحقق مما إذا كانت هناك معلمات سطر الأوامر، حتى نتمكن بسهولة من إجراء اختبارات استدعاء m() وعدم استدعاء m().
انسخ رمز الكود كما يلي:
~/github/test$ cat C.java
الطبقة العامة C تنفذ I1، I2 {
public static void main(String[] args) {
ج ج = جديد C();
إذا (args.length == 0){
سم ()؛
}
}
}
~/github/test$ cat base/I1.java
الواجهة العامة I1 {
الفراغ الافتراضي م () {
System.out.println("مرحبًا بالواجهة 1");
}
}
~/github/test$ cat base/I2.java
الواجهة العامة I2 {
}
استخدم سطر الأوامر التالي للتجميع والتشغيل:
انسخ الكود كما يلي:~/github/test$ javac -cp .:base C.java
~/github/test$ java -cp .:base C
مرحبا واجهة 1
يحتوي الدليل المتوافق على واجهة I2 بطريقة مجردة m() وواجهة I1 غير المعدلة.
انسخ الكود كما يلي:~/github/test$ cat متوافق/I2.java
الواجهة العامة I2 {
باطلة م ()؛
}
لا يمكن استخدام هذا لتجميع الفئة C:
انسخ الكود كما يلي:~/github/test$ javac -cp .:compatible C.java
C.java:1: خطأ: لغة C ليست مجردة ولا تتجاوز الطريقة المجردة m() في I2
الطبقة العامة C تنفذ I1، I2 {
^
خطأ واحد
رسالة الخطأ دقيقة للغاية. نظرًا لأننا حصلنا على فئة C. في التجميع السابق، إذا قمنا بتجميع الواجهات في الدليل المتوافق، فسنظل نحصل على واجهتين يمكنها تشغيل فئة التنفيذ:
انسخ رمز الكود كما يلي:
~/github/test$ javac متوافق/I*.java
~/github/test$ java -cp .:متوافق مع C
مرحبا واجهة 1
يحتوي الدليل الثالث المسمى خطأ على واجهة I2 التي تحدد أيضًا طريقة m():
انسخ رمز الكود كما يلي:
~/github/test$ cat error/I2.java
الواجهة العامة I2 {
الفراغ الافتراضي م () {
System.out.println("مرحبًا بالواجهة 2");
}
}
يجب أن نأخذ عناء تجميعها. على الرغم من تعريف طريقة m() مرتين، إلا أنه لا يزال من الممكن تشغيل فئة التنفيذ طالما أنها لا تستدعي الطريقة المحددة عدة مرات. ومع ذلك، طالما أننا نسميها طريقة m()، فسوف تفشل على الفور. فيما يلي معلمات سطر الأوامر التي نستخدمها:
انسخ رمز الكود كما يلي:
~/github/test$ javac error/*.java
~/github/test$ java -cp .:خاطئ C
استثناء في مؤشر الترابط "الرئيسي" java.lang.IncompatibleClassChangeError: متعارض
الطرق الافتراضية: I1.m I2.m
في سم (C.java)
في C.main(C.java:5)
~/github/test$ java -cp .:wrong C x
~/جيثب/اختبار$
ختاماً
عندما تقوم بنقل مكتبة فئة تضيف تطبيقًا افتراضيًا إلى واجهة إلى بيئة Java 8، فلن تكون هناك مشكلة بشكل عام. على الأقل هذا ما اعتقده مطورو مكتبة فئة Java8 عندما أضافوا الطرق الافتراضية إلى فئات المجموعة. لا تزال التطبيقات التي تستخدم مكتبتك تعتمد على مكتبات Java 7 التي لا تحتوي على طرق افتراضية. عند استخدام وتعديل عدة مكتبات فئات مختلفة، هناك احتمال ضئيل بحدوث تعارضات. كيف يمكن تجنب ذلك؟
صمم مكتبة صفك كما كان من قبل. لا تأخذ الأمر على محمل الجد عند الاعتماد على الطريقة الافتراضية. لا تستخدم كملاذ أخير. اختر أسماء الطرق بحكمة لتجنب التعارضات مع الواجهات الأخرى. سوف نتعلم كيفية استخدام هذه الميزة للتطوير في برمجة Java.