CodeGym /مدونة جافا /Random-AR /نوع المحو
John Squirrels
مستوى
San Francisco

نوع المحو

نشرت في المجموعة
أهلاً! نواصل سلسلة دروسنا حول الأدوية الجنيسة. لقد حصلنا سابقًا على فكرة عامة عن ماهيتها وسبب الحاجة إليها. سنتعلم اليوم المزيد عن بعض ميزات الأدوية الجنيسة وعن العمل معها. دعنا نذهب! تحدثنا نوع المحو - 1في الدرس الأخير عن الفرق بين الأنواع العامة والأنواع الخام . النوع الخام هو فئة عامة تمت إزالة نوعها.
List list = new ArrayList();
هنا مثال. نحن هنا لا نشير إلى نوع الكائنات التي سيتم وضعها في ملف List. إذا حاولنا إنشاء مثل هذا Listوإضافة بعض الكائنات إليه، فسنرى تحذيرًا في IDEA:

"Unchecked call to add(E) as a member of raw type of java.util.List".
لكننا تحدثنا أيضًا عن حقيقة أن الأدوية العامة ظهرت فقط في Java 5. وبحلول الوقت الذي تم فيه إصدار هذا الإصدار، كان المبرمجون قد كتبوا بالفعل مجموعة من التعليمات البرمجية باستخدام الأنواع الأولية، لذلك لا يمكن أن تتوقف ميزة اللغة هذه عن العمل، والقدرة على تم الحفاظ على إنشاء أنواع خام في Java. ومع ذلك، تبين أن المشكلة أصبحت أكثر انتشارا. كما تعلم، يتم تحويل كود Java إلى تنسيق مترجم خاص يسمى bytecode، والذي يتم تنفيذه بعد ذلك بواسطة جهاز Java الظاهري. ولكن إذا وضعنا معلومات حول معلمات النوع في الكود الثانوي أثناء عملية التحويل، فسيؤدي ذلك إلى كسر جميع التعليمات البرمجية المكتوبة مسبقًا، لأنه لم تكن هناك معلمات نوع قبل Java 5! عند العمل مع الأدوية الجنيسة، هناك مفهوم واحد مهم جدًا عليك أن تتذكره. ويسمى محو النوع . وهذا يعني أن الفصل لا يحتوي على معلومات حول معلمة النوع. تتوفر هذه المعلومات فقط أثناء التجميع ويتم مسحها (يصبح غير قابل للوصول) قبل وقت التشغيل. إذا حاولت وضع نوع خاطئ من الكائنات في ملفك List<String>، فسيقوم المترجم بإنشاء خطأ. هذا هو بالضبط ما يريد منشئو اللغة تحقيقه عندما قاموا بإنشاء الأدوية العامة: فحوصات وقت الترجمة. ولكن عندما يتحول كل كود Java الخاص بك إلى كود ثانوي، فإنه لم يعد يحتوي على معلومات حول معلمات النوع. في الرمز الثانوي، List<Cat>لا تختلف قائمة القطط الخاصة بك عن List<String>السلاسل. في الرمز الثانوي، لا يوجد شيء يشير إلى أن catsهذه قائمة Catبالكائنات. يتم مسح هذه المعلومات أثناء التجميع - فقط حقيقة أن لديك قائمة List<Object> catsستنتهي في الكود الثانوي للبرنامج. دعونا نرى كيف يعمل هذا:
public class TestClass<T> {

   private T value1;
   private T value2;

   public void printValues() {
       System.out.println(value1);
       System.out.println(value2);
   }

   public static <T> TestClass<T> createAndAdd2Values(Object o1, Object o2) {
       TestClass<T> result = new TestClass<>();
       result.value1 = (T) o1;
       result.value2 = (T) o2;
       return result;
   }

   public static void main(String[] args) {
       Double d = 22.111;
       String s = "Test String";
       TestClass<Integer> test = createAndAdd2Values(d, s);
       test.printValues();
   }
}
لقد أنشأنا فئة عامة خاصة بنا TestClass. الأمر بسيط جدًا: إنها في الواقع "مجموعة" صغيرة من كائنين، يتم تخزينهما فورًا عند إنشاء الكائن. لديها 2 Tالحقول. عند createAndAdd2Values()تنفيذ الطريقة، يجب تحويل الكائنين اللذين تم تمريرهما ( Object aويجب Object bتحويلهما إلى Tالنوع ثم إضافتهما إلى TestClassالكائن. في main()الطريقة، نقوم بإنشاء ملف TestClass<Integer>، أي أن Integerوسيطة النوع تحل محل Integerمعلمة النوع. كما نقوم أيضًا بتمرير a Doubleو a Stringإلى الطريقة createAndAdd2Values(). هل تعتقد أن برنامجنا سيعمل؟ بعد كل شيء، لقد حددنا Integerوسيطة النوع، ولكن بالتأكيد Stringلا يمكن إرسالها إلى Integer! فلنقم بتشغيل main()الطريقة والتحقق.

22.111 
Test String
كان هذا غير متوقع! لماذا حدث هذا؟ إنها نتيجة محو النوع. تم مسح المعلومات حول Integerوسيطة النوع المستخدمة لإنشاء مثيل TestClass<Integer> testللكائن الخاص بنا عندما تم تجميع التعليمات البرمجية. يصبح المجال TestClass<Object> test. تم تحويل وسيطاتنا وبسهولة إلى كائنات (لم يتم تحويلها إلى كائنات كما توقعنا!) وتمت إضافتها بهدوء Doubleإلى . إليك مثال آخر بسيط ولكنه كاشف جدًا لمحو الكتابة: StringObjectIntegerTestClass
import java.util.ArrayList;
import java.util.List;

public class Main {

   private class Cat {

   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       List<Integer> numbers = new ArrayList<>();
       List<Cat> cats = new ArrayList<>();

       System.out.println(strings.getClass() == numbers.getClass());
       System.out.println(numbers.getClass() == cats.getClass());

   }
}
إخراج وحدة التحكم:

true 
true
يبدو أننا أنشأنا مجموعات بثلاثة أنواع مختلفة من الوسائط - Stringو Integerو وفئتنا الخاصة Cat. ولكن أثناء التحويل إلى الرمز الثانوي، تصبح القوائم الثلاث جميعها List<Object>، لذلك عندما يتم تشغيل البرنامج، يخبرنا أننا نستخدم نفس الفئة في جميع الحالات الثلاث.

محو الكتابة عند العمل مع المصفوفات والأسماء العامة

هناك نقطة مهمة جدًا يجب فهمها بوضوح عند العمل مع المصفوفات والفئات العامة (مثل List). يجب عليك أيضًا أخذها بعين الاعتبار عند اختيار هياكل البيانات لبرنامجك. الأدوية العامة تخضع لمسح النوع. لا تتوفر معلومات حول معلمات النوع في وقت التشغيل. على النقيض من ذلك، تعرف المصفوفات المعلومات المتعلقة بنوع بياناتها ويمكنها استخدامها عند تشغيل البرنامج. ستؤدي محاولة وضع نوع غير صالح في مصفوفة إلى طرح استثناء:
public class Main2 {

   public static void main(String[] args) {

       Object x[] = new String[3];
       x[0] = new Integer(222);
   }
}
إخراج وحدة التحكم:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
نظرًا لوجود فرق كبير بين المصفوفات والأسماء العامة، فقد يكون لديهم مشكلات في التوافق. قبل كل شيء، لا يمكنك إنشاء مصفوفة من الكائنات العامة أو حتى مجرد مصفوفة ذات معلمات. هل يبدو هذا مربكا بعض الشيء؟ لنلقي نظرة. على سبيل المثال، لا يمكنك القيام بأي من هذا في Java:
new List<T>[]
new List<String>[]
new T[]
إذا حاولنا إنشاء مصفوفة من List<String>الكائنات، فسنحصل على خطأ في الترجمة يشكو من إنشاء مصفوفة عامة:
import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       // Compilation error! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
ولكن لماذا يتم ذلك؟ لماذا لا يسمح بإنشاء مثل هذه المصفوفات؟ هذا كله لتوفير نوع الأمان. إذا سمح لنا المترجم بإنشاء مثل هذه المصفوفات من الكائنات العامة، فيمكننا أن نسبب الكثير من المشاكل لأنفسنا. فيما يلي مثال بسيط من كتاب جوشوا بلوخ "جافا الفعالة":
public static void main(String[] args) {

   List<String>[] stringLists = new List<String>[1];  //  (1)
   List<Integer> intList = Arrays.asList(42, 65, 44);  //  (2)
   Object[] objects = stringLists;  //  (3)
   objects[0] = intList;  //  (4)
   String s = stringLists[0].get(0);  //  (5)
}
لنتخيل أن إنشاء مصفوفة مثل هذا List<String>[] stringListsمسموح به ولن يؤدي إلى حدوث خطأ في الترجمة. إذا كان هذا صحيحًا، فإليك بعض الأشياء التي يمكننا القيام بها: في السطر 1، نقوم بإنشاء مجموعة من القوائم: List<String>[] stringLists. تحتوي مجموعتنا على واحد List<String>. في السطر 2، نقوم بإنشاء قائمة من الأرقام: List<Integer>. في السطر 3، قمنا بتعيين List<String>[]متغير Object[] objects. تسمح لغة Java بذلك: يمكن لمجموعة من Xالكائنات تخزين Xالكائنات والكائنات من جميع الفئات الفرعية X. وفقا لذلك، يمكنك وضع أي شيء على الإطلاق في Objectمجموعة. في السطر الرابع، نستبدل العنصر الوحيد في المصفوفة objects()(a List<String>) بـ List<Integer>. وهكذا، قمنا بوضع List<Integer>مصفوفة كانت مخصصة فقط لتخزين List<String>الكائنات! سنواجه خطأ فقط عندما ننفذ السطر 5. ClassCastExceptionسيتم طرح A في وقت التشغيل. وفقا لذلك، تمت إضافة حظر على إنشاء مثل هذه المصفوفات إلى جافا. وهذا يتيح لنا تجنب مثل هذه المواقف.

كيف يمكنني التغلب على محو الكتابة؟

حسنًا، لقد تعلمنا عن محو الكتابة. دعونا نحاول خداع النظام! :) المهمة: لدينا TestClass<T>فئة عامة. نريد أن نكتب createNewT()طريقة لهذه الفئة من شأنها إنشاء Tكائن جديد وإرجاعه. لكن هذا مستحيل، أليس كذلك؟ يتم مسح جميع المعلومات المتعلقة بالنوع Tأثناء التجميع، وفي وقت التشغيل لا يمكننا تحديد نوع الكائن الذي نحتاج إلى إنشائه. هناك في الواقع طريقة واحدة صعبة للقيام بذلك. ربما تتذكر أن Java بها Classفصل دراسي. يمكننا استخدامه لتحديد فئة أي من الكائنات لدينا:
public class Main2 {

   public static void main(String[] args) {

       Class classInt = Integer.class;
       Class classString = String.class;

       System.out.println(classInt);
       System.out.println(classString);
   }
}
إخراج وحدة التحكم:

class java.lang.Integer 
class java.lang.String
ولكن هنا جانب واحد لم نتحدث عنه. في وثائق أوراكل، سترى أن فئة الفصل عامة! نوع المحو - 3

https://docs.Oracle.com/javase/8/docs/api/Java/lang/Class.html

تقول الوثائق، "T - نوع الفئة التي تم تصميمها بواسطة كائن الفئة هذا." بترجمة هذا من لغة التوثيق إلى كلام عادي، نفهم أن فئة الكائن Integer.classليست فقط Class، بل بالأحرى Class<Integer>. نوع الكائن String.classليس فقط Class، بل Class<String>، وما إلى ذلك. إذا كان الأمر لا يزال غير واضح، فحاول إضافة معلمة نوع إلى المثال السابق:
public class Main2 {

   public static void main(String[] args) {

       Class<Integer> classInt = Integer.class;
       // Compilation error!
       Class<String> classInt2 = Integer.class;


       Class<String> classString = String.class;
       // Compilation error!
       Class<Double> classString2 = String.class;
   }
}
والآن، باستخدام هذه المعرفة، يمكننا تجاوز محو الكتابة وإنجاز مهمتنا! دعونا نحاول الحصول على معلومات حول معلمة النوع. حجة النوع لدينا ستكون MySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("A MySecretClass object was created successfully!");
   }
}
وإليك كيفية استخدام حلنا عمليًا:
public class TestClass<T> {

   Class<T> typeParameterClass;

   public TestClass(Class<T> typeParameterClass) {
       this.typeParameterClass = typeParameterClass;
   }

   public T createNewT() throws IllegalAccessException, InstantiationException {
       T t = typeParameterClass.newInstance();
       return t;
   }

   public static void main(String[] args) throws InstantiationException, IllegalAccessException {

       TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
       MySecretClass secret = testString.createNewT();

   }
}
إخراج وحدة التحكم:

A MySecretClass object was created successfully!
لقد مررنا للتو وسيطة الفئة المطلوبة إلى مُنشئ فئتنا العامة:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
سمح لنا هذا بحفظ المعلومات حول وسيطة النوع، مما منع مسحها بالكامل. ونتيجة لذلك، تمكنا من إنشاء Tكائن! :) وبهذا ينتهي درس اليوم. يجب أن تتذكر دائمًا مسح الكتابة عند العمل مع الأدوية الجنيسة. لا يبدو هذا الحل مناسبًا جدًا، لكن يجب أن تفهم أن الأدوية العامة لم تكن جزءًا من لغة Java عند إنشائها. هذه الميزة، التي تساعدنا في إنشاء مجموعات ذات معلمات واكتشاف الأخطاء أثناء التجميع، تم تناولها لاحقًا. في بعض اللغات الأخرى التي تضمنت أسماء عامة من الإصدار الأول، لا يوجد مسح للنوع (على سبيل المثال، في C#). بالمناسبة، لم ننتهي من دراسة الأدوية الجنيسة! في الدرس التالي، سوف تتعرف على بعض الميزات الإضافية للأدوية الجنيسة. في الوقت الحالي، سيكون من الجيد حل بعض المهام! :)
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION