1. لماذا - سبب إدخال الآليات العامة
إذا أردنا تنفيذ مجموعة سلسلة وطلبها لتغيير الحجم ديناميكيًا ، فسوف نفكر جميعًا في استخدام ArrayList لتجميع كائنات السلسلة. ومع ذلك ، بعد فترة من الوقت ، نريد تنفيذ مجموعة من كائنات التاريخ التي يمكن تغيير حجمها. في هذا الوقت ، نأمل بالتأكيد أن نكون قادرين على إعادة استخدام تطبيق ArrayList لكائنات السلسلة التي كتبتها من قبل.
قبل Java 5 ، يكون تنفيذ ArrayList تقريبًا على النحو التالي:
الفئة العامة ArrayList {get abours get (int i) {...} public void add (Object o) {...} ... كائن خاص [] elementData ؛}من الكود أعلاه ، يمكننا أن نرى أن وظيفة إضافة المستخدمة لإضافة عناصر إلى ArrayList تتلقى معلمة من نوع الكائن. تقوم طريقة GET التي تحصل على العنصر المحدد من ArrayList أيضًا بإرجاع كائن من نوع الكائن. يخزن elementData كائن كائن الكائن الكائن في arraylist. وهذا يعني ، بغض النظر عن نوع النوع الذي تضعه في قائمة ArrayList ، فهو كائن داخله.
التنفيذ العام القائم على الميراث سيؤدي إلى مشكلتين: السؤال الأول هو حول طريقة الحصول على. في كل مرة نسميها طريقة GET ، سنعيد كائن كائن ، وفي كل مرة يتعين علينا إلقاء النوع إلى النوع الذي نحتاجه ، والذي سيبدو مزعجًا للغاية ؛ السؤال الثاني هو حول طريقة إضافة. إذا أضفت كائن ملف إلى arraylist الذي يجمع كائن السلسلة ، فلن يقوم المترجم بإنشاء أي مطالبات خطأ ، وهذا ليس ما نريد.
لذلك ، بدءًا من Java 5 ، يمكن استخدام ArrayList لإضافة معلمة نوع (معلمة النوع) عند استخدامها. يتم استخدام معلمة النوع هذه للإشارة إلى نوع العنصر في ArrayList. إن إدخال معلمات النوع يحل المشكلتين المذكورين أعلاه ، كما هو موضح في الكود التالي:
ArrayList <string> s = new ArrayList <String> () ؛ S.Add ("ABC") ؛ String S = S.Get (0) ؛ // لا حاجة لإلقاء S.Add (123) ؛ // خطأ في التجميع ، يمكنك فقط إضافة كائنات السلسلة إليها ...في الكود أعلاه ، بعد أن يعرف "المترجم" سلسلة المعلمة النوع من ArrayList ، سيكمل فحص الصب والنوع لنا.
2. فصول عامة
ما يسمى الفئة العامة هي فئة ذات معلمات واحدة أو أكثر. على سبيل المثال:
زوج الطبقة العامة <T ، U> {private t أولاً ؛ خاص يو الثاني ؛ الزوج العام (t أولاً ، u الثاني) {this.first = first ؛ this.Second = الثاني ؛ } t getFirst () {return first ؛ } public u getSecond () {return second ؛ } public void setFirst (t newValue) {first = newValue ؛ }}في الكود أعلاه ، يمكننا أن نرى أن معلمات النوع لزوج الفئة العامة هي T و U ، ويتم وضعها في أقواس الزاوية بعد اسم الفصل. هنا ، يعني t الحرف الأول من النوع ، والذي يمثل النوع. عادةً ما تستخدم E (العنصر) ، K (المفتاح) ، V (القيمة) ، إلخ. بالطبع ، من الجيد أيضًا عدم استخدام هذه الحروف للإشارة إلى معلمات النوع.
عند إنشاء فئة عامة ، نحتاج فقط إلى استبدال المعلمة النوع بنوع معين ، مثل إنشاء زوج <T ، U> ، يمكننا القيام بذلك:
زوج <string ، integer> pair = new pair <string ، integer> () ؛
3. طرق عامة
الطريقة العامة المزعومة هي طريقة مع معلمات النوع. يمكن تعريفه في فئة عامة أو فئة عادية. على سبيل المثال:
Arrayalg {public static <T> t getMiddle (t [] a) {return a [A.Length / 2] ؛ }}طريقة getMiddle في الكود أعلاه هي طريقة عامة ، والتنسيق المحدد هو أن متغير النوع يتم وضعه بعد المعدل وقبل نوع الإرجاع. يمكننا أن نرى أنه يمكن استدعاء الطرق العامة أعلاه لأنواع مختلفة من المصفوفات. عندما تكون أنواع هذه المصفوفات محدودة ، على الرغم من أنه يمكن تنفيذها أيضًا مع التحميل الزائد ، فإن كفاءة الترميز أقل بكثير. رمز المثال لاستدعاء الطريقة العامة أعلاه هو كما يلي:
String [] STRINGS = {"AA" ، "BB" ، "CC"} ؛
String Middle = ArrayAlg.getMiddle (أسماء) ؛
4. الحد من متغيرات النوع
في بعض الحالات ، تريد الفئات العامة أو الأساليب العامة الحد من معلمات نوعها. على سبيل المثال ، إذا كنا نريد تحديد معلمات النوع التي يمكن أن تكون فقط فئات فرعية لفئة معينة أو فئات فقط تنفذ واجهة معينة. بناء الجملة ذي الصلة كما يلي:
<T يمتد BoundingType> (BouldType هو فئة أو واجهة). يمكن أن يكون هناك أكثر من 1 حدود ، فقط استخدم "&" للاتصال.
5. فهم تنفيذ الأدوية الجيلية
في الواقع ، من منظور الأجهزة الافتراضية ، لا يوجد مفهوم لـ "الأدوية". على سبيل المثال ، يبدو أن زوج الفئة العام الذي حددناه أعلاه في الجهاز الظاهري (أي بعد تجميعه في Bytecode):
زوج الفئة العامة {كائن خاص أولاً ؛ كائن خاص الثاني ؛ الزوج العام (الكائن أولاً ، الكائن الثاني) {this.first = first ؛ this.Second = الثاني ؛ } الكائن العام getFirst () {return first ؛ } الكائن العام getSecond () {return second ؛ } public void setFirst (Object newValue) {first = newValue ؛ } public void setSecond (Object newValue) {second = newValue ؛ }}يتم الحصول على الفئة أعلاه عن طريق محو النوع وهو النوع الخام المقابل للفئة العامة للزوج. يعني النوع محو المسح استبدال جميع معلمات النوع باستخدام outsitytype (استبدله بكائن إذا لم تتم إضافة قيود).
يمكننا ببساطة التحقق من أنه بعد تجميع pair.java ، اكتب "javap -c -s pair" للحصول على:
الخط مع "واصف" في الشكل أعلاه هو توقيع الطريقة المقابلة. على سبيل المثال ، من السطر الرابع ، يمكننا أن نرى أن المعلمتين الرسميتين لمؤسسة الزوج أصبحت كائنًا بعد محو النوع.
نظرًا لأن زوج الفئة العام يصبح نوعه الخام في الجهاز الظاهري ، فإن طريقة getFirst تُرجع كائن كائن ، ومن منظور المترجم ، تُرجع هذه الطريقة كائنًا من المعلمة النوع المحددة عندما نؤسس الفصل. في الواقع ، فإن المترجم هو الذي يساعدنا على إكمال أعمال الصب. بمعنى آخر ، سيقوم برنامج التحويل البرمجي بتحويل المكالمة إلى طريقة getFirst في الفئة العامة للزوج إلى تعليمين من الجهاز الظاهري:
الأول هو مكالمة إلى طريقة النوع الخام getFirst ، والتي تُرجع كائن كائن ؛ يلقي التعليمات الثانية كائن الكائن الذي تم إرجاعه إلى نوع المعلمة النوع الذي حددناه.
يحدث محو النوع أيضًا في الطرق العامة ، مثل الطرق العامة التالية:
الثابت العام <t يمتد قابلة للمقارنة> t min (t [] a)
بعد التجميع ، سيصبح هكذا بعد محو النوع:
MIN الثابتة العامة المماثلة (قابلة للمقارنة [] أ)
اكتب محو الطرق يمكن أن يسبب بعض المشكلات ، فكر في الكود التالي:
يمتد DateInterval للصف زوج <date ، date> {public void setSecond (Date Second) {if (second.compareto (getFirst ())> = 0) {super.setsecond (second) ؛ }} ...}بعد تمحى الكود أعلاه حسب النوع ، يصبح:
يمتد DateInterval الفئة الزوج {public void setSecond (Date Second) {...} ...}في فئة DateInterval ، هناك أيضًا طريقة SetSecond الموروثة من الفئة الزوجية (بعد النوع المحو) على النحو التالي:
setSecond public void (كائن ثاني)
الآن يمكننا أن نرى أن هذه الطريقة لها توقيعات مختلفة (معلمات رسمية مختلفة) من طريقة SetSecond التي تجاوزت DateInterval ، لذلك فهي طريقتين مختلفتين ، ومع ذلك ، لا ينبغي أن تكون هاتان الطريقتين طرقًا مختلفة (لأنه يتم تجاوزه). النظر في الرمز التالي:
DateInterval الفاصل الزمني = DateInterval جديد (...) ؛ زوج <التاريخ ، التاريخ> الزوج = الفاصل الزمني ؛ تاريخ تاريخ = تاريخ جديد (...) ؛ pair.setsecond (adate) ؛
من الكود أعلاه ، يمكننا أن نرى أن الزوج يشير فعليًا إلى كائن DateInterval ، لذلك يجب استدعاء طريقة SetSecond لـ DateInterval. المشكلة هنا هي أن هذا النوع يمحو الصراع مع تعدد الأشكال.
دعونا نفرز سبب حدوث هذه المشكلة: تم الإعلان عن الزوج سابقًا كزوج نوع <التاريخ ، التاريخ> ، ويبدو أن هذه الفئة لديها طريقة واحدة فقط "setSecond (Object)" في الجهاز الظاهري. لذلك ، عند التشغيل ، يكتشف الجهاز الظاهري أن الزوج يشير فعليًا إلى كائن DateInterval ، فسوف يسمي "setSecond (كائن)" من DateInterval ، ولكن هناك فقط طريقة "SetSecond (Date)" في فئة DateInterval.
يتمثل حل هذه المشكلة في إنشاء طريقة جسر في DateInterval بواسطة المترجم:
setSecond public void (كائن ثاني) {setSecond ((التاريخ) الثاني) ؛}6. أشياء يجب ملاحظتها
(1) لا يمكن إنشاء معلمات النوع مع الأنواع الأساسية
وهذا هو ، البيان التالي غير قانوني:
زوج <int ، int> pair = new pair <int ، int> () ؛
ومع ذلك ، يمكننا استخدام نوع التغليف المقابل بدلاً من ذلك.
(2) لا يمكن رمي أو التقاط مثيلات فئة عامة
تمديد الطبقة العامة قابلة للتطبيق غير قانوني ، لذلك لا يمكن إلقاء حالات الطبقة العامة أو التقاطها. لكن من القانوني استخدام معلمات النوع في إعلانات الاستثناء:
الثابت العام <t يمتد رمي> void dowork (t t) يلقي t {try {...} catch (remable rewercause) {t.initcause (RealCause) ؛ رمي ر ؛ }}(3) صفيف المعلمة غير قانوني
في Java ، يمكن أن يكون كائن [] الصفيف هو الفئة الأصل لأي صفيف (لأنه يمكن تحويل أي صفيف إلى تصاعدي إلى مجموعة من الفئة الأصل التي تحدد نوع العنصر عند تحديدها). النظر في الرمز التالي:
String [] strs = سلسلة جديدة [10] ؛ كائن [] objs = strs ؛ obj [0] = تاريخ جديد (...) ؛
في الكود أعلاه ، نقوم بتعيين عنصر الصفيف إلى كائن يرضي نوع الفئة الأصل (الكائن) ، ولكن على عكس النوع الأصلي (الزوج) ، يمكن أن يمر في وقت الترجمة ، وسيتم إلقاء استثناء ArrayStoreException في وقت التشغيل.
بناءً على الأسباب المذكورة أعلاه ، لنفترض أن Java يسمح لنا بإعلان وتهيئة مجموعة عامة من خلال البيان التالي:
زوج <string ، string> [] pairs = زوج جديد <string ، string> [10] ؛
ثم بعد أن يقوم الجهاز الظاهري بإجراء محو النوع ، تصبح الأزواج فعليًا صفائف [] ، ويمكننا تحويلها لأعلى إلى كائن []. في هذا الوقت ، إذا أضفنا زوجًا <التاريخ ، التاريخ> الكائنات إليه ، فيمكننا اجتياز الشيكات وقت التجميع والشيكات وقت التشغيل. نيتنا الأصلية هي فقط ترك زوج متجر الصفيف هذا <string ، string> Objects ، مما سيؤدي إلى صعوبة تحديد الأخطاء. لذلك ، لا تسمح لنا Java بإعلان وتهيئة مجموعة عامة من خلال نموذج البيان أعلاه.
يمكن الإعلان عن صفيف عام وتهيئته باستخدام العبارة التالية:
زوج <string ، string> [] bairs = (pair <string ، string> []) زوج جديد [10] ؛
(4) لا يمكن إنشاء متغير النوع
لا يمكن استخدام متغيرات النوع في أشكال مثل "T (...)" ، "New T [...]" ، "T.Class". السبب في أن جافا تمنعنا من القيام بذلك أمر بسيط. نظرًا لوجود محو النوع ، ستصبح عبارات مثل "T (...) الجديدة" كائنًا جديدًا (...) "، وهو عادة ما لا نعنيه. يمكننا استبدال المكالمة إلى "New T [...]" بالبيان التالي:
المصفوفات = (t []) كائن جديد [n] ؛
(5) لا يمكن استخدام متغيرات النوع في السياق الثابت للفئات العامة
لاحظ أننا نؤكد فصول عامة هنا. لأنه يمكن تعريف الطرق العامة الثابتة في الفصول العادية ، مثل طريقة getMiddle في فئة Arrayalg المذكورة أعلاه. لأسباب مثل هذه القاعدة ، يرجى النظر في الرمز التالي:
أفراد الطبقة العامة <T> {public static t name ؛ ثابت عام t getName () {...}}نحن نعلم أنه في الوقت نفسه ، قد يكون هناك أكثر من مثيل فئة <T> في الذاكرة. لنفترض أن هناك كائنًا <string> للأشخاص والأشخاص <integer> في الذاكرة الآن ، ويتم مشاركة المتغيرات الثابتة والأساليب الثابتة للفئة من قبل جميع مثيلات الفصل. لذا فإن السؤال هو ، هل نوع سلسلة الاسم أو نوع عدد صحيح؟ لهذا السبب ، لا يُسمح باستخدام متغيرات النوع في Java في سياقات ثابتة من الفئات العامة.
7. اكتب البرية
قبل تقديم نوع Wildcard ، قدم أولاً نقطتين:
(1) لنفترض أن الطالب عبارة عن فئة فرعية من الناس ، لكن الزوج <الطالب ، الطالب> ليس فئة فرعية من الزوجين <الأشخاص ، الناس ، ولا توجد علاقة "IS-A" بينهما.
(2) هناك علاقة "IS-A" بين الزوج <t ، t> وزوجها الأصلي. يمكن تحويل الزوج <T ، t> إلى نوع الزوج في أي حالة.
الآن فكر في هذه الطريقة:
printName printname الثابتة العامة (زوج <people ، people> p) {people p1 = p.getFirst () ؛ System.out.println (p1.getName ()) ؛ // لنفترض أن فئة الناس تحدد طريقة مثيل getName}في الطريقة المذكورة أعلاه ، نريد أن نكون قادرين على تمرير معلمات الزوج <الطالب ، الطالب> والاقتران <People ، People> في نفس الوقت ، ولكن لا توجد علاقة "IS-A" بين الاثنين. في هذه الحالة ، توفر لنا Java حلاً: هل تستخدم الزوج <؟ يمتد الناس> كنوع من المعلمة الرسمية. وهذا يعني ، يمكن اعتبار الزوج <الطالب ، الطالب> والاقتران <الأشخاص ، كلاهما فئات فرعية من الزوج <؟ يمتد الناس>.
يسمى الكود الذي يشبه "<؟ يمتد BouldtingType>" القيد من النوع الفرعي لأحرف Wildcard. المقابل لهذا هو القيد الفائق لأحرف البدل ، التنسيق كما يلي: <؟ Super BoundingType>.
الآن دعنا ننظر في الكود التالي:
Pair <Tudent> student> = زوج جديد <STUTROM> (Student1 ، Student2) ؛ Pair <؟ يمتد الأشخاص> البرية = الطلاب ؛ wildchards.setFirst (people1) ؛
السطر الثالث من الرمز أعلاه سيبلغ عن خطأ لأن الكاردات البرية هي زوج <؟ يمتد الأشخاص> الكائن ، وطريقة setFirst وطريقة getFirst هي كما يلي:
void setFirst (؟ يمتد الناس)؟ يمتد الناس getfirst ()
بالنسبة لطريقة setFirst ، لن يعرف التحويل البرمجي نوع المعلمات الرسمية (المعروفة فقط بأنها فئة فرعية للأشخاص). عندما نحاول تمرير كائن الأشخاص ، لا يمكن للمترجم تحديد ما إذا كان الأشخاص والمعلمات الرسمية "IS-A" ، لذا فإن استدعاء طريقة setFirst سيبلغ عن خطأ. من القانوني استدعاء طريقة GetFirst للبطولات البرية لأننا نعرف أنها ستعيد الفئة الفرعية للأشخاص ، والفئة الفرعية للأشخاص "دائمًا شعب". (يمكنك دائمًا تحويل كائنات الفئة الفرعية إلى كائنات الأصل)
في حالة الحد من الحد الأقصى للبطاقات البرية ، يعد استدعاء طريقة Getter غير قانونية ، في حين أن استدعاء طريقة Setter قانونية.
بالإضافة إلى قيود النوع الفرعي والقيود الفائقة ، هناك أيضًا بطاقة برية تسمى The Infinite Wildcard ، والتي تشبه هذا: <؟>. متى سنستخدم هذا الشيء؟ النظر في هذا السيناريو. عندما نسمي طريقة ، سنعيد طريقة getPairs ، والتي ستعيد مجموعة من الكائنات الزوجية <T ، t>. من بينها زوج <الطالب ، الطالب> ، والاقتران <المعلم ، المعلم> الكائنات. (لا توجد علاقة ميراث بين فصل الطلاب وصف المعلم) من الواضح ، في هذه الحالة ، لا يمكن استخدام كل من الحد من النوع الفرعي وتقييد SuperType. في هذا الوقت ، يمكننا استخدام هذا البيان لحلها:
زوج <؟> [] أزواج = getPairs (...) ؛