لماذا لا يمكنني الإعلان عن الأساليب الثابتة في الواجهة؟

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

سؤال

الموضوع يوضح معظم ما يدور حوله - ما سبب عدم إمكانية الإعلان عن الأساليب الثابتة في الواجهة؟

public interface ITest {
    public static String test();
}

الكود أعلاه يعطيني الخطأ التالي (في Eclipse، على الأقل):"مُعدِّل غير قانوني لأسلوب الواجهة ITest.test();يُسمح فقط بالعامة والملخص".

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

المحلول

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

public interface Foo {
  public static int bar();
}

و

public interface Foo {
  public static int bar() {
    ...
  }
}

الأول مستحيل للأسباب التي إسبو يذكر:أنت لا تعرف أي فئة تنفيذية هي التعريف الصحيح.

جافا استطاع السماح لهذا الأخير؛وفي الواقع، بدءًا من Java 8، فهو كذلك!

نصائح أخرى

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

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

Math.add(2, 3);

إذا كانت الرياضيات واجهة بدلاً من فئة، فلن يكون لها أي وظائف محددة.على هذا النحو، فإن قول شيء مثل Math.add(2, 3) ليس له أي معنى.

السبب يكمن في مبدأ التصميم، أن جافا لا تسمح بالميراث المتعدد.يمكن توضيح مشكلة الميراث المتعدد من خلال المثال التالي:

public class A {
   public method x() {...}
}
public class B {
   public method x() {...}
}
public class C extends A, B { ... }

الآن ماذا يحدث إذا اتصلت بـ C.x()؟هل سيتم تنفيذ A.x() أو B.x()؟يجب على كل لغة ذات وراثة متعددة أن تحل هذه المشكلة.

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

public interface A {
   public static method x() {...}
}
public interface B {
   public static method x() {...}
}
public class C implements A, B { ... }

نفس المشكلة هنا، ماذا يحدث إذا اتصلت بـ C.x()؟

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

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

interface X {
    static void foo() {
       System.out.println("foo");
    }
}

class Y implements X {
    //...
}

public class Z {
   public static void main(String[] args) {
      X.foo();
      // Y.foo(); // won't compile because foo() is a Static Method of X and not Y
   }
}

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

هناك إجابة لطيفة وموجزة للغاية لسؤالك هنا.(لقد أذهلتني هذه الطريقة الواضحة والمباشرة لشرح الأمر لدرجة أنني أريد ربطه من هنا.)

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

interface Foo {
    // ...
    class fn {
        public static void func1(...) {
            // ...
        }
    }
}

ويمكن أيضًا استخدام نفس التقنية في التعليقات التوضيحية:

public @interface Foo {
    String value();

    class fn {
        public static String getValue(Object obj) {
            Foo foo = obj.getClass().getAnnotation(Foo.class);
            return foo == null ? null : foo.value();
        }
    }
}

يجب دائمًا الوصول إلى الطبقة الداخلية في شكل Interface.fn... بدلاً من Class.fn..., ، ثم يمكنك التخلص من مشكلة غامضة.

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

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

public interface StaticMethodInterface {
public static int testStaticMethod() {
    return 0;
}

/**
 * Illegal combination of modifiers for the interface method
 * testStaticMethod; only one of abstract, default, or static permitted
 * 
 * @param i
 * @return
 */
// public static abstract int testStaticMethod(float i);

default int testNonStaticMethod() {
    return 1;
}

/**
 * Without implementation.
 * 
 * @param i
 * @return
 */
int testNonStaticMethod(float i);

}

مجموعة غير قانونية من المعدلات :ثابتة ومجردة

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

إذا تم الإعلان عن عضو في فئة ما على أنه مجرد، فأنت بحاجة إلى إعلان الفئة على أنها مجردة وتحتاج إلى توفير تنفيذ العضو المجرد في فئته الموروثة (الفئة الفرعية).

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

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

مع جافا 8, ، يمكن أن تحتوي الواجهات الآن على طرق ثابتة.

على سبيل المثال، يحتوي Comparator على طريقة NaturalOrder() ثابتة.

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

ربما يكون مثال التعليمات البرمجية مفيدًا، سأستخدم C#، ولكن يجب أن تكون قادرًا على المتابعة.

لنتظاهر بأن لدينا واجهة تسمى IPayable

public interface IPayable
{
    public Pay(double amount);
}

الآن، لدينا فئتان محددتان تنفذان هذه الواجهة:

public class BusinessAccount : IPayable
{
    public void Pay(double amount)
    {
        //Logic
    }
}

public class CustomerAccount : IPayable
{
    public void Pay(double amount)
    {
        //Logic
    }
}

الآن، لنتظاهر بأن لدينا مجموعة من الحسابات المتنوعة، وللقيام بذلك سنستخدم قائمة عامة من النوع IPayable

List<IPayable> accountsToPay = new List<IPayable>();
accountsToPay.add(new CustomerAccount());
accountsToPay.add(new BusinessAccount());

الآن، نريد دفع 50.00 دولارًا لجميع هذه الحسابات:

foreach (IPayable account in accountsToPay)
{
    account.Pay(50.00);
}

الآن ترى كيف تكون الواجهات مفيدة بشكل لا يصدق.

يتم استخدامها على الكائنات التي تم إنشاء مثيل لها فقط.ليس على فئات ثابتة.

إذا كنت قد جعلت الدفع ثابتًا، فعند المرور عبر IPayable في AccountsToPay، لن تكون هناك طريقة لمعرفة ما إذا كان يجب الاتصال بالدفع على BusinessAcount أو CustomerAccount.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top