سؤال

لقد قمت مؤخرًا بتحسين مهاراتي في Java وعثرت على بعض الوظائف التي لم أكن أعرف عنها من قبل.تعد المُهيئات الثابتة والمثيلات من هذه التقنيات.

سؤالي هو متى يمكن استخدام المُهيئ بدلاً من تضمين الكود في المُنشئ؟لقد فكرت في احتمالين واضحين:

  • يمكن استخدام مُهيئات المثيلات الثابتة/المثيلة لتعيين قيمة المتغيرات الثابتة/المثيلات "النهائية" بينما لا يستطيع المُنشئ ذلك

  • يمكن استخدام المُهيئات الثابتة لتعيين قيمة أي متغيرات ثابتة في الفصل الدراسي، والتي يجب أن تكون أكثر كفاءة من وجود كتلة من التعليمات البرمجية "if (someStaticVar == null) // do stuff" في بداية كل مُنشئ

تفترض كلتا الحالتين أن الكود المطلوب لتعيين هذه المتغيرات أكثر تعقيدًا من مجرد "var = value"، وإلا فلن يكون هناك أي سبب لاستخدام مُهيئ بدلاً من مجرد تعيين القيمة عند الإعلان عن المتغير.

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

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

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

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

المحلول

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

أجد أن "if (someStaticVar == null) // do stuff" فوضوي وعرضة للخطأ.إذا تمت تهيئته بشكل ثابت وتم الإعلان عنه final, ، فإنك تتجنب احتمال وجوده null.

ولكنني في حيرة من أمري عندما تقول:

يمكن استخدام مهيئات ثابتة / مثيل لتعيين قيمة "نهائي" المتغيرات الثابتة / المثيل بينما لا يمكن للمنشئ

أفترض أنك تقول كلا من:

  • يمكن استخدام المُهيئات الثابتة لتعيين قيمة المتغيرات الثابتة "النهائية" بينما لا يستطيع المُنشئ ذلك
  • يمكن استخدام مُهيئات المثيل لتعيين قيمة متغيرات المثيل "النهائية" بينما لا يستطيع المُنشئ ذلك

وأنت على حق في النقطة الأولى، وعلى خطأ في الثانية.يمكنك، على سبيل المثال، القيام بذلك:

class MyClass {
    private final int counter;
    public MyClass(final int counter) {
        this.counter = counter;
    }
}

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

class MyClass {
    private final int counter;
    public MyClass() {
        this(0);
    }
    public MyClass(final int counter) {
        this.counter = counter;
    }
}

نصائح أخرى

لا يمكن أن تحتوي الفئات الداخلية المجهولة على مُنشئ (لأنها مجهولة)، لذا فهي مناسبة بشكل طبيعي للمُهيئات على سبيل المثال.

غالبًا ما أستخدم كتل المُهيئ الثابتة لإعداد البيانات الثابتة النهائية، وخاصة المجموعات.على سبيل المثال:

public class Deck {
  private final static List<String> SUITS;

  static {
    List<String> list = new ArrayList<String>();
    list.add("Clubs");
    list.add("Spades");
    list.add("Hearts");
    list.add("Diamonds");
    SUITS = Collections.unmodifiableList(list);
  }

  ...
}

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

private final static List<String> SUITS =
  Collections.unmodifiableList(
    Arrays.asList("Clubs", "Spades", "Hearts", "Diamonds")
  );

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

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

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

public class MyClass {

    static private Properties propTable;

    static
    {
        try 
        {
            propTable.load(new FileInputStream("/data/user.prop"));
        } 
        catch (Exception e) 
        {
            propTable.put("user", System.getProperty("user"));
            propTable.put("password", System.getProperty("password"));
        }
    }

عكس

public class MyClass 
{
    public MyClass()
    {
        synchronized (MyClass.class) 
        {
            if (propTable == null)
            {
                try 
                {
                    propTable.load(new FileInputStream("/data/user.prop"));
                } 
                catch (Exception e) 
                {
                    propTable.put("user", System.getProperty("user"));
                    propTable.put("password", System.getProperty("password"));
                }
            }
        }
    }

لا تنس أنه يتعين عليك الآن المزامنة على مستوى الفصل الدراسي، وليس على مستوى المثيل.يؤدي ذلك إلى تكبد تكلفة لكل مثيل تم إنشاؤه بدلاً من التكلفة لمرة واحدة عند تحميل الفصل.بالإضافة إلى أنه قبيح ;-)

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

/**
 * Demonstrate order of initialization in Java.
 * @author Daniel S. Wilkerson
 */
public class CtorOrder {
  public static void main(String[] args) {
    B a = new B();
  }
}

class A {
  A() {
    System.out.println("A ctor");
  }
}

class B extends A {

  int x = initX();

  int initX() {
    System.out.println("B initX");
    return 1;
  }

  B() {
    super();
    System.out.println("B ctor");
  }

}

انتاج:

java CtorOrder
A ctor
B initX
B ctor

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

بشكل عام، يعد مُهيئ المثيل هو الأفضل للفئات الداخلية المجهولة.نلقي نظرة على كتاب الطبخ JMock لرؤية طريقة مبتكرة لاستخدامها لجعل التعليمات البرمجية أكثر قابلية للقراءة.

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

يعد JMock استثناءً، لأن هذه هي الطريقة التي تم بها استخدام إطار العمل.

هناك جانب مهم يجب عليك مراعاته عند اختيارك:

كتل التهيئة هي أعضاء للفئة/الكائن، بينما البنائين ليسوا كذلك.وهذا مهم عند النظر التمديد/الفئة الفرعية:

  1. يتم توريث المُهيئات بواسطة الفئات الفرعية.(رغم أنه يمكن تظليله)
    هذا يعني أنه من المؤكد بشكل أساسي أن تتم تهيئة الفئات الفرعية على النحو المقصود من قبل الفئة الأصلية.
  2. البنائين هم لا وارث, ، رغم ذلك.(إنهم يدعون فقط super() [أي.لا توجد معلمات] ضمنيًا أو يتعين عليك إجراء تحديد محدد super(...) اتصل يدويًا.)
    وهذا يعني أنه من الممكن أن تكون ضمنية أو صريحة super(...) call قد لا يقوم بتهيئة الفئة الفرعية كما هو مقصود من قبل الفئة الأصلية.

خذ بعين الاعتبار هذا المثال لكتلة المُهيئ:

class ParentWithInitializer {
    protected final String aFieldToInitialize;

    {
        aFieldToInitialize = "init";
        System.out.println("initializing in initializer block of: " 
            + this.getClass().getSimpleName());
    }
}

class ChildOfParentWithInitializer extends ParentWithInitializer{
    public static void main(String... args){
        System.out.println(new ChildOfParentWithInitializer().aFieldToInitialize);
    }
}

انتاج:
initializing in initializer block of: ChildOfParentWithInitializer init
-> بغض النظر عن المُنشئات التي تنفذها الفئة الفرعية، سيتم تهيئة الحقل.

الآن فكر في هذا المثال مع المنشئين:

class ParentWithConstructor {
    protected final String aFieldToInitialize;

    // different constructors initialize the value differently:
    ParentWithConstructor(){
        //init a null object
        aFieldToInitialize = null;
        System.out.println("Constructor of " 
            + this.getClass().getSimpleName() + " inits to null");
    }

    ParentWithConstructor(String... params) {
        //init all fields to intended values
        aFieldToInitialize = "intended init Value";
        System.out.println("initializing in parameterized constructor of:" 
            + this.getClass().getSimpleName());
    }
}

class ChildOfParentWithConstructor extends ParentWithConstructor{
    public static void main (String... args){
        System.out.println(new ChildOfParentWithConstructor().aFieldToInitialize);
    }
}

انتاج:
Constructor of ChildOfParentWithConstructor inits to null null
-> سيؤدي هذا إلى تهيئة الحقل ل null بشكل افتراضي، على الرغم من أنها قد لا تكون النتيجة التي تريدها.

وأود أيضًا أن أضيف نقطة واحدة إلى جانب جميع الإجابات الرائعة المذكورة أعلاه.عندما نقوم بتحميل برنامج تشغيل في JDBC باستخدام Class.forName("")، يحدث تحميل الفئة ويتم تشغيل المُهيئ الثابت لفئة برنامج التشغيل ويقوم الكود الموجود بداخله بتسجيل برنامج التشغيل في Driver Manager.يعد هذا أحد الاستخدامات المهمة لكتلة التعليمات البرمجية الثابتة.

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

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

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

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