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




interface static-methods (16)

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

في الواقع يمكنك في Java 8.

حسب docs.oracle/staticMethod Java:

الطريقة الثابتة هي الطريقة التي ترتبط بالفئة التي يتم تعريفها فيها بدلاً من أي كائن. كل مثيل للفصل يشارك أساليبه الثابتة

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

مثال على الطريقة الافتراضية:

list.sort(ordering);

بدلا من

Collections.sort(list, ordering);

مثال على طريقة ثابتة (من docs.oracle/staticMethod نفسه):

public interface TimeClient {
    // ...
    static public ZoneId getZoneId (String zoneString) {
        try {
            return ZoneId.of(zoneString);
        } catch (DateTimeException e) {
            System.err.println("Invalid time zone: " + zoneString +
                "; using default time zone instead.");
            return ZoneId.systemDefault();
        }
    }

    default public ZonedDateTime getZonedDateTime(String zoneString) {
        return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));
    }    
}

https://code.i-harness.com

هنا هو المثال:

public interface IXMLizable<T>
{
  static T newInstanceFromXML(Element e);
  Element toXMLElement();
}

بالطبع هذا لن ينجح. لكن لم لا؟

إحدى المشكلات المحتملة هي ما يحدث عند الاتصال:

IXMLizable.newInstanceFromXML(e);

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

تحرير: أعتقد أنني أبحث عن إجابة أعمق من "لأن هذا هو طريقة جافا".

هل هناك سبب تكنولوجي معين لماذا لا يمكن الكتابة فوق الأساليب الثابتة؟ وهذا هو السبب ، لماذا قرر مصممي جافا لجعل أساليب سبيل المثال overredeable ولكن ليست ثابتة؟

تحرير: المشكلة مع تصميمي هو محاولة استخدام الواجهات لفرض اصطلاح ترميز.

وهذا هو ، هدف الواجهة ذو شقين:

  1. أريد واجهة IXMLizable للسماح لي بتحويل الفئات التي تقوم بتطبيقها على عناصر XML (باستخدام تعدد الأشكال ، يعمل بشكل جيد).

  2. إذا أراد أحدهم إنشاء مثيل جديد لفئة تقوم بتطبيق واجهة IXMLizable ، فسيعرفون دائمًا أنه سيكون هناك منشئ ثابت newInstanceFromXML (العنصر e).

هل هناك أي طريقة أخرى لضمان ذلك ، بخلاف وضع تعليق في الواجهة؟

تحرير: اعتبارا من جافا 8 ، يسمح الآن الأساليب الثابتة في الواجهات.


يتيح Java 8 أساليب الواجهة الثابتة

مع Java 8 ، يمكن أن تحتوي الواجهات على أساليب ثابتة. يمكن أن يكون لها أيضًا طرق محددة ، ولكنها لا تحتوي على أمثلة.

هناك حقا سؤالان هنا:

  1. لماذا ، في الأيام القديمة السيئة ، لا يمكن واجهات تحتوي على أساليب ثابتة؟
  2. لماذا لا يمكن تجاوز الطرق الثابتة؟

طرق ثابتة في الواجهات

لم يكن هناك سبب تقني قوي لأن الواجهات لم يكن لديها أساليب ثابتة في الإصدارات السابقة. يتم تلخيص هذا بشكل جيد بواسطة ملصق لسؤال مكرر. تم اعتبار أساليب الواجهة الثابتة في البداية كتغيير لغوي صغير ، ثم كان هناك اقتراح رسمي لإضافتها في Java 7 ، ولكن تم إسقاطها لاحقًا بسبب مضاعفات غير متوقعة.

وأخيرًا ، أدخلت Java 8 أساليب الواجهة الثابتة ، بالإضافة إلى أساليب المثيل القديرة للتطبيق مع التنفيذ الافتراضي. لا يزالوا لا يستطيعون الحصول على حقول المثال رغم ذلك. هذه الميزات جزء من دعم التعبير lambda ، ويمكنك قراءة المزيد عنها في الجزء H من JSR 335.

تجاوز الأساليب الثابتة

الجواب على السؤال الثاني أكثر تعقيدًا بعض الشيء.

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

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

التظاهر بأن كل فصل يحتوي على جدول تجزئة يقوم بتعيين خرائط التواقيع (أنواع الاسم والمعلمات) إلى جزء فعلي من التعليمات البرمجية لتنفيذ الطريقة. عندما يحاول الجهاز الظاهري استدعاء أسلوب على مثيل ، فإنه يستعلم عن الكائن لفئته ويبحث عن التوقيع المطلوب في جدول الفصل. إذا تم العثور على نص الطريقة ، فسيتم استدعاؤه. وإلا ، يتم الحصول على الفئة الأصل للفئة ، وتكرار البحث هناك. NoSuchMethodError هذا حتى يتم العثور على الطريقة ، أو لا توجد المزيد من الفئات الأصل - مما يؤدي إلى NoSuchMethodError .

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

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

منشئ "واجهات"

إليك المزيد من المواد لمعالجة التعديل الأخير على السؤال.

يبدو أنك تريد تفويض أسلوب يشبه منشئ لكل تطبيق IXMLizable . نسيان محاولة فرض هذا باستخدام واجهة لمدة دقيقة ، والتظاهر بأن لديك بعض الفئات التي تلبي هذا المطلب. كيف يمكنك أن تستفيد منها؟

class Foo implements IXMLizable<Foo> {
  public static Foo newInstanceFromXML(Element e) { ... }
}

Foo obj = Foo.newInstanceFromXML(e);

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

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


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

وثانيا ، أننا نستخدم أساليب ثابتة بكل الوسائل ، هناك سبب وجيه لوجود نمط واجهة يتضمن أساليب ثابتة - لا أستطيع التحدث عن أي منكم ، لكني أستخدم أساليب ثابتة على أساس منتظم.

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


افترض أنك تستطيع القيام بذلك ؛ ضع في اعتبارك هذا المثال:

interface Iface {
  public static void thisIsTheMethod();
}

class A implements Iface {

  public static void thisIsTheMethod(){
    system.out.print("I'm class A");
  }

}

class B extends Class A {

  public static void thisIsTheMethod(){
    System.out.print("I'm class B");
  } 
}

SomeClass {

  void doStuff(Iface face) {
    IFace.thisIsTheMethod();
    // now what would/could/should happen here.
  }

}

تعليق EDIT: As of Java 8, static methods are now allowed in interfaces.

إنها طرق صحيحة واستاتيكية منذ السماح بـ Java 8 في واجهات ، ولكن لا يزال المثال الخاص بك لا يعمل. لا يمكنك فقط تحديد أسلوب ثابت: يجب عليك تنفيذه أو ستحصل على خطأ تجميع.


تهتم الواجهات بتعدد الأشكال الذي يرتبط بطبيعته بأجزاء الكائن وليس الفئات. لذلك ، لا يعتبر الاستاتيكي منطقيًا في سياق الواجهة.


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

مع الأدوية ، لديهم استخدام - مع أو بدون تنفيذ الافتراضي. من الواضح أن هناك حاجة إلى أن تكون مهيمنة وهكذا. ومع ذلك ، أعتقد أن هذا الاستخدام لم يكن غاية OO (كما تشير الإجابات الأخرى بشكل مبسط) وبالتالي لم يتم اعتبارها تستحق الجهد الذي تحتاجه لتنفيذها بشكل مفيد.


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

SI si = clazz.getStatic(SI.class); // null if clazz doesn't implement SI
// alternatively if the class is known at compile time
SI si = Someclass.static.SI; // either compiler errror or not null

ثم يمكنك استدعاء si.method(params) . هذا من شأنه أن يكون مفيدا (لنمط تصميم المصنع على سبيل المثال) لأنه يمكنك الحصول على (أو التحقق من تنفيذ) SI تنفيذ أساليب ثابتة من فئة غير معروفة وقت التجميع! إرسال حيوي ضروري ويمكنك تجاوز الأساليب الثابتة (إذا لم يكن نهائياً) للفئة بتمديده (عند استدعاؤه من خلال واجهة ثابتة). من الواضح أن هذه الطرق لا يمكنها الوصول إلا إلى المتغيرات الثابتة لفئتها.


على الرغم من أنني أدرك أن Java 8 يحل هذه المشكلة ، فكرت أن أقوم بالتناغم مع سيناريو أعمل عليه حاليًا (مقفل إلى استخدام Java 7) حيث يكون من الممكن أن تكون قادرًا على تحديد طرق ثابتة في الواجهة.

لدي العديد من تعريفات التعداد حيث قمت بتعريف حقول "id" و "displayName" مع أساليب المساعد التي تقوم بتقييم القيم لأسباب مختلفة. يتيح تنفيذ الواجهة إمكانية التأكد من أن طرق getter موجودة ولكن ليس أساليب المساعد الثابت. كونه تعدادًا ، لا توجد طريقة نظيفة لإفراغ طرق المساعدة في فئة مجردة موروثة أو شيء من هذا القبيل ، لذلك يجب تحديد الطرق في التعداد نفسه. أيضاً لأنه تعداد ، لن تتمكن من تمريره في الواقع ككائن تم إنشاؤه ومعالجته كنوع الواجهة ، ولكن القدرة على طلب وجود طرق المساعد الثابت من خلال الواجهة هي ما يعجبني يتم دعمه في Java 8.

هنا رمز يوضح وجهة نظري.

تعريف الواجهة:

public interface IGenericEnum <T extends Enum<T>> {
    String getId();
    String getDisplayName();
    //If I was using Java 8 static helper methods would go here
}

مثال على تعريف تعدادي واحد:

public enum ExecutionModeType implements IGenericEnum<ExecutionModeType> {
    STANDARD ("Standard", "Standard Mode"),
    DEBUG ("Debug", "Debug Mode");

    String id;
    String displayName;

    //Getter methods
    public String getId() {
        return id;
    }

    public String getDisplayName() {
        return displayName;
    }

    //Constructor
    private ExecutionModeType(String id, String displayName) {
        this.id = id;
        this.displayName = displayName;
    }

    //Helper methods - not enforced by Interface
    public static boolean isValidId(String id) {
        return GenericEnumUtility.isValidId(ExecutionModeType.class, id);
    }

    public static String printIdOptions(String delimiter){
        return GenericEnumUtility.printIdOptions(ExecutionModeType.class, delimiter);
    }

    public static String[] getIdArray(){
        return GenericEnumUtility.getIdArray(ExecutionModeType.class);
    }

    public static ExecutionModeType getById(String id) throws NoSuchObjectException {
        return GenericEnumUtility.getById(ExecutionModeType.class, id);
    }
}

تعريف أداة التعريف العامة:

public class GenericEnumUtility {
    public static <T extends Enum<T> & IGenericEnum<T>> boolean isValidId(Class<T> enumType, String id) {       
        for(IGenericEnum<T> enumOption : enumType.getEnumConstants()) {
            if(enumOption.getId().equals(id)) {
                return true;
            }
        }

        return false;
    }

    public static <T extends Enum<T> & IGenericEnum<T>> String printIdOptions(Class<T> enumType, String delimiter){
        String ret = "";
        delimiter = delimiter == null ? " " : delimiter;

        int i = 0;
        for(IGenericEnum<T> enumOption : enumType.getEnumConstants()) {
            if(i == 0) {
                ret = enumOption.getId();
            } else {
                ret += delimiter + enumOption.getId();
            }           
            i++;
        }

        return ret;
    }

    public static <T extends Enum<T> & IGenericEnum<T>> String[] getIdArray(Class<T> enumType){
        List<String> idValues = new ArrayList<String>();

        for(IGenericEnum<T> enumOption : enumType.getEnumConstants()) {
            idValues.add(enumOption.getId());
        }

        return idValues.toArray(new String[idValues.size()]);
    }

    @SuppressWarnings("unchecked")
    public static <T extends Enum<T> & IGenericEnum<T>> T getById(Class<T> enumType, String id) throws NoSuchObjectException {
        id = id == null ? "" : id;
        for(IGenericEnum<T> enumOption : enumType.getEnumConstants()) {
            if(id.equals(enumOption.getId())) {
                return (T)enumOption;
            }
        }

        throw new NoSuchObjectException(String.format("ERROR: \"%s\" is not a valid ID. Valid IDs are: %s.", id, printIdOptions(enumType, " , ")));
    }
}

لأنه لا يمكن تجاوز الطرق الثابتة في الفئات الفرعية ، وبالتالي لا يمكن أن تكون مجردة. وجميع الطرق في الواجهة هي ، في الواقع ، مجردة.


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

ومع ذلك ، إذا كنت تريد أن تفعل ذلك:

public class A {
  public static void methodX() {
  }
}

public class B extends A {
  public static void methodX() {
  }
}

في هذه الحالة ما لديك فئتين مع 2 أساليب ثابتة مميزة تسمى methodX ().


لحل هذه المشكلة: الخطأ: جسم الأسلوب المفقود ، أو الإعلان عن الفراغ الثابت التجريدي الرئيسي (String [] args) ؛

interface I
{
    int x=20;
    void getValue();
    static void main(String[] args){};//Put curly braces 
}
class InterDemo implements I
{
    public void getValue()
    {
    System.out.println(x);
    }
    public static void main(String[] args)
    {
    InterDemo i=new InterDemo();
    i.getValue();   
    }

}

الإخراج: 20

الآن يمكننا استخدام طريقة ثابتة في الواجهة


لنفترض أن الطرق الثابتة مسموح بها في واجهات: * ستجبر جميع فئات التنفيذ على إعلان هذه الطريقة. * عادةً ما يتم استخدام الواجهات من خلال الكائنات ، لذلك فإن الطرق الفعالة الوحيدة في هذه الطرق هي الطرق غير الثابتة. * يمكن لأي فئة تعرف واجهة معينة استدعاء أساليبها الثابتة. ومن ثم ، يمكن تسمية "طريقة التنفيذ" "فئة ثابتة" ، ولكن فئة المتجولين لا تعرف ذلك. كيف تعرف ذلك؟ لا يوجد لديه تأخر لتخمين ذلك!

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

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

تخدم metaclasses لهذا الغرض. يمكنك تجربة فئة صنف جافا. لكن المشكلة هي أن جافا ليست مرنة بما فيه الكفاية لهذا. لا يمكنك تعريف طريقة في كائن الفئة لواجهة.

هذه مشكلة في meta - عندما تحتاج إلى فعل الحمار

.. بلاه بلاه

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


مع قدوم Java 8 أصبح من الممكن الآن كتابة الطرق الافتراضية والثابتة في الواجهة. docs.oracle/staticMethod

فمثلا:

public interface Arithmetic {

    public int add(int a, int b);

    public static int multiply(int a, int b) {
        return a * b;
    }
}
public class ArithmaticImplementation implements Arithmetic {

    @Override
    public int add(int a, int b) {
        return a + b;
    }

    public static void main(String[] args) {
        int result = Arithmetic.multiply(2, 3);
        System.out.println(result);
    }
}

النتيجة : 6

تلميح: لا يتطلب تنفيذ أي طريقة من فئات الاتصال استدعاء طريقة واجهة ثابتة. من المؤكد أن هذا يحدث لأن القواعد نفسها للطرق الثابتة في الطبقة الفائقة تنطبق على الطرق الثابتة على السطوح البينية.


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


Why can't I define a static method in a Java interface?

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





static-methods