جافا 8:لماذا يُمنع تحديد طريقة افتراضية لطريقة من java.lang.Object

StackOverflow https://stackoverflow.com//questions/24016962

سؤال

تعد الطرق الافتراضية أداة جديدة رائعة في صندوق أدوات Java الخاص بنا.ومع ذلك، حاولت أن أكتب واجهة تحدد ملف default نسخة من toString طريقة.أخبرتني Java أن هذا ممنوع، حيث تم الإعلان عن الطرق في java.lang.Object قد لا يكون defaultإد.لماذا هذا هو الحال؟

أعلم أن هناك قاعدة "الفئة الأساسية تفوز دائمًا" ، لذا افتراضيًا (التورية ؛) ، أي default تنفيذ Object سيتم استبدال الطريقة بالطريقة من Object على أي حال.ومع ذلك، لا أرى أي سبب لعدم وجود استثناء للطرق من Object في المواصفات.خصوصا ل toString قد يكون من المفيد جدًا أن يكون لديك تطبيق افتراضي.

إذن، ما هو السبب وراء قرار مصممي Java بعدم السماح بذلك default أساليب تجاوز الأساليب من Object?

هل كانت مفيدة؟

المحلول

هذه مشكلة أخرى من مشكلات تصميم اللغة التي تبدو "فكرة جيدة بشكل واضح" حتى تبدأ في البحث وتدرك أنها فكرة سيئة بالفعل.

هذا البريد لديه الكثير حول هذا الموضوع (وفي مواضيع أخرى أيضًا.) كانت هناك العديد من قوى التصميم التي تقاربت لتوصلنا إلى التصميم الحالي:

  • الرغبة في إبقاء نموذج الميراث بسيطًا؛
  • حقيقة أنه بمجرد النظر إلى الأمثلة الواضحة (على سبيل المثال، الدوران AbstractList في واجهة)، فإنك تدرك أن وراثة يساوي/hashCode/toString مرتبطة بقوة بالميراث والحالة الفردية، وأن الواجهات تتضاعف وتورث وعديمة الحالة؛
  • أنه من المحتمل أن يفتح الباب أمام بعض السلوكيات المدهشة.

لقد تطرقت بالفعل إلى هدف "الحفاظ على البساطة"؛تم تصميم قواعد الميراث وحل الصراعات لتكون بسيطة جدًا (تفوز الفئات على الواجهات، والواجهات المشتقة تفوز على الواجهات الفائقة، ويتم حل أي تعارضات أخرى بواسطة الفئة المنفذة). بالطبع يمكن تعديل هذه القواعد لإجراء استثناء، ولكن أعتقد أنك ستجد عندما تبدأ في استخدام هذا الخيط، أن التعقيد المتزايد ليس صغيرًا كما تظن.

وبطبيعة الحال، هناك درجة معينة من الفوائد التي تبرر المزيد من التعقيد، ولكن في هذه الحالة ليس هناك.الأساليب التي نتحدث عنها هنا هي يساوي، hashCode، وtoString.تتعلق جميع هذه الأساليب بشكل جوهري بحالة الكائن، والطبقة هي التي تمتلك الحالة، وليس الواجهة، وهي في أفضل وضع لتحديد معنى المساواة لتلك الطبقة (خاصة وأن عقد المساواة قوي جدًا؛راجع Java الفعالة للتعرف على بعض العواقب المدهشة)؛تمت إزالة كتاب الواجهة بعيدًا جدًا.

من السهل سحب AbstractList مثال؛سيكون جميلا لو تمكنا من التخلص منها AbstractList ووضع السلوك في List واجهه المستخدم.ولكن بمجرد تجاوز هذا المثال الواضح، لن يكون هناك العديد من الأمثلة الجيدة الأخرى التي يمكن العثور عليها.في الجذر، AbstractList تم تصميمه للميراث الفردي.ولكن يجب تصميم الواجهات للميراث المتعدد.

علاوة على ذلك، تخيل أنك تكتب هذا الفصل:

class Foo implements com.libraryA.Bar, com.libraryB.Moo { 
    // Implementation of Foo, that does NOT override equals
}

ال Foo ينظر الكاتب إلى الأنواع الفائقة، ولا يرى أي تطبيق للمساواة، ويخلص إلى أنه للحصول على المساواة المرجعية، كل ما يحتاج إليه هو أن يرث المساواة من Object.وبعد ذلك، في الأسبوع التالي، يضيف مشرف المكتبة لـ Bar الإعداد الافتراضي "بشكل مفيد". equals تطبيق.عفوا!الآن دلالات Foo تم تعطيلها بواسطة واجهة في مجال صيانة آخر "بشكل مفيد" مما أدى إلى إضافة إعداد افتراضي لطريقة شائعة.

من المفترض أن تكون الإعدادات الافتراضية هي الإعدادات الافتراضية.إضافة افتراضي إلى واجهة لا يوجد بها أي شيء (في أي مكان في التسلسل الهرمي) يجب ألا يؤثر على دلالات فئات التنفيذ الملموسة.ولكن إذا كان بإمكان الإعدادات الافتراضية "تجاوز" أساليب الكائنات، فلن يكون هذا صحيحًا.

لذلك، على الرغم من أنها تبدو وكأنها ميزة غير ضارة، إلا أنها في الواقع ضارة جدًا:إنه يضيف الكثير من التعقيد لقليل من التعبير المتزايد، ويجعل من السهل جدًا إجراء تغييرات حسنة النية وغير ضارة على واجهات مجمعة بشكل منفصل لتقويض الدلالات المقصودة لفئات التنفيذ.

نصائح أخرى

يحظر تحديد الطرق الافتراضية في الواجهات للطرق الموجودة في java.lang.Object, ، نظرًا لأن الأساليب الافتراضية لن تكون "قابلة للوصول" أبدًا.

يمكن استبدال أساليب الواجهة الافتراضية في الفئات التي تنفذ الواجهة ويكون تنفيذ الفئة للطريقة له أسبقية أعلى من تنفيذ الواجهة، حتى لو تم تنفيذ الطريقة في فئة فائقة.وبما أن جميع الطبقات ترث من java.lang.Object, ، الأساليب في java.lang.Object ستكون لها الأسبقية على الطريقة الافتراضية في الواجهة وسيتم استدعاؤها بدلاً من ذلك.

يقدم Brian Goetz من Oracle بعض التفاصيل الإضافية حول قرار التصميم في هذا الشأن مشاركة القائمة البريدية.

أنا لا أرى ما في ذهن مؤلفي لغة جافا، لذلك يمكننا فقط التخمين.لكني أرى أسبابا كثيرة وأتفق معها تماما في هذه المسألة.

السبب الرئيسي لإدخال الأساليب الافتراضية هو القدرة على إضافة أساليب جديدة إلى الواجهات دون كسر التوافق مع الإصدارات السابقة للتطبيقات القديمة.يمكن أيضًا استخدام الطرق الافتراضية لتوفير طرق "ملائمة" دون الحاجة إلى تعريفها في كل فئة من فئات التنفيذ.

لا ينطبق أي من هذه على toString وأساليب الكائن الأخرى.ببساطة، تم تصميم الطرق الافتراضية لتوفير تقصير السلوك حيث لا يوجد تعريف آخر.عدم توفير تطبيقات من شأنها "التنافس" مع التطبيقات الأخرى الموجودة.

إن قاعدة "الطبقة الأساسية تفوز دائمًا" لها أسبابها القوية أيضًا.ومن المفترض أن تحدد الطبقات حقيقي التطبيقات، بينما تحدد الواجهات تقصير التنفيذ، وهي أضعف إلى حد ما.

كما أن تقديم أي استثناءات للقواعد العامة يسبب تعقيدًا غير ضروري ويثير أسئلة أخرى.الكائن (أكثر أو أقل) فئة مثل أي فئة أخرى، فلماذا يجب أن يكون له سلوك مختلف؟

وعلى العموم، فإن الحل الذي تقترحه من المحتمل أن يجلب سلبيات أكثر من إيجابيات.

السبب بسيط للغاية، وذلك لأن الكائن هو الفئة الأساسية لجميع فئات Java.لذلك، حتى لو تم تعريف طريقة الكائن كطريقة افتراضية في بعض الواجهات، فستكون عديمة الفائدة لأن طريقة الكائن سيتم استخدامها دائمًا.ولهذا السبب، لتجنب الالتباس، لا يمكن أن يكون لدينا أساليب افتراضية تتجاوز أساليب فئة الكائن.

لإعطاء إجابة متحذلقة للغاية، يُمنع فقط تحديد أ default طريقة ل عام الطريقة من java.lang.Object.هناك 11 طريقة يجب أخذها بعين الاعتبار، والتي يمكن تصنيفها إلى ثلاث طرق للإجابة على هذا السؤال.

  1. ستة من Object الأساليب لا يمكن أن يكون default الأساليب لأنها كذلك final ولا يمكن تجاوزه على الإطلاق: getClass(), notify(), notifyAll(), wait(), wait(long), ، و wait(long, int).
  2. ثلاثة من Object الأساليب لا يمكن أن يكون default الأساليب للأسباب المذكورة أعلاه من قبل بريان جويتز: equals(Object), hashCode(), ، و toString().
  3. اثنان من Object طُرق يستطيع يملك default الأساليب، على الرغم من أن قيمة هذه الإعدادات الافتراضية مشكوك فيها في أحسن الأحوال: clone() و finalize().

    public class Main {
        public static void main(String... args) {
            new FOO().clone();
            new FOO().finalize();
        }
    
        interface ClonerFinalizer {
            default Object clone() {System.out.println("default clone"); return this;}
            default void finalize() {System.out.println("default finalize");}
        }
    
        static class FOO implements ClonerFinalizer {
            @Override
            public Object clone() {
                return ClonerFinalizer.super.clone();
            }
            @Override
            public void finalize() {
                ClonerFinalizer.super.finalize();
            }
        }
    }
    
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top