لماذا توجد طريقة افتراضية؟
قادم Java 8. كما ذكرنا سابقًا ، ناقشنا الكثير حول هذا الموضوع من قبل ، لكن تعبير Lambdas ليس هو التغيير الوحيد لقواعد اللعبة في Java 8.
لنفترض أن Java 8 قد تم إصداره ويحتوي على Lambda. الآن تخطط لاستخدام Lambda.
قائمة <؟> قائمة = ... list.faceach (...) ؛
لا يمكن العثور على تعريف foreach في java.util.list أو java.util.collection. عادة ما يكون حلًا لإضافة طرق جديدة وتنفيذ الواجهات ذات الصلة في JDK. ومع ذلك ، بالنسبة لإصدار الإصدار ، من المستحيل إضافة طريقة جديدة إلى الواجهة مع عدم التأثير على التنفيذ الحالي.
لذلك ، إذا تم استخدام Lambda في Java 8 ، فلا يمكن استخدامه في مكتبة التجميع بسبب التوافق الأمامي.
للأسباب المذكورة أعلاه ، تم تقديم مفهوم جديد. يمكن الآن إضافة طريقة التمديد الظاهري ، والتي عادة ما تكون طريقة الدفاع ، إلى الواجهة ، بحيث يمكن توفير التنفيذ الافتراضي لسلوك البيان.
ببساطة ، يمكن لواجهة Java الآن تطبيق الطريقة. ميزة الطريقة الافتراضية هي أنه يمكنه إضافة طريقة افتراضية جديدة إلى الواجهة دون تدمير تنفيذ الواجهة.
في رأيي ، هذا ليس نوعًا من خاصية Java التي سيتم استخدامها كل يوم ، ولكن يمكن أن تستخدم بالتأكيد Lambda لاستخدام Lambda بشكل طبيعي.
أبسط مثال
دعونا نرى أبسط مثال: واجهة A ، فئة Clazz تنفذ الواجهة A.
الواجهة العامة A {افتراضي void foo () {system.out.println ("Calling A.FOO ()") ؛} فئة الفئة العامة A {}يمكن تجميع الكود ، حتى لو لم تنفذ فئة Clazz طريقة FOO (). يتم توفير التنفيذ الافتراضي لطريقة FOO () في الواجهة أ.
رمز العميل باستخدام هذا المثال:
Clazz Clazz = New Clazz () ؛
ميراث متعدد؟
هناك سؤال شائع: سيسأل الناس لأول مرة الميزات الجديدة للطريقة الافتراضية "إذا كان الفصل يبرز واجهتين ، ويعرض كلا الواجهات الطريقة الافتراضية بنفس التوقيع. مع الأمثلة السابقة:
الواجهة العامة A {void foo () {system.out.println ("Calling A.FOO ()") ؛لا يمكن لهذا الرمز تجميع الأسباب التالية:
Java: Class Clazz من الأنواع A إلى B إلى Foo () يرث القيمة الافتراضية غير المرتبطة
من أجل إصلاح هذا ، في Clazz ، يتعين علينا حل طريقة إعادة كتابة الصراع يدويًا:
Clazz Public Clazz A ، b {public void foo () {}}} ولكن ماذا يجب أن نفعل إذا أردنا استدعاء طريقة التنفيذ الافتراضية من الواجهة أ ، بدلاً من تنفيذ طريقتنا الخاصة؟ هذا ممكن ، اقتبس FOO () في A ، كما هو موضح أدناه:
Clazz Public Clazz أدوات A ، B {public void foo () {A.Super.foo () ؛}}} الآن لا أستطيع أن أكون مقتنعًا جدًا بأنني أحب هذا الحل النهائي. ربما يكون أكثر إيجازًا من إدراك الطريقة الافتراضية في التوقيع ، كما أعلن في المسودة الأولى لمواصفات الطريقة الافتراضية:
Clazz Public Clazz A ، B {public void foo () الافتراضي A.FOO ؛}ولكن هذا يغير القواعد ، أليس كذلك؟ إذا حددت الواجهة A والواجهة B العديد من الأساليب الافتراضية التي تتعارض مع بعضها البعض ، وأنا على استعداد لاستخدام الطريقة الافتراضية لجميع الواجهة A لحل الصراع؟ في الوقت الحاضر ، لا بد لي من حل الصراع واحد تلو الآخر لإعادة كتابة كل زوج من الصراع. قد يتطلب هذا الكثير من العمل وكتابة عدد كبير من رمز القالب.
أقدر أن طريقة حل الصراع تتطلب الكثير من المناقشات ، لكن يبدو أن الخالق يقرر قبول الكارثة التي لا مفر منها.
مثال حقيقي
يمكن العثور على الأمثلة الحقيقية للطريقة الافتراضية في الحقيبة المبكرة من JDK8. بالعودة إلى مثال طريقة جمع المجموعة ، يمكننا أن نجد أنه في واجهة java.lang.iterable ، يكون تنفيذها الافتراضي كما يلي:
inctionalinterfacep interface iterface <T> {iterator <T> iterator () ؛ يستخدم Foreach معلمة من Java.Util.Function.Consumer نوع واجهة وظيفة ، والتي تتيح لنا تمرير تعبير lambda أو مرجع الطريقة ، على النحو التالي:
قائمة <؟> list = ... list.faceach (system.out :: println) ؛
استدعاء الطريقة
دعنا نلقي نظرة على كيفية استدعاء الطريقة الافتراضية بالفعل. إذا لم تكن على دراية بهذه المشكلة ، فقد تكون مهتمًا بقراءة مختبرات Rebel حول Java Bytes.
من منظور رمز العميل ، فإن الطريقة الافتراضية ليست سوى طريقة افتراضية شائعة. لذلك ، يجب أن يكون الاسم طريقة تمديد افتراضية. لذلك ، للحصول على مثال بسيط للطريقة الافتراضية كواجهة ، سيقوم رمز العميل تلقائيًا بالاتصال بالواجهة في المكان الذي يتم فيه استدعاء الطريقة الافتراضية.
A Clazz () Clazz () ؛
إذا تم حل تعارض الطريقة الافتراضية ، فعننا نعدل الطريقة الافتراضية وتحديد أحد الواجهات ، سيحدد Invokespecial تنفيذ تطبيق الواجهة للمكالمة المحددة.
Clazz Public Clazz A ، B {public void foo () {A.Super.foo () ؛ فيما يلي إخراج Javap:
Public Void Foo () ؛
كما ترى: يتم استخدام تعليمات vokespecial لاستدعاء طريقة الواجهة FOO (). من وجهة نظر Bytecode ، لا يزال هذا شيئًا جديدًا ، لأنه قبل أن تتمكن فقط من استدعاء الطريقة من خلال الإشارة إلى فئة (الوالد) بدلاً من Super التي تشير إلى واجهة.
أخيرًا ...
الطريقة الافتراضية هي الملحق المثير للاهتمام للغة Java. الهدف الرئيسي من التعبير الافتراضي هو تطوير واجهة JDK القياسية ، وعندما نبدأ أخيرًا في استخدام تعبير Lambdas لـ Java 8 ، فإننا نوفر لنا تجربة انتقال سلسة. من يدري ، ربما سنرى المزيد من الأساليب الافتراضية في تصميم API في المستقبل.