جاء ، ورأى ، معمم: مغمور في Java Generics

يعد Java Generics أحد أهم التغييرات في تاريخ لغة Java. جعلت Generics المتوفرة مع Java 5 استخدام Java Collection Framework أسهل وأكثر ملاءمة وأكثر أمانًا. تم الكشف عن الأخطاء المرتبطة بالاستخدام غير الصحيح للأنواع في مرحلة التجميع. نعم ، وأصبحت لغة جافا نفسها أكثر أمانًا. على الرغم من بساطة الأنواع العامة ، إلا أن العديد من المطورين يجدون صعوبة في استخدامها. في هذا المنشور ، سأتحدث عن ميزات العمل مع Java Generics ، بحيث يكون لديك أقل من هذه الصعوبات. مفيد إذا لم تكن معلمًا عامًا ، وسوف يساعد على تجنب الكثير من الصعوبات عند الانغماس في الموضوع.



العمل مع المجموعات


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

public long getSum(List accounts) {   long sum = 0;   for (int i = 0, n = accounts.size(); i < n; i++) {       Object account = accounts.get(i);       if (account instanceof Account) {           sum += ((Account) account).getAmount();       }   }   return sum; } 

لقد كررنا ، استعرضنا قائمة الحسابات وراجعنا ما إذا كان العنصر من هذه القائمة هو حقًا مثيل لفئة Account - أي حساب المستخدم. تم تحويل نوع getAmount فئة Account وطريقة getAmount ، والتي أعادت المبلغ في هذا الحساب. ثم لخصوا كل ذلك وأعادوا المبلغ الإجمالي. مطلوب خطوتين:
 if (account instanceof Account) { // (1) 

 sum += ((Account) account).getAmount(); // (2) 

إذا لم تقم بتحديد ( instanceof ) للانتماء إلى فئة Account ، فمن الممكن في المرحلة الثانية وجود ClassCastException - أي تعطل البرنامج. لذلك ، كان هذا الاختيار إلزاميا.

مع ظهور Generics ، اختفت الحاجة إلى فحص النوع والصب:
 public long getSum2(List<Account> accounts) {  long sum = 0;  for (Account account : accounts) {      sum += account.getAmount();  }  return sum; } 

الطريقة الآن
 getSum2(List<Account> accounts) 
يقبل كحجج فقط قائمة كائنات Account الفئة. يشار إلى هذا التقييد في الطريقة نفسها ، في توقيعها ، لا يمكن للمبرمج ببساطة نقل أي قائمة أخرى - فقط قائمة حسابات العملاء.

لا نحتاج إلى التحقق من نوع العناصر من هذه القائمة: فهذا يعني ضمنيًا من خلال وصف النوع لمعلمة الأسلوب
 List<Account> accounts 
(يمكن قراءتها Account ). وسيقوم المترجم بإلقاء خطأ إذا حدث خطأ ما - أي إذا حاول شخص ما تمرير قائمة من الكائنات بخلاف فئة Account إلى هذه الطريقة.

في السطر الثاني من الشيك ، اختفت الحاجة أيضًا. إذا لزم الأمر ، سيتم casting في مرحلة التجميع.

مبدأ الاستبدال


مبدأ الاستبدال في Barbara Liskov هو تعريف محدد لنوع فرعي في البرمجة الشيئية. تحدد فكرة ليسكوف عن "النوع الفرعي" مفهوم الاستبدال: إذا كان S نوعًا فرعيًا من T ، فيمكن استبدال كائنات من النوع T في البرنامج بكائنات من النوع S دون أي تغييرات في الخصائص المطلوبة لهذا البرنامج.

اكتب
النوع الفرعي
رقم
عدد صحيح
قائمة <E>
ArrayList <E>
مجموعة <E>
قائمة <E>
متكرر <E>
مجموعة <E>

أمثلة على العلاقة بين النوع / النوع الفرعي

فيما يلي مثال على استخدام مبدأ الاستبدال في Java:
 Number n = Integer.valueOf(42); List<Number> aList = new ArrayList<>(); Collection<Number> aCollection = aList; Iterable<Number> iterable = aCollection; 

Integer هو نوع فرعي من Number ، وبالتالي ، يمكن تعيين المتغير n النوع Number القيمة التي Integer.valueOf(42) الطريقة Integer.valueOf(42) .

التغاير والتناقض والثابت


أولاً ، نظرية صغيرة. التغاير هو الحفاظ على التسلسل الهرمي لوراثة أنواع المصادر في الأنواع المشتقة بنفس الترتيب. على سبيل المثال ، إذا كانت القطة نوعًا فرعيًا من الحيوانات ، فإن مجموعة <القطط> هي نوع فرعي من مجموعة <الحيوانات> . لذلك ، مع مراعاة مبدأ الاستبدال ، يمكن للمرء القيام بالمهمة التالية:

العديد من الحيوانات <= العديد من القطط>

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

العديد من <القطط> = العديد من الحيوانات>

الثبات - نقص الميراث بين الأنواع المشتقة. إذا كانت القط نوعًا فرعيًا من الحيوانات ، فإن مجموعة <القطط> ليست نوعًا فرعيًا من مجموعة <الحيوانات> ومجموعة الحيوانات < ليست نوعًا فرعيًا من مجموعة <القطط> .

المصفوفات في جافا متغايرة . النوع S[] هو نوع فرعي من T[] إذا كان S نوعًا فرعيًا من T مثال التعيين:
 String[] strings = new String[] {"a", "b", "c"}; Object[] arr = strings; 

قمنا بتعيين ارتباط إلى صفيف من السلاسل إلى المتغير arr ، ونوعه هو « » . إذا لم تكن المصفوفات متغيرة ، فلن نتمكن من القيام بذلك. تسمح لك Java بالقيام بذلك ، يقوم البرنامج بتجميع وتشغيل دون أخطاء.

 arr[0] = 42; // ArrayStoreException.       

ولكن إذا حاولنا تغيير محتويات المصفوفة من خلال متغير arr وكتابة الرقم 42 هناك ، فسوف نحصل على ArrayStoreException في مرحلة تنفيذ البرنامج ، حيث أن 42 ليست سلسلة ، ولكنها رقم. هذا هو العيب في تباين مصفوفات جافا: لا يمكننا إجراء عمليات فحص في مرحلة التجميع ، وقد ينكسر شيء بالفعل في وقت التشغيل.

"Generics" ثابتة. هنا مثال:
 List<Integer> ints = Arrays.asList(1,2,3); List<Number> nums = ints; // compile-time error.      nums.set(2, 3.14); assert ints.toString().equals("[1, 2, 3.14]"); 

إذا أخذت قائمة بالأعداد الصحيحة ، فلن تكون نوعًا فرعيًا من النوع Number ، أو أي نوع فرعي آخر. هو فقط نوع فرعي من نفسه. بمعنى ، List <Integer> List<Integer> ولا شيء آخر. سيضمن المحول البرمجي أن المتغير ints المُعلن كقائمة لكائنات من فئة Integer يحتوي فقط على كائنات من فئة Integer ولا شيء آخر. في مرحلة التجميع ، يتم إجراء فحص ، ولن يسقط أي شيء في وقت التشغيل.

أحرف البدل


هل الأجيال دائمًا ثابتة؟ لا. سأقدم أمثلة:
 List<Integer> ints = new ArrayList<Integer>(); List<? extends Number> nums = ints; 

هذا هو التباين. List<Integer> - نوع فرعي من List<? extends Number> List<? extends Number>

 List<Number> nums = new ArrayList<Number>(); List<? super Integer> ints = nums; 

هذا تناقض. List<Number> هي نوع فرعي من List<? super Integer> List<? super Integer> .

يسمى سجل مثل "? extends ..." أو "? super ..." حرف بدل أو حرف بدل ، مع حد علوي ( extends ) أو حد سفلي ( super ). List<? extends Number> List<? extends Number> قد يحتوي على كائنات من الفئة Number أو ترث من Number . List<? super Number> قد يحتوي List<? super Number> على كائنات من فئتها Number أو Number ورث (نوع فائق من Number ).


يمتد B - حرف البدل مع الحد العلوي
super B - حرف بدل بحد أدنى
حيث B - يمثل الحد

يعني تسجيل النموذج T 2 <= T 1 أن مجموعة الأنواع التي وصفها T 2 هي مجموعة فرعية من مجموعة الأنواع التي وصفها T 1.

أي
رقم <=؟ يمتد الكائن
؟؟؟ يمتد رقم <=؟ يمتد الكائن
و
؟؟؟ كائن سوبر <=؟ رقم خارق


المزيد من التفسير الرياضي للموضوع

زوج من المهام لاختبار المعرفة:

1. لماذا يوجد خطأ وقت الترجمة في المثال أدناه؟ ما القيمة التي يمكنني إضافتها إلى قائمة nums ؟
 List<Integer> ints = new ArrayList<Integer>(); ints.add(1); ints.add(2); List<? extends Number> nums = ints; nums.add(3.14); // compile-time error 

الجواب
هل يجب التصريح عن الحاوية بحرف بدل ? extends ? extends ، يمكنك فقط قراءة القيم. لا يمكن إضافة أي شيء إلى القائمة باستثناء القيمة null . من أجل إضافة كائن إلى القائمة ، نحتاج إلى نوع آخر من أحرف البدل - ? super ? super


2. لماذا لا يمكنني الحصول على عنصر من القائمة أدناه؟
 public static <T> T getFirst(List<? super T> list) {  return list.get(0); // compile-time error } 

الجواب
لا يمكنك قراءة عنصر من حاوية باستخدام حرف بدل ? super ? super ، باستثناء كائن من فئة Object

 public static <T> Object getFirst(List<? super T> list) {  return list.get(0); } 



مبدأ Get and Put أو PECS (المنتج يوسع نطاق المستهلك)


توفر ميزة حرف البدل مع الحدود العلوية والسفلية ميزات إضافية تتعلق بالاستخدام الآمن للأنواع. يمكنك فقط القراءة من نوع واحد من المتغيرات ، والكتابة فقط إلى نوع آخر (الاستثناء هو القدرة على الكتابة null extends وقراءة Object لـ super ). لتسهيل تذكر وقت استخدام أي حرف بدل ، هناك مبدأ PECS - Producer Extended Consumer Super.

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

ضع في اعتبارك استخدام Wildcard ومبدأ PECS باستخدام طريقة النسخ في فئة java.util.Collections كمثال.

 public static <T> void copy(List<? super T> dest, List<? extends T> src) { … } 

يقوم الأسلوب بنسخ العناصر من قائمة src الأصلية إلى القائمة dest . src - معلن مع حرف بدل ? extends ? extends و هو المنتج و هل يصرح بحرف البدل ? super ? super ومستهلك. بالنظر إلى التباين والتناقض في حرف البدل ، يمكنك نسخ العناصر من قائمة nums قائمة nums :
 List<Number> nums = Arrays.<Number>asList(4.1F, 0.2F); List<Integer> ints = Arrays.asList(1,2); Collections.copy(nums, ints); 


إذا nums في معلمات طريقة النسخ عن طريق الخطأ nums النسخ من قائمة nums إلى قائمة nums ، فلن يسمح المترجم لنا بذلك:
 Collections.copy(ints, nums); // Compile-time error 


<؟> وأنواع Raw


يوجد أدناه حرف بدل مع حرف بدل غير محدود. نضع <?> للتو ، بدون الكلمات الرئيسية super أو extends :
 static void printCollection(Collection<?> c) {  // a wildcard collection  for (Object o : c) {      System.out.println(o);  } } 


في الواقع ، لا يزال مثل هذا البدل "غير المحدود" محدودًا ، من الأعلى. Collection<?> هي أيضًا حرف بدل ، مثل " ? extends Object ". سجل نموذج Collection<?> يعادل Collection<? extends Object> Collection<? extends Object> ، مما يعني أن المجموعة يمكن أن تحتوي على كائنات من أي فئة ، حيث أن جميع الفئات في Java ترث من Object - لذلك يسمى الاستبدال غير محدود.

إذا حذفنا إشارة النوع ، على سبيل المثال ، كما يلي:
 ArrayList arrayList = new ArrayList(); 

ثم يقولون أن ArrayList هو نوع Raw من ArrayList ذي المعلمات <T> . باستخدام أنواع Raw ، نعود إلى عصر الأدوية الجنيسة ونترك بوعي جميع الميزات المتأصلة في الأنواع ذات المعلمات.

إذا حاولنا استدعاء طريقة ذات معلمات على نوع Raw ، فسوف يعطينا المحول البرمجي تحذيرًا بعنوان "مكالمة غير محددة". إذا حاولنا تعيين مرجع لنوع Raw ذي معلمات لنوع ، فسوف يعطي المحول البرمجي تحذيرًا "مهمة غير محددة". يمكن أن يؤدي تجاهل هذه التحذيرات ، كما سنرى لاحقًا ، إلى حدوث أخطاء أثناء تنفيذ طلبنا.
 ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings; // Ok strings = arrayList; // Unchecked assignment arrayList.add(1); //unchecked call 


التقاط حرف البدل


الآن دعنا نحاول تنفيذ طريقة تسمح بعناصر القائمة بالترتيب العكسي.

 public static void reverse(List<?> list); // ! public static void reverse(List<?> list) { List<Object> tmp = new ArrayList<Object>(list); for (int i = 0; i < list.size(); i++) {   list.set(i, tmp.get(list.size()-i-1)); // compile-time error } } 

حدث خطأ في التحويل البرمجي لأن الطريقة reverse تأخذ قائمة بحرف بدل غير محدود <?> كوسيطة.
يعني <?> نفس <? extends Object> <? extends Object> . لذلك ، وفقًا لمبدأ PECS ، تكون list producer . producer ينتج فقط العناصر. ونحن في الحلقة للاستدعاء طريقة set() ، أي تحاول الكتابة إلى list . ولذا فإننا نرتاح ضد حماية جافا ، التي لا تسمح لنا بتعيين بعض القيمة حسب الفهرس.

ماذا تفعل سيساعدنا نمط Wildcard Capture . نحن هنا ننشئ طريقة rev عامة. تم التصريح عنها بمتغير من النوع T تقبل هذه الطريقة قائمة أنواع T ، ويمكننا عمل مجموعة.
 public static void reverse(List<?> list) { rev(list); } private static <T> void rev(List<T> list) { List<T> tmp = new ArrayList<T>(list); for (int i = 0; i < list.size(); i++) {   list.set(i, tmp.get(list.size()-i-1)); } } 

الآن سيتم تجميع كل شيء معنا. تم التقاط التقاط حرف البدل هنا. عندما يتم استدعاء الأسلوب reverse(List<?> list) ، يتم تمرير قائمة ببعض الكائنات (على سبيل المثال ، سلاسل أو أعداد صحيحة) كوسيطة. إذا تمكنا من التقاط نوع هذه الكائنات وتعيينها لمتغير من النوع X ، فيمكننا أن نستنتج أن T هو X

يمكنك قراءة المزيد حول Wildcard Capture هنا وهنا .

الخلاصة


إذا كنت بحاجة إلى القراءة من الحاوية ، فاستخدم حرف بدل مع الحد العلوي " ? extends ". إذا كنت بحاجة إلى الكتابة إلى الحاوية ، فاستخدم حرف بدل بحد أدنى " ? super ". لا تستخدم حرف البدل إذا كنت بحاجة إلى التسجيل والقراءة.

لا تستخدم أنواع Raw ! إذا لم يتم تعريف وسيطة النوع ، فاستخدم حرف البدل <?> .

اكتب متغيرات


عندما نكتب المعرف في أقواس زاوية ، على سبيل المثال ، <T> أو <E> عند الإعلان عن فئة أو أسلوب ، نقوم بإنشاء متغير نوع . متغير النوع هو معرف غير مؤهل يمكن استخدامه كنوع في نص فئة أو أسلوب. يمكن تحديد متغير نوع أعلاه.
 public static <T extends Comparable<T>> T max(Collection<T> coll) { T candidate = coll.iterator().next(); for (T elt : coll) {   if (candidate.compareTo(elt) < 0) candidate = elt; } return candidate; } 

في هذا المثال ، فإن التعبير T extends Comparable<T> يحدد T (متغير نوع) يحده أعلاه النوع Comparable<T> . على عكس حرف البدل ، يمكن أن تكون متغيرات الكتابة محدودة فقط في الأعلى ( extends فقط). لا يمكن الكتابة super . بالإضافة إلى ذلك ، في هذا المثال ، تعتمد T على نفسها ، وتسمى حد recursive bound - حد عودي.

هنا مثال آخر من فئة Enum:
 public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable 

هنا ، يتم تحديد معلمة فئة Enum حسب النوع E ، وهو نوع فرعي من Enum<E> .

حدود متعددة


Multiple Bounds - قيود متعددة. يتم كتابتها من خلال الحرف " & " ، أي نقول أن النوع الذي يمثله متغير من النوع T يجب أن يحده من الأعلى فئة Object والواجهة Comparable .

 <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) 

تسجيل Object & Comparable<? super T> Object & Comparable<? super T> يشكل نوع تقاطع Multiple Bounds . يتم استخدام القيد الأول - في هذه الحالة ، Object - erasure ، عملية الكتابة فوق الأنواع. يتم تنفيذه بواسطة المترجم في مرحلة التجميع.

الخلاصة


يمكن أن يقتصر متغير النوع على نوع واحد أو أكثر. في حالة وجود قيود متعددة ، يتم استخدام الحد الأيسر (القيد الأول) في عملية الاستبدال (Type Erasure).

اكتب محو


Type Erasure هو تعيين أنواع (ربما يتضمن أنواع معلمات ومتغيرات كتابة) للأنواع التي لا يتم تعيينها أبداً لأنواع معلمات أو أنواع متغيرة. نكتب هريس نوع T كـ |T| .

يتم تعريف شاشة الهريس على النحو التالي:
  • يهرس النوع ذو المعلمات G < T1 ، ...، Tn > | G |
  • يهرس نوع TC المتداخل | T |. ج
  • يهرس نوع الصفيف T [] هو | T | []
  • يهرس متغير نوع يهرس حده الأيسر
  • يهرس أي نوع آخر هو هذا النوع نفسه


أثناء تنفيذ Type Erasure (نوع mashing) ، يقوم المترجم بتنفيذ الإجراءات التالية:
  • يضيف صب نوع لتوفير نوع السلامة إذا لزم الأمر
  • يولد طرق الجسر للحفاظ على تعدد الأشكال


T (النوع)
| T | (يهرس نوع)
قائمة <عدد صحيح> قائمة <سلسلة> قائمة <قائمة <سلسلة >>
قائمة
قائمة <عدد صحيح> []
قائمة []
قائمة
قائمة
عدد
عدد
عدد صحيح
عدد صحيح
<T يمتد <T> قابل للمقارنة
قابلة للمقارنة
<T يوسع الكائن وقابل للمقارنة <؟ سوبر تي >>
كائن
LinkedCollection <E>. عقدة
LinkedCollection.Node

يوضح هذا الجدول ما تتحول إليه الأنواع المختلفة أثناء عملية الهرس ، نوع المحو.

في لقطة الشاشة أدناه مثالان للبرنامج:


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

الجواب
في Java ، لا يمكن أن يكون لطريقتين مختلفتين نفس التوقيع. في عملية مسح النوع ، سيقوم المحول البرمجي بإضافة طريقة الجسر public int compareTo(Object o) . لكن الفصل يحتوي بالفعل على طريقة بمثل هذا التوقيع بحيث يتسبب في حدوث خطأ أثناء التحويل البرمجي.

قم بتجميع فئة الاسم عن طريق إزالة طريقة compareTo(Object o) وإلقاء نظرة على الرمز الناتج الناتج باستخدام javap:
 # javap Name.class Compiled from "Name.java" public class ru.sberbank.training.generics.Name implements java.lang.Comparable<ru.sberbank.training.generics.Name> { public ru.sberbank.training.generics.Name(java.lang.String); public java.lang.String toString(); public int compareTo(ru.sberbank.training.generics.Name); public int compareTo(java.lang.Object); } 

نرى أن الفئة تحتوي على طريقة int compareTo(java.lang.Object) ، على الرغم من أننا أزلناها من شفرة المصدر. هذه هي طريقة الجسر التي أضافها المترجم.


أنواع قابلة للتكرار


في Java ، نقول أن النوع قابل reifiable إذا كان الوصول إلى معلوماته بالكامل في وقت التشغيل. تشمل الأنواع القابلة للتكرار ما يلي:
  • الأنواع البدائية ( int ، long ، boolean )
  • أنواع غير معلمة (غير عامة) ( سلسلة ، عدد صحيح )
  • الأنواع ذات المعلمات التي يتم تمثيل معلماتها كحرف بدل غير محدود (أحرف البدل غير محدودة) ( List <؟> ، Collection <؟> )
  • الأنواع الأولية (غير المُشكلة) ( List ، ArrayList )
  • المصفوفات التي تكون مكوناتها أنواع قابلة للتكرار ( int [] ، Number [] ، List <؟> [] ، List [ )


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

يعد قرار عدم إتاحة جميع الأنواع العامة في وقت التشغيل أحد قرارات التصميم الأكثر أهمية وتضاربًا في نظام نوع جافا. يتم ذلك أولاً للتوافق مع التعليمات البرمجية الموجودة. كان علي أن أدفع مقابل توافق الترحيل - لا يمكن الوصول الكامل لنظام من الأنواع العامة في وقت التشغيل.

ما هي الأنواع غير القابلة للتكرار:
  • متغير النوع ( T )
  • نوع ذي معلمة بنوع المعلمة المحدد ( القائمة <Number> ArrayList <String> ، List <List <String>> )
  • نوع ذو معلمات بحد أعلى أو أدنى محدد ( قائمة <؟ Extends Number> ، قابلة للمقارنة <؟ Super String> ). ولكن هنا تحفظ: قائمة <؟ يمتد الكائن> - غير قابل للتكرار ، ولكن القائمة <؟> - قابلة للتطبيق


ومهمة أخرى. لماذا في المثال أدناه لا يمكن إنشاء استثناء معلمات؟

 class MyException<T> extends Exception {  T t; } 

الجواب
يتحقق كل تعبير التقاط في try-catch من نوع الاستثناء الذي تم تلقيه أثناء تنفيذ البرنامج (وهو ما يعادل المثيل) ، على التوالي ، يجب أن يكون النوع قابلاً لإعادة التحقق. لذلك ، لا يمكن تحديد المعلمات القابلة للرمي وأنواعه الفرعية.

 class MyException<T> extends Exception {// Generic class may not extend 'java.lang.Throwable'  T t; } 



تحذيرات لم يتم التحقق منها


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

تلوث الكومة


كما ذكرنا سابقًا ، يؤدي تعيين مرجع لنوع Raw إلى متغير من النوع ذي المعلمات إلى التحذير "مهمة غير محددة". إذا تجاهلناه ، فمن الممكن حدوث حالة تسمى " Heap Pollution الركام" (تلوث الركام). هنا مثال:
 static List<String> t() {  List l = new ArrayList<Number>();  l.add(1);  List<String> ls = l; // (1)  ls.add("");  return ls; } 

في السطر (1) ، يحذر المترجم من "مهمة غير محددة".

نحن بحاجة إلى إعطاء مثال آخر على "تلوث الكومة" - عندما نستخدم كائنات ذات معلمات. يوضح مقتطف الشفرة أدناه بوضوح أنه لا يُسمح باستخدام أنواع ذات معلمات Varargs لأسلوب يستخدم Varargs . في هذه الحالة ، تكون معلمة الأسلوب m هي List<String>… ، أي في الواقع ، صفيف من عناصر نوع List<String> . بالنظر إلى قاعدة عرض الأنواع أثناء الهريس ، تتحول stringLists النوع إلى مصفوفة من القوائم الأولية ( List[] ) ، أي يمكن أن يتم التعيين Object[] array = stringLists; ثم اكتب إلى array كائنًا غير قائمة السلاسل (1) ، والتي ClassCastException في السلسلة (2).

 static void m(List<String>... stringLists) {  Object[] array = stringLists;  List<Integer> tmpList = Arrays.asList(42);  array[0] = tmpList; // (1)  String s = stringLists[0].get(0); // (2) } 


تأمل مثالاً آخر:
 ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings; // (1) Ok arrayList.add(1); // (2) unchecked call 

تسمح Java بالتخصيص في السطر (1). هذا ضروري للتوافق مع الإصدارات السابقة. ولكن إذا حاولنا تنفيذ طريقة add في السطر (2) ، نحصل على تحذير Unchecked call - يحذرنا المترجم من خطأ محتمل. في الواقع ، نحن نحاول إضافة عدد صحيح إلى قائمة السلاسل.

التأمل


على الرغم من أنه خلال عملية التجميع ، تخضع الأنواع ذات المعلمات لإجراء مسح نوع ، يمكننا الحصول على بعض المعلومات باستخدام Reflection.

  • كل ما يمكن إعادة قياسه متاح من خلال آلية التأمل.
  • تتوفر معلومات حول نوع حقول الفئات ومعلمات الطريقة والقيم التي يتم إرجاعها من خلال Reflection.

Reflection Reifiable , . , , , - , :
 java.lang.reflect.Method.getGenericReturnType() 

Generics java.lang.Class . :
 List<Integer> ints = new ArrayList<Integer>(); Class<? extends List> k = ints.getClass(); assert k == ArrayList.class; 


ints List<Integer> ArrayList< Integer> . ints.getClass() Class<ArrayLis> , List<Integer> List . Class<ArrayList> k Class<? extends List> , ? extends . ArrayList.class Class<ArrayList> .

الخلاصة


, Reifiable. Reifiable : , , , Raw , reifiable.

Unchecked Warnings « » .

Reflection , Reifiable. Reflection , .

Type Inference


« ». () . :
 List<Integer> list = new ArrayList<Integer>(); 

- Java 7 ArrayList :
 List<Integer> list = new ArrayList<>(); 

ArrayListList<Integer> . type inference .

Java 8 JEP 101.
Type Inference. :
  • (reduction)
  • (incorporation)
  • (resolution)

: , , — .
, . JEP 101 .

, :
 class List<E> {  static <Z> List<Z> nil() { ... };  static <Z> List<Z> cons(Z head, List<Z> tail) { ... };  E head() { ... } } 

List.nil() :
 List<String> ls = List.nil(); 

, List.nil() String — JDK 7, .

, , , :
 List.cons(42, List.nil()); //error: expected List<Integer>, found List<Object> 

JDK 7 compile-time error. JDK 8 . JEP-101, — . JDK 8 — :
 List.cons(42, List.<Integer>nil()); 


JEP-101 , , :
 String s = List.nil().head(); //error: expected String, found Object 

, . , JDK , :
 String s = List.<String>nil().head(); 


JEP 101 StackOverflow . , , 7- , 8- – ? :
 class Test {  static void m(Object o) {      System.out.println("one");  }  static void m(String[] o) {      System.out.println("two");  }  static <T> T g() {      return null;  }  public static void main(String[] args) {      m(g());  } } 


- JDK1.8:
   public static void main(java.lang.String[]);   descriptor: ([Ljava/lang/String;)V   flags: ACC_PUBLIC, ACC_STATIC   Code:     stack=1, locals=1, args_size=1        0: invokestatic  #6   // Method g:()Ljava/lang/Object;        3: checkcast     #7   // class "[Ljava/lang/String;"        6: invokestatic  #8   // Method m:([Ljava/lang/String;)V        9: return     LineNumberTable:       line 15: 0       line 16: 9 


0 g:()Ljava/lang/Object; java.lang.Object . , 3 («») , java.lang.String , 6 m:([Ljava/lang/String;) , «two».

- JDK1.7 – Java 7:
   public static void main(java.lang.String[]);   flags: ACC_PUBLIC, ACC_STATIC   Code:     stack=1, locals=1, args_size=1        0: invokestatic  #6   // Method g:()Ljava/lang/Object;        3: invokestatic  #7   // Method m:(Ljava/lang/Object;)V        6: return            LineNumberTable:       line 15: 0       line 16: 6 


, checkcast , Java 8, m:(Ljava/lang/Object;) , «one». Checkcast – , Java 8.

, Oracle JDK1.7 JDK 1.8 , Java, , .

, Java 8 , Java 7, :

 public static void main(String[] args) { m((Object)g()); } 


الخلاصة


Java Generics . , :


  • Bloch, Joshua. Effective Java. Third Edition. Addison-Wesley. ISBN-13: 978-0-13-468599-1

, Java Generics.

Source: https://habr.com/ru/post/ar416413/


All Articles