لماذا هي قائمة وليس نوع الفرعي من القائمة <كائن>؟

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

سؤال

public void wahey(List<Object> list) {}

wahey(new LinkedList<Number>());

لن يتم التحقق من المكالمة إلى الطريقة. لا أستطيع حتى إلقاء المعلمة على النحو التالي:

wahey((List<Object>) new LinkedList<Number>());

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

List<Double> ld;
wahey(ld);

داخل الطريقة WAHEY، يمكننا إضافة بعض الأوتار إلى قائمة الإدخال (مع الحفاظ على المعلمة List<Object> المرجعي). الآن، بعد استدعاء الطريقة، يشير LD إلى قائمة بنوع List<Double>, ، ولكن تحتوي القائمة الفعلية على بعض كائنات السلسلة!

هذا يبدو مختلفا عن الطريقة العادية تعمل Java بدون أعمدة. على سبيل المثال:

Object o;
Double d;
String s;

o = s;
d = (Double) o;

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

هذا يقودني للاعتقاد أن هذا قرارا تصميم بحتة فيما يتعلق بالقيود المفروضة على الأجداد. كنت آمل أن أحصل على بعض التعليقات على هذا القرار؟

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

المحلول

ما تفعله في مثال "بدون Genericonics" هو ممثلون، مما يجعل من الواضح أنك تقوم بعمل شيء غير آمن. ما يعادله مع Generics سيكون:

Object o;
List<Double> d;
String s;

o = s;
d.add((Double) o);

الذي يتصرف بنفس الطريقة (يجمع، لكنه فشل في وقت التشغيل). السبب لعدم السماح بالسلوك الذي تسأل عنه هو أنه سيسمح ضمنية الإجراءات غير الآمنة، والتي هي أكثر صعوبة في الإشعارات في التعليمات البرمجية. علي سبيل المثال:

public void Foo(List<Object> list, Object obj) {
  list.add(obj);
}

هذا يبدو جيدا تماما ونوع آمن حتى تسميها مثل هذا:

List<Double> list_d;
String s;

Foo(list_d, s);

والتي تبدو أيضا آمنة من النوع، لأنك كما لا يعرف المتصل بالضرورة ما الذي ستفعله FOO مع معلماته.

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

نصائح أخرى

النظر إذا كان ...

List<Integer> nums = new ArrayList<Integer>();
List<Object> objs = nums
objs.add("Oh no!");
int x = nums.get(0); //throws ClassCastException

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

أنها ليست فرعية من بعضها البعض بسبب كيفية عمل الأجهزة. ما تريده هو إعلان وظيفتك مثل هذا:

public void wahey(List<?> list) {}

ثم سيقبل قائمة بأي شيء يمتد الكائن. يمكنك أيضا القيام به:

public void wahey(List<? extends Number> list) {}

سيتيح لك ذلك أن تأخذ في قوائم شيء فئة فرعية من رقم.

أود أن أوصي بك نسخة من "Gava Generics and Collections" من Maurice Naftalin & Philip Wadler.

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

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

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

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

من java.sun.com.

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

في جاوة، List<S> ليس نوع فرعي List<T> متي S هو نوع فرعي من T. وبعد توفر هذه القاعدة سلامة النوع.

دعنا نقول أننا نسمح List<String> أن تكون فرعية من List<Object>. وبعد النظر في المثال التالي:

public void foo(List<Object> objects) {
    objects.add(new Integer(42));
}

List<String> strings = new ArrayList<String>();
strings.add("my string");
foo(strings); // this is not allow in java
// now strings has a string and an integer!
// what would happen if we do the following...??
String myString = strings.get(1);

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

class MyCollection<T> {
    public void addAll(Collection<T> otherCollection) {
        ...
    }
}

هنا لديك مجموعة من T"أنت تريد إضافة جميع العناصر من مجموعة أخرى. لا يمكنك استدعاء هذه الطريقة مع Collection<S> ل S نوع فرعي في T. وبعد من الناحية المثالية، هذا جيد لأنك تضيف فقط عناصر في مجموعتك، فلن تقوم بتعديل مجموعة المعلمات.

لإصلاح هذا، تقدم Java ما يسمونه "أحرف البدل". البدل هي وسيلة لتوفير التباين / كونكتافاري. فكر الآن فيما يلي باستخدام أحرف البدل:

class MyCollection<T> {
     // Now we allow all types S that are a subtype of T
     public void addAll(Collection<? extends T> otherCollection) {
         ...

         otherCollection.add(new S()); // ERROR! not allowed (Here S is a subtype of T)
     }
} 

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

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