java لماذا لا توجد طرق ثابتة في الواجهات ، ولكن الحقول الثابتة والطبقات الداخلية موافق؟




interface jls (12)

كانت هناك بعض الأسئلة المطروحة هنا حول لماذا لا يمكنك تحديد طرق ثابتة داخل الواجهات ، لكن لا أحد منهم يعالج عدم تناسق أساسي: لماذا يمكنك تحديد حقول ثابتة وأنواع داخلية ثابتة داخل واجهة ، ولكن ليس أساليب ثابتة؟

قد لا تكون أنواع داخلية ثابتة مقارنة عادلة ، لأن هذا هو مجرد السكر النحوي الذي يولد طبقة جديدة ، ولكن لماذا الحقول ولكن لا طرق؟

إن الوسيطة ضد الأساليب الثابتة في الواجهات هي أنها تكسر إستراتيجية تحليل الجدول الظاهري المستخدمة من قبل JVM ، لكن ألا ينطبق ذلك على الحقول الثابتة بالتساوي ، أي يمكن للملجم أن يدمجها فقط؟

الاتساق هو ما أريده ، وجافا يجب أن يكون إما دعم أي احصائيات من أي شكل داخل واجهة ، أو ينبغي أن تكون متسقة وتسمح لهم.


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


لا يوجد سبب حقيقي لعدم وجود طرق ثابتة في الواجهات ما عدا: لم يرغب مصممو لغة جافا في ذلك. من الناحية الفنية سيكون من المنطقي السماح لهم بذلك. بعد كل فئة مجردة يمكن أن يكون لهم كذلك. أفترض ولكن لم اختباره ، أنه يمكنك "كود الحرف اليدوية" بايت حيث واجهة لديها طريقة ثابتة ويجب أن يعمل imho مع أي مشاكل في استدعاء الأسلوب و / أو استخدام الواجهة كما عادة.


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

هناك حقول ثابتة (أ) لأنها كانت موجودة في JDK 1.0 ، وتم اتخاذ العديد من القرارات المراوغة في JDK 1.0 ، و (ب) كانت الحقول النهائية الثابتة في الواجهات هي أقرب شيء كان جافا إلى الثوابت في ذلك الوقت.

وقد تم السماح بدورات داخلية ثابتة في واجهات لأن هذا السكر النحوي النقي - الطبقة الداخلية ليست في الواقع أي شيء يتعلق بالفئة الأم.

لذلك لا يُسمح بالطرق الثابتة ببساطة لأنه لا يوجد سبب مقنع للقيام بذلك ؛ الاتساق ليس مقنعًا بشكل كاف لتغيير الوضع الراهن.

بالطبع ، يمكن السماح بذلك في إصدارات JLS المستقبلية دون كسر أي شيء.


قبل Java 5 ، كان الاستخدام الشائع للحقول الثابتة:

interface HtmlConstants {
    static String OPEN = "<";
    static String SLASH_OPEN = "</";
    static String CLOSE = ">";
    static String SLASH_CLOSE = " />";
    static String HTML = "html";
    static String BODY = "body";
    ...
}

public class HtmlBuilder implements HtmlConstants { // implements ?!?
    public String buildHtml() {
       StringBuffer sb = new StringBuffer();
       sb.append(OPEN).append(HTML).append(CLOSE);
       sb.append(OPEN).append(BODY).append(CLOSE);
       ...
       sb.append(SLASH_OPEN).append(BODY).append(CLOSE);
       sb.append(SLASH_OPEN).append(HTML).append(CLOSE);
       return sb.toString();
    }
}

هذا يعني أن HtmlBuilder لن يحتاج إلى تأهيل كل ثابت ، لذلك يمكن استخدام OPEN بدلاً من HtmlConstants.OPEN

استخدام الأدوات بهذه الطريقة هو في نهاية المطاف مربكة.

الآن مع Java 5 ، لدينا بناء جملة ثابت للاستقرار لتحقيق نفس التأثير:

private final class HtmlConstants {
    ...
    private HtmlConstants() { /* empty */ }
}

import static HtmlConstants.*;
public class HtmlBuilder { // no longer uses implements
    ...
}

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


ترتبط الأساليب الثابتة بفئة. في Java ، لا تعتبر الواجهة من الناحية الفنية فئة ، بل هي نوع ، ولكنها ليست فئة (وبالتالي ، فإن الكلمات الرئيسية تطبق ، والواجهات لا تمد الكائن). نظرًا لأن الواجهات ليست فئات ، فلا يمكن أن يكون لها أساليب ثابتة ، نظرًا لعدم وجود فئة فعلية مرتبطة بها.

يمكنك استدعاء InterfaceName.class للحصول على كائن Class المطابق للواجهة ، ولكن فئة Class تشير بشكل خاص إلى أنها تمثل فئات وواجهات في تطبيق Java. ومع ذلك ، لا يتم التعامل مع الواجهة نفسها على أنها فئة ، وبالتالي لا يمكنك إرفاق طريقة ثابتة.


طريقة Java interface interface 1.8 مرئية لأساليب الواجهة فقط ، إذا قمنا بإزالة الطريقة methodTta1 () من فئة InterfaceExample ، فلن نتمكن من استخدامها لكائن InterfaceExample. ومع ذلك ، مثل الأساليب الثابتة الأخرى ، يمكننا استخدام أساليب ثابتة للواجهة باستخدام اسم الفئة. على سبيل المثال ، سيكون عبارة صحيحة هي: exp1.methodSta1 ()؛

لذلك ، بعد النظر في المثال أدناه يمكننا أن نقول: 1) طريقة Java interface static هي جزء من الواجهة ، ولا يمكننا استخدامها في كائنات فئة التنفيذ.

2) طرق جافا ساكن إستاتيكية جيدة لتزويد طرق المرافق ، على سبيل المثال فحص فارغ ، تصنيف الفرز ، سجل الخ.

3) يساعدنا الأسلوب الثابت لواجهة Java في توفير الأمان من خلال عدم السماح بفئات التطبيق (InterfaceExample) لتجاوزها.

4) لا يمكننا تعريف طريقة ثابتة لواجهة لطرق فئة كائن ، سوف نحصل على خطأ المحول البرمجي كـ "لا يمكن لهذه الطريقة الثابتة إخفاء أسلوب المثيل من الكائن". هذا لأنه غير مسموح به في java ، حيث أن Object هو الفئة الأساسية لجميع الفئات ، ولا يمكن أن نحصل على طريقة ثابتة واحدة على مستوى الفصل وطريقة مثيل أخرى لها نفس التوقيع.

5) يمكننا استخدام أساليب ثابتة واجهة جافا لإزالة فئات المرافق مثل مجموعات ونقل كل من أساليب ثابتة إلى واجهة المقابلة ، التي من السهل العثور عليها واستخدامها.

public class InterfaceExample implements exp1 {

    @Override
    public void method() {
        System.out.println("From method()");
    }

    public static void main(String[] args) {
        new InterfaceExample().method2();
        InterfaceExample.methodSta2();      //  <---------------------------    would not compile
        // methodSta1();                        //  <---------------------------    would not compile
        exp1.methodSta1();
    }

    static void methodSta2() {          //          <-- it compile successfully but it can't be overridden in child classes
        System.out.println("========= InterfaceExample :: from methodSta2() ======");
    }
}


interface exp1 {

    void method();
    //protected void method1();         //          <--      error
    //private void method2();           //          <--      error
    //static void methodSta1();         //          <--      error it require body in java 1.8

    static void methodSta1() {          //          <-- it compile successfully but it can't be overridden in child classes
        System.out.println("========= exp1:: from methodSta1() ======");
    }

    static void methodSta2() {          //          <-- it compile successfully but it can't be overridden in child classes
        System.out.println("========= exp1:: from methodSta2() ======");
    }

    default void method2() { System.out.println("---  exp1:: from method2() ---");}
    //synchronized default void method3() { System.out.println("---");}             // <-- Illegal modifier for the interface method method3; only public, abstract, default, static 
                                                                                // and strictfp are permitted
    //final default void method3() { System.out.println("---");} //             <--      error
}

أنا غالبا ما أتساءل لماذا أساليب ثابتة على الإطلاق؟ لديهم الاستخدامات الخاصة بهم ، ولكن أساليب مستوى الحزمة / مساحة الاسم ربما تغطي 80 من ما تستخدم أساليب ثابتة ل.


الغرض من الواجهات هو تحديد العقد دون توفير التنفيذ. لذلك ، لا يمكن أن يكون لديك أساليب ثابتة ، لأنه يجب أن يكون لديك تطبيق بالفعل في الواجهة حيث لا يمكنك تجاوز الأساليب الثابتة. بالنسبة للحقول ، يتم السماح فقط final fields الثابتة ، والتي هي ، في الأساس ، الثوابت (في 1.5+ يمكنك أيضا أن يكون enums في واجهات). الثوابت موجودة للمساعدة في تعريف الواجهة بدون أرقام سحرية.

راجع للشغل ، ليس هناك حاجة لتحديد صراحة المعدلات static final للحقول في الواجهات ، لأنه يتم السماح فقط الحقول النهائية الثابتة.


لا توجد أبدا نقطة للإعلان عن طريقة ثابتة في واجهة. لا يمكن تنفيذها بواسطة المكالمة العادية MyInterface.staticMethod (). (تحرير: حيث أن الجملة الأخيرة الخلط بين بعض الناس ، تنفيذ MyClass.staticMethod () تنفيذ بدقة تنفيذ staticMethod على MyClass ، والتي إذا MyClass هو واجهة لا يمكن أن توجد!) إذا كنت تسميها بتحديد فئة التطبيق MyImplementor.staticMethod () ثم يجب عليك معرفة الطبقة الفعلية ، لذلك لا يهم ما إذا كانت واجهة تحتوي عليه أم لا.

الأهم من ذلك ، لا يتم أبداً تجاوز الطرق الثابتة ، وإذا حاولت القيام بما يلي:

MyInterface var = new MyImplementingClass();
var.staticMethod();

تنص القواعد الثابتة على أنه يجب تنفيذ الطريقة المحددة في نوع var المُعلن. بما أن هذه واجهة ، فهذا مستحيل.

يمكنك بالطبع إزالة الكلمة الأساسية الثابتة من الطريقة. كل شيء سوف يعمل بشكل جيد. قد تضطر إلى منع بعض التحذيرات إذا تم استدعاؤها من أسلوب مثيل.

للإجابة على بعض التعليقات أدناه ، السبب الذي لا يمكنك تنفيذه "result = MyInterface.staticMethod ()" هو أنه سيتعين عليه تنفيذ إصدار الطريقة المحددة في MyInterface. ولكن لا يمكن أن يكون هناك إصدار محدد في MyInterface ، لأنه واجهة. ليس لديه كود التعريف.


تم تقديم اقتراح رسمي للسماح بأساليب ثابتة في واجهات في Java 7. يتم إجراء هذا الاقتراح ضمن Project Coin .

رأيي الشخصي هو أنها فكرة رائعة. لا توجد صعوبة فنية في التنفيذ ، ومن المنطقي والمنطقي جدًا القيام بذلك. هناك العديد من المقترحات في Project Coin التي آمل ألا تصبح أبدًا جزءًا من لغة Java ، ولكن هذا هو ما يمكن تنظيف الكثير من واجهات برمجة التطبيقات. على سبيل المثال ، يحتوي فئة Collections على أساليب ثابتة لمعالجة أي تطبيق List ؛ يمكن تضمينها في واجهة List .

تحديث: في Java Posse Podcast # 234 ، ذكر جو D'arcy الاقتراح لفترة وجيزة ، قائلا أنه كان "معقدة" وربما لن يجعلها في إطار مشروع كوين.

تحديث: في حين أنها لم تجعله في Project Coin for Java 7 ، تدعم Java 8 الدوال الثابتة في الواجهات.


السبب في ذلك هو أن كافة الطرق المعرّفة في واجهة ما تكون مجردة سواء قمت بتعريف هذا المُعدّل بشكل صريح أم لا. لا تعتبر الطريقة التجريدية المجردة مزيجًا مسموحًا به من المعدّلات حيث لا يمكن تجاوز الطرق الثابتة.

لماذا تسمح واجهات الحقول الثابتة. لدي شعور بأنه ينبغي اعتباره "ميزة". الاحتمال الوحيد الذي يمكنني التفكير فيه هو تجميع الثوابت التي تهتم بتطبيقات الواجهة.

أوافق على أن الاتساق كان سيكون نهجا أفضل. يجب عدم السماح لأعضاء ثابت في واجهة.





jls