generics اكواد - لماذا يتم ترجمة هذا الرمز العام في java 8؟





لغة جافا (4)


أظن أن هذا هو لأن List هي واجهة. إذا تجاهلنا حقيقة أن String final لثانية واحدة ، فيمكنك ، من الناحية النظرية ، أن يكون لديك فصل extends String (يعني أنه بإمكانك تعيينه إلى s ) ولكنه implements List<Integer> (بمعنى أنه يمكن إرجاعها من newList() ). بمجرد تغيير نوع الإرجاع من واجهة ( T extends List ) إلى فئة ملموسة ( T extends ArrayList ) يمكن أن يستنتج المجمع أنه غير قابل للتخصيص من بعضها البعض ، وينتج خطأ.

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

لقد عثرت على جزء من التعليمات البرمجية التي تتساءل عن سبب تجميعها بنجاح:

public class Main {
    public static void main(String[] args) {
        String s =  newList(); // why does this line compile?
        System.out.println(s);
    }

    private static <T extends List<Integer>> T newList() {
        return (T) new ArrayList<Integer>();
    }
}

ما المثير للاهتمام هو أنه إذا قمت بتعديل التوقيع على الأسلوب newList مع <T extends ArrayList<Integer>> فإنه لا يعمل بعد الآن.

التحديث بعد التعليقات والردود: إذا قمت بنقل النوع العام من الطريقة إلى الفئة ، فلن يتم ترجمة الشفرة بعد الآن:

public class SomeClass<T extends List<Integer>> {
    public  void main(String[] args) {
        String s = newList(); // this doesn't compile anymore
        System.out.println(s);
    }

    private T newList() {
        return (T) new ArrayList<Integer>();
    }
}



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

لذا ، newList() هي طريقة عامة ، تحتوي على معلمة نوع واحد. إذا قمت بتحديد هذه المعلمة ، فسيتحقق المجمع من ذلك:

فشل في ترجمة:

String s =  Main.<String>newList(); // this doesn't compile anymore
System.out.println(s);

يمرر خطوة الترجمة:

List<Integer> l =  Main.<ArrayList<Integer>>newList(); // this compiles and works well
System.out.println(l);

تحديد معلمة النوع

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

اكتب معلمة عند إنشاء مثيل

الحالة الأكثر شيوعًا هي تحديد الأنماط لمثيل كائن. أي لقوائم:

List<String> list = new ArrayList<>();

هنا يمكننا أن نرى أن List<String> تحدد نوع عناصر القائمة. من ناحية أخرى ، لا جديد ArrayList<>() . ويستخدم مشغل الماس بدلا من ذلك. بمعنى أن java compiler يقوم بالنوع بناءً على الإعلان.

معلمة نوع ضمني في استدعاء الأسلوب

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

public static <T extends Number> T max(T n1, T n2) {
    if (n1.doubleValue() < n2.doubleValue()) {
        return n2;
    }
    return n1;
}

يمكنك استخدامه على النحو التالي:

int max = max(3, 4); // implicit param type: Integer

او مثل هذا:

double max2 = max(3.0, 4.0); // implicit param type: Double

معلمات النوع الصريح في استدعاء الأسلوب:

قل على سبيل المثال ، هذه هي الطريقة التي يمكنك بها إنشاء قائمة فارغة آمنة من النوع:

List<Integer> noIntegers = Collections.<Integer>emptyList();

يتم تمرير المعلمة نوع <Integer> إلى الأسلوب emptyList() . القيد الوحيد هو أن عليك تحديد الفئة أيضًا. بمعنى لا يمكنك القيام بذلك:

import static java.util.Collections.emptyList;
...
List<Integer> noIntegers = <Integer>emptyList(); // this won't compile

رمز نوع وقت التشغيل

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

private static enum Letters {A, B, C}; // dummy enum
...
public static void main(String[] args) {
    Map<Letters, Integer> map = new EnumMap<>(Letters.class);
}



إذا قمت بتعريف معلمة نوع في طريقة ما ، فأنت تسمح للمتصل باختيار نوع فعلي له ، طالما أن النوع الفعلي سوف يستوفي القيود. لا يجب أن يكون هذا النوع نوعًا فعليًا للخرسانة ، فقد يكون نوعًا مجرّدًا ، أو متغيّرًا من النوع أو نوع التقاطع ، في كلمات أخرى ، أكثر عامية ، نوع افتراضي. لذلك ، كما قال Mureinik ، يمكن أن يكون هناك نوع توسيع List تنفيذ. لا يمكننا تحديد نوع التقاطع يدويًا للاحتجاج ، ولكن يمكننا استخدام متغير نوع لإظهار المنطق:

public class Main {
    public static <X extends String&List<Integer>> void main(String[] args) {
        String s = Main.<X>newList();
        System.out.println(s);
    }

    private static <T extends List<Integer>> T newList() {
        return (T) new ArrayList<Integer>();
    }
}

وبالطبع ، لا يستطيع newList() الوفاء بتوقعات إعادة مثل هذا النوع ، ولكن هذه هي مشكلة التعريف (أو التنفيذ) لهذه الطريقة. يجب أن تحصل على تحذير "غير محدد" عند إرسال ArrayList إلى T التنفيذ الصحيح الوحيد الممكن هو إرجاع null هنا ، مما يجعل الأسلوب غير ذي جدوى.

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

public class SomeClass<T extends List<Integer>> {
    public  void main(String[] args) {
        String s = newList(); // this doesn't compile anymore
        System.out.println(s);
    }

    private T newList() {
        return (T) new ArrayList<Integer>();
    }
}

تعد معلمة النوع جزءًا من عقد الفئة ، لذلك فإن الشخص الذي يقوم بإنشاء مثيل سيختار الأنواع الفعلية لذلك المثيل. طريقة المثال main جزء من هذه الفئة ويجب أن تخضع لهذا العقد. لا يمكنك اختيار T الذي تريده ؛ تم تعيين النوع الفعلي لـ T وفي Java ، لا يمكنك عادةً معرفة T

النقطة الأساسية للبرمجة العامة هي كتابة التعليمات البرمجية التي تعمل بشكل مستقل عن الأنواع الفعلية التي تم اختيارها لمعلمات النوع.

ولكن لاحظ أنه يمكنك إنشاء مثيل آخر مستقل بأي نوع تريده واستدعاء الطريقة ، على سبيل المثال

public class SomeClass<T extends List<Integer>> {
    public <X extends String&List<Integer>> void main(String[] args) {
        String s = new SomeClass<X>().newList();
        System.out.println(s);
    }

    private T newList() {
        return (T) new ArrayList<Integer>();
    }
}

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




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

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

إذا قمت بالتحويل إلى UTC ، فأضف كل ثانية ، وقم بالتحويل إلى التوقيت المحلي للعرض. أنت ستذهب خلال 11:54:08 pm LMT - 11:59:59 مساء LMT ثم 11:54:08 pm CST - 11:59:59 pm CST.





java generics compiler-errors java-8