سؤال

خذ بعين الاعتبار الواجهة التالية في Java:

public interface I {
    public final String KEY = "a";
}

والفئة التالية :

public class A implements I {
    public String KEY = "b";

    public String getKey() {
        return KEY;
    }
}

لماذا يمكن للفئة "أ" أن تأتي وتتجاوز الثابت النهائي للواجهة؟

حاول بنفسك:

A a = new A();
String s = a.getKey(); // returns "b"!!!
هل كانت مفيدة؟

المحلول

على الرغم من أنك تقوم بتظليل المتغير، فمن المثير للاهتمام معرفة أنه يمكنك تغيير الحقول النهائية في Java كما يمكنك القراءة هنا:

Java 5 - "النهائي" لم يعد نهائيًا بعد الآن

أرسل لي Narve Saetre من Machina Networks في النرويج ملاحظة بالأمس ، مشيراً إلى أنه من المؤسف أنه يمكننا تغيير المقبض إلى صفيف نهائي.لقد أسيء فهمه ، وبدأت في شرحنا بصبر أننا لا نستطيع جعل الصفيف ثابتًا ، وأنه لم تكن هناك طريقة لحماية محتويات الصفيف."لا" ، قال ، "يمكننا تغيير مقبض نهائي باستخدام التفكير."

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

public class Person {
  private final String name;
  private final int age;
  private final int iq = 110;
  private final Object country = "South Africa";

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String toString() {
    return name + ", " + age + " of IQ=" + iq + " from " + country;
  }
}

جدك 1.1.x

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

كان JDK 1.1.8 Javadocs لـ java.lang.reflect.field ما يلي ليقول:

  • إذا كان كائن الحقل هذا يفرض التحكم في الوصول إلى لغة Java ، وكان من الممكن الوصول إلى الحقل الأساسي ، فإن الطريقة ترمي غير قانونية.
  • إذا كان الحقل الأساسي نهائيًا، فستطرح الطريقة IllegalAccessException.

جدك 1.2.x

في JDK 1.2.x، تغير هذا قليلاً.يمكننا الآن جعل الحقول الخاصة متاحة مع طريقة setAccessible (الحقيقية).تم الآن فحص الوصول إلى الحقول في وقت التشغيل ، لذلك لم نتمكن من استخدام خدعة تبديل الفصل للوصول إلى الحقول الخاصة.ومع ذلك ، يمكننا الآن أن نرد فجأة الحقول النهائية!أنظر إلى هذا الكود:

import java.lang.reflect.Field;

public class FinalFieldChange {
  private static void change(Person p, String name, Object value)
      throws NoSuchFieldException, IllegalAccessException {
    Field firstNameField = Person.class.getDeclaredField(name);
    firstNameField.setAccessible(true);
    firstNameField.set(p, value);
  }
  public static void main(String[] args) throws Exception {
    Person heinz = new Person("Heinz Kabutz", 32);
    change(heinz, "name", "Ng Keng Yap");
    change(heinz, "age", new Integer(27));
    change(heinz, "iq", new Integer(150));
    change(heinz, "country", "Malaysia");
    System.out.println(heinz);
  }
}

عندما قمت بتشغيل هذا في JDK 1.2.2_014، حصلت على النتيجة التالية:

Ng Keng Yap, 27 of IQ=110 from Malaysia    Note, no exceptions, no complaints, and an incorrect IQ result. It seems that if we set a

الحقل النهائي للبداية في وقت الإعلان ، يتم ضم القيمة ، إذا كان النوع بدائي أو سلسلة.

JDK 1.3.x و1.4.x

في JDK 1.3.x ، شددت الشمس الوصول قليلاً ، ومنعنا من تعديل الحقل النهائي مع انعكاس.كان هذا هو الحال أيضًا مع JDK 1.4.x.إذا حاولنا تشغيل فئة FinalfieldChange لإعادة ربط الحقول النهائية في وقت التشغيل باستخدام التفكير ، فسوف نحصل على:

إصدار جافا "1.3.1_12":موضوع استثناء "Main" غير شرعي:يعد الحقل نهائيًا في java.lang.reflect.field.set (الطريقة الأصلية) في Finalfieldchange.Change (Finalfieldchange.java:8) في Finalfieldchange.main (Finalfieldchange.java:12)

إصدار Java "1.4.2_05" Thread Thread "MAINالحقل هو نهائي في java.lang.reflect.field.set (field.java:519) في Finalfieldchange.Change (Finalfieldchange.java:8) في Finalfieldchange.main (Finalfieldchange.java:12)

جدك 5.x

نصل الآن إلى JDK 5.x.تحتوي فئة Finalfieldchange على نفس الإخراج كما في JDK 1.2.x:

Ng Keng Yap, 27 of IQ=110 from Malaysia    When Narve Saetre mailed me that he managed to change a final field in JDK 5 using

للتفكير، كنت آمل أن يكون هناك خطأ ما قد تسلل إلى JDK.ومع ذلك ، فقد شعرنا أنه من غير المرجح ، وخاصة مثل هذا الخطأ الأساسي.بعد بعض البحث، وجدت JSR-133:نموذج الذاكرة Java ومواصفات الموضوع.معظم المواصفات هي القراءة الصعبة ، وتذكرني بأيامي الجامعية (اعتدت أن أكتب مثل هذا ؛-) ومع ذلك ، فإن JSR-133 مهم للغاية لدرجة أنه يجب أن تكون مطلوبة القراءة لجميع مبرمجي Java.(حظ سعيد)

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

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

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

الغريب ، يختلف JDK 5 قليلاً عن JDK 1.2.x ، حيث لا يمكنك تعديل حقل نهائي ثابت.

import java.lang.reflect.Field;

public class FinalStaticFieldChange {
  /** Static fields of type String or primitive would get inlined */
  private static final String stringValue = "original value";
  private static final Object objValue = stringValue;

  private static void changeStaticField(String name)
      throws NoSuchFieldException, IllegalAccessException {
    Field statFinField = FinalStaticFieldChange.class.getDeclaredField(name);
    statFinField.setAccessible(true);
    statFinField.set(null, "new Value");
  }

  public static void main(String[] args) throws Exception {
    changeStaticField("stringValue");
    changeStaticField("objValue");
    System.out.println("stringValue = " + stringValue);
    System.out.println("objValue = " + objValue);
    System.out.println();
  }
}

عندما نقوم بتشغيل هذا باستخدام JDK 1.2.x و JDK 5.x ، نحصل على الإخراج التالي:

إصدار جافا "1.2.2_014":StringValue = القيمة الأصلية ObjValue = قيمة جديدة

إصدار جافا "1.5.0" استثناء مؤشر الترابط "الرئيسي" IllegalAccessException:الحقل نهائي في java.lang.reflect.field.set (field.java:656) في FinalStaticfieldChange.Changestaticfield (12) في FinalStaticfieldChange.main (16)

لذا، JDK 5 يشبه JDK 1.2.x، هل هو مختلف تمامًا؟

خاتمة

هل تعرف متى تم إصدار JDK 1.3.0؟لقد ناضلت لمعرفة ذلك ، لذلك قمت بتنزيله وتثبيته.يحتوي ملف readme.txt على تاريخ 2000/06/02 13:10.لذلك ، عمره أكثر من 4 سنوات (الخير لي ، يبدو الأمر بالأمس).تم إصدار JDK 1.3.0 قبل عدة أشهر من بدء كتابة النشرة الإخبارية المتخصصة في Java (TM)!أعتقد أنه سيكون من الآمن القول أن قلة قليلة من مطوري Java يمكنهم تذكر تفاصيل ما قبل JDK1.3.0.آه، الحنين ليس كما كان من قبل!هل تتذكر تشغيل جافا لأول مرة والحصول على هذا الخطأ:""غير قادر على تهيئة المواضيع:لا يمكن العثور على فئة java/lang/Thread"؟

نصائح أخرى

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

وبالمناسبة، يمكنك نطاق ذلك مرة أخرى إذا كنت ترغب:

public class A implements I {
    public String KEY = "b";

    public String getKey() {
        String KEY = "c";
        return KEY;
    }
}

والآن سوف KEY العودة "ج"؛

وقام لأن الأصل امتص عند إعادة القراءة.

ويبدو صفك هو مجرد يختبئ متغير، وليس الكتابة فيه:

public class A implements I {
    public String   KEY = "B";

    public static void main(String args[])
    {
        A t = new A();
        System.out.println(t.KEY);
        System.out.println(((I) t).KEY);
    }
}

وهذا سيطبع "B"، و "A"، كما وجدت. يمكنك تعيين حتى إلى ذلك، كما لم يتم تعريف متغير A.KEY نهائية.

 A.KEY="C" <-- this compiles.

ولكن -

public class C implements I{

    public static void main (String args[])
    {
        C t = new C();
        c.KEY="V"; <--- compiler error ! can't assign to final

    }
}

ويجب عدم الوصول ثابت الخاص بك في هذا الطريق، واستخدام إشارة ثابتة بدلا من ذلك:

I.KEY //returns "a"
B.KEY //returns "b"

وكاعتبار تصميم،

public interface I {
    public final String KEY = "a";
}

وطرق ثابتة دوما بإرجاع المفتاح الأصل.

public class A implements I {
    public String KEY = "b";

    public String getKey() {
        return KEY; // returns "b"
    }

    public static String getParentKey(){
        return KEY; // returns "a"
    }
}

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

والميادين والطرق الثابتة وتعلق على الدرجة / واجهة معلنا لهم (على الرغم من واجهات لا يمكن أن تعلن أساليب ثابتة كما هي فئات مجردة كليا التي يتعين تنفيذها).

وهكذا، إذا كان لديك واجهة مع ساكنة العام (vartype) (VARNAME)، ويرد هذا المجال إلى أن واجهة.

إذا كان لديك فئة تنفذ تلك الواجهة، والتحويلات مترجم خدعة (هذا). VARNAME إلى InterfaceName.varname. ولكن، إذا صفك إعادة تعريف VARNAME، ويرد اسمه ثابت VARNAME جديد لصفك، ومترجم يعرف إلى الآن ترجمة (هذا). VARNAME إلى NewClass.varname. وينطبق الشيء نفسه بالنسبة لطرق: إذا لم فئة جديدة إعادة تحديد الأسلوب، يتم ترجمتها (. هذا) METHODNAME إلى SuperClass.methodName، وإلا، يتم تفعيل (. هذا) METHODNAME إلى CurrentClass.methodName

وهذا هو السبب في أنك سوف تواجه "يجب الوصول إلى المجال س / طريقة بطريقة ثابتة" التحذير. المترجم أقول لك ذلك، على الرغم من أنها قد تستخدم خدعة، انها تفضل الذي استخدمته ClassName.method / FIELDNAME، لأنها أكثر وضوحا لأغراض القراءة.

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