تضيف الطريقة الافتراضية ميزة جديدة جيدة جدًا إلى مجموعة تعليمات JVM. بعد استخدام الطريقة الافتراضية ، إذا تمت إضافة طريقة جديدة إلى الواجهة في المكتبة ، يمكن لفئة المستخدم التي تنفذ هذه الواجهة الحصول تلقائيًا على التنفيذ الافتراضي لهذه الطريقة. بمجرد أن يرغب المستخدم في تحديث فئة التنفيذ الخاصة به ، فقط تجاوز هذه الطريقة الافتراضية ، واستبدالها بتنفيذ يجعل أكثر جدوى في سيناريو محدد. والأفضل من ذلك هو أنه يمكن للمستخدمين استدعاء التنفيذ الافتراضي للواجهة في طريقة إعادة الكتابة لإضافة بعض الوظائف الإضافية.
كل شيء جيد جدًا حتى الآن. ومع ذلك ، قد يؤدي إضافة الطرق الافتراضية إلى واجهات Java الحالية إلى عدم توافق الكود . من السهل أن نفهم من خلال النظر في مثال. لنفترض أن هناك مكتبة تتطلب من المستخدم تنفيذ أحد واجهاته كمدخلات:
واجهة SimpleInput {void foo () ؛ void bar () ؛} class simpleinputAdapter التجريدي ينفذ SimpleInput {Override public void bar () {// بعض السلوك الافتراضي ...}}قبل Java 8 ، كان الجمع بين الواجهة أعلاه وفئة المحول المقابلة نمطًا شائعًا جدًا في لغة Java. يوفر مطورو مكتبة الفصل محولًا لتقليل كمية الترميز لمستهلكي المكتبة. ومع ذلك ، فإن الغرض من توفير هذه الواجهة هو في الواقع تحقيق علاقة مشابهة للميراث المتعدد.
لنفترض أن المستخدم يستخدم هذا المحول:
class myinput يمتد SimpleInputAdapter {Override public void foo () {// افعل شيئًا ... everride public void bar () {super.bar () ؛ // افعل شيئًا إضافيًا ...}}مع هذا التنفيذ ، يمكن للمستخدمين التفاعل مع المكتبة. لاحظ كيف يتجاوز هذا التنفيذ طريقة الشريط لإضافة وظائف إضافية إلى التنفيذ الافتراضي.
إذن ماذا سيحدث إذا تم ترحيل هذه المكتبة إلى Java 8؟ بادئ ذي بدء ، من المحتمل أن تتجاهل هذه المكتبة فئة المحول وترحيل هذه الوظيفة إلى الطريقة الافتراضية. في النهاية ، ستبدو هذه الواجهة هكذا:
واجهة SimpleInput {void foo () ؛ شريط void الافتراضي () {// بعض السلوك الافتراضي}}}باستخدام هذه الواجهة الجديدة ، يتعين على المستخدم تحديث رمزه لاستخدام هذه الطريقة الافتراضية بدلاً من فئة المحول. واحدة من الفوائد الرائعة لاستخدام واجهة جديدة بدلاً من فئة المحول هي أنه يمكن للمستخدمين أن يرثوا فئة أخرى بدلاً من فئة المحول هذه. لنبدأ بممارسة فئة myinput وتحويلها إلى استخدام الطريقة الافتراضية. نظرًا لأننا نستطيع الآن أن نرث فصولًا أخرى ، سنحاول تمديد فئة قاعدة ثالثة إضافية. ما يفعله هذه الفئة الأساسية ليس مهمًا هنا. دعنا نفترض أن هذا منطقي لحالة الاستخدام لدينا.
Class MyInput يمتد ThirdPartyBaseClass ينفذ SimpleInput {Override public void foo () {// do something ...} override public void bar () {simpleinput.super.foo () ؛ // افعل شيئًا إضافيًا ...}}من أجل تحقيق نفس الوظيفة مثل الفئة الأصلية ، استخدمنا بناء جملة Java 8 الجديد للاتصال بالطرق الافتراضية للواجهة. وبالمثل ، وضعنا منطق mymethod في فئة أساسية myBase. يمكنك الاسترخاء بعد قصف كتفيك. كان رائعا بعد إعادة البناء!
تم تحسين هذه المكتبة التي نستخدمها بشكل كبير. ومع ذلك ، يحتاج موظفو الصيانة إلى إضافة واجهة أخرى لتنفيذ بعض الوظائف الإضافية. تسمى هذه الواجهة compexinput ، والتي ترث فئة SimpleInput وتضيف طريقة إضافية. نظرًا لأنه يُعتقد عمومًا أنه يمكن إضافة الطريقة الافتراضية بثقة ، فقد تجاوز موظفو الصيانة الطريقة الافتراضية لفئة SimpleInput وإضافة بعض الإجراءات الإضافية لتزويد المستخدم بتطبيق افتراضي أفضل. بعد كل شيء ، هذا أمر شائع جدًا عند استخدام فئات المحول:
Interface ComplexInput يمتد SimpleInput {void qux () ؛ Override void bar () {simpleinput.super.bar () ؛ // معقدة جدًا ، نحتاج إلى بذل المزيد من الجهد ...}}تبدو هذه الميزة الجديدة جيدة جدًا ، لذا قرر المشرف على فئة ThirdPartyBaseClass أيضًا استخدام هذه المكتبة. لتحقيق ذلك ، قام بتنفيذ فئة ThirdPartyBaseClass إلى واجهة ComplexInput.
ولكن ماذا يعني هذا بالنسبة لفئة myinput؟ نظرًا لأنها ترث فئة ThirdPartyBaseClass ، يتم تنفيذ واجهة ComplexInput افتراضيًا. وبهذه الطريقة ، فإن استدعاء الطريقة الافتراضية لـ SimpleInput ليس قانونيًا. والنتيجة هي أنه لا يمكن تجميع رمز المستخدم في النهاية. أيضًا ، من المستحيل الاتصال بهذه الطريقة تمامًا ، لأن Java تعتبر هذه الطريقة الفائقة الفائقة لاستدعاء فئة الوالدين غير المباشرة غير قانونية. يمكنك فقط استدعاء الطريقة الافتراضية لواجهة ComplexInput. ومع ذلك ، فإن هذا يتطلب منك أولاً تنفيذ هذه الواجهة بشكل صريح في فئة myinput. بالنسبة لمستخدمي هذه المكتبة ، فإن هذه التغييرات غير متوقعة تمامًا.
(ملاحظة: بعبارة ببساطة ، إنه في الواقع:
واجهة A {الافتراضية اختبار void () {}} BENFAFE B يمتد اختبار void الافتراضي () {}} يقوم اختبار الفئة العامة بتنفيذ B {public void test () {B.Super.Test () ؛ //a.super.test () ؛ خطأ }}بالطبع ، إذا كتبت هذا ، فقد اختار المستخدم بنشاط تنفيذ واجهة B. قدم المثال في المقالة فئة أساسية ، لذلك تم إجراء تغيير غير متلازمة على ما يبدو في المكتبة والفئة الأساسية ، ولكنه في الواقع تسبب في عدم قدرة رمز المستخدم على تجميعها)
الغريب ، جافا لا تميز هذا في وقت التشغيل. يتيح مدقق JVM للفئة المترجمة استدعاء طريقة SimpleInput :: FOO ، على الرغم من أن الفئة المحملة ترث الإصدار المحدث من ThirdPartyBaseClass ويقوم ضمنيًا بتنفيذ واجهة المركب. إذا كنت ترغب في إلقاء اللوم ، يمكنك فقط إلقاء اللوم على المترجم. (ملحوظة: المترجم وسلوك وقت التشغيل غير متناسقين)
إذن ما الذي تعلمناه منه؟ ببساطة ، لا تتجاوز الطريقة الافتراضية للواجهة الأصلية في واجهة أخرى. لا تعيد كتابتها بطريقة افتراضية أخرى ، ولا تعيد كتابتها باستخدام طريقة تجريدية. باختصار ، يجب أن تكون حذراً للغاية عند استخدام الطريقة الافتراضية. على الرغم من أنها تجعل واجهات مكتبات التجميع الحالية في Java أسهل في تحسينها ، إلا أنها تتيح لك إجراء مكالمات الطريقة في بنية الميراث للفئة ، مما يزيد بشكل أساسي من التعقيد. قبل Java 7 ، كان عليك فقط اجتياز التسلسل الهرمي للفئة الخطية والنظر إلى رمز الاتصال الفعلي. عندما تشعر أنك بحاجة إليها حقًا ، استخدم الطريقة الافتراضية.
ما سبق هو شرح مفصل لسبب استخدام الطريقة الافتراضية لـ Java 8 بحذر. آمل أن يكون ذلك مفيدًا لتعلم الجميع.