CodeGym /وبلاگ جاوا /Random-FA /پاک کردن را تایپ کنید
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".
اما ما همچنین در مورد این واقعیت صحبت کردیم که ژنریک ها فقط در جاوا 5 ظاهر می شوند. در زمان انتشار این نسخه، برنامه نویسان قبلاً مجموعه ای از کدها را با استفاده از انواع خام نوشته بودند، بنابراین این ویژگی زبان نمی توانست کار را متوقف کند و توانایی ایجاد انواع خام در جاوا حفظ شد. با این حال، معلوم شد که این مشکل گسترده تر است. همانطور که می دانید کد جاوا به فرمت کامپایل شده خاصی به نام بایت کد تبدیل می شود که سپس توسط ماشین مجازی جاوا اجرا می شود. اما اگر در طی فرآیند تبدیل اطلاعات مربوط به پارامترهای نوع را در بایت کد قرار دهیم، تمام کدهای نوشته شده قبلی را می شکند، زیرا قبل از جاوا 5 هیچ پارامتر نوع وجود نداشت! هنگام کار با ژنریک، یک مفهوم بسیار مهم وجود دارد که باید آن را به خاطر بسپارید. به آن پاک کردن نوع می گویند . به این معنی که یک کلاس حاوی هیچ اطلاعاتی در مورد پارامتر نوع نیست. این اطلاعات فقط در حین کامپایل موجود است و قبل از زمان اجرا پاک می شود (غیرقابل دسترسی می شود). اگر بخواهید نوع اشتباهی از شی را در خود قرار دهید List<String>، کامپایلر یک خطا ایجاد می کند. این دقیقاً همان چیزی است که سازندگان زبان هنگام ایجاد ژنریک می‌خواهند به آن دست یابند: بررسی‌های زمان کامپایل. اما وقتی همه کدهای جاوا شما به بایت کد تبدیل می شوند، دیگر حاوی اطلاعاتی در مورد پارامترهای نوع نیستند. در بایت کد، لیست گربه های شما 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 شی است که بلافاصله پس از ایجاد شی ذخیره می شوند. دارای 2 Tفیلد زمانی که createAndAdd2Values()متد اجرا شد، دو شیء ارسال شده ( Object aو Object bباید به Ttype ریخته شوند و سپس به TestClassشی اضافه شوند. در main()متد یک را ایجاد می کنیم TestClass<Integer>، یعنی Integerآرگومان type جایگزین پارامتر type می شود. همچنین a و a را به آن Integerپاس می دهیم. آیا فکر می کنید برنامه ما کار می کند؟ بالاخره ما به عنوان آرگومان نوع مشخص کردیم، اما قطعا نمی توان یک را به یک فرستاد ! بیایید روش را اجرا کنیم و بررسی کنیم. خروجی کنسول: DoubleStringcreateAndAdd2Values()IntegerStringIntegermain()

22.111 
Test String
این غیر منتظره بود! چرا این اتفاق افتاد؟ این نتیجه پاک کردن نوع است. اطلاعات مربوط به Integerنوع آرگومان مورد استفاده برای نمونه سازی TestClass<Integer> testشیء ما هنگام کامپایل شدن کد پاک شد. میدان می شود TestClass<Object> test. آرگومان های ما Doubleو ما Stringبه راحتی به اشیا تبدیل شدند ( آنطور که انتظار داشتیم Objectبه اشیا تبدیل نمی شوند !) و بی سر و صدا به . در اینجا یک مثال ساده اما بسیار آشکار دیگر از پاک کردن نوع است: IntegerTestClass
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
از آنجا که تفاوت زیادی بین آرایه ها و ژنریک ها وجود دارد، ممکن است مشکلات سازگاری داشته باشند. مهمتر از همه، شما نمی توانید یک آرایه از اشیاء عمومی یا حتی فقط یک آرایه پارامتری ایجاد کنید. آیا این کمی گیج کننده به نظر می رسد؟ بیا یک نگاهی بیندازیم. به عنوان مثال، شما نمی توانید هیچ یک از این موارد را در جاوا انجام دهید:
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متغیر اختصاص می دهیم. زبان جاوا این اجازه را می دهد: آرایه ای از Xاشیا می توانند Xاشیاء و اشیاء همه زیر کلاس ها را ذخیره کنند X. بر این اساس، شما می توانید هر چیزی را در یک Objectآرایه قرار دهید. در خط 4، تنها عنصر آرایه objects()(a List<String>) را با یک جایگزین می کنیم List<Integer>. بنابراین، ما a را List<Integer>در آرایه ای قرار می دهیم که فقط برای ذخیره List<String>اشیاء در نظر گرفته شده است! تنها زمانی که خط 5 را اجرا می کنیم با خطا مواجه می شویم. A ClassCastExceptionدر زمان اجرا پرتاب می شود. بر این اساس، ممنوعیت ایجاد چنین آرایه هایی به جاوا اضافه شد. این به ما امکان می دهد از چنین موقعیت هایی اجتناب کنیم.

چگونه می توانم از پاک کردن نوع استفاده کنم؟

خوب، ما در مورد پاک کردن نوع یاد گرفتیم. بیایید سعی کنیم سیستم را فریب دهیم! :) وظیفه: ما یک کلاس عمومی داریم TestClass<T>. می خواهیم برای این کلاس متدی بنویسیم createNewT()که یک شی جدید ایجاد و برگرداند T. اما این غیر ممکن است، درست است؟ تمام اطلاعات مربوط به Tنوع در طول کامپایل پاک می شود و در زمان اجرا نمی توانیم تعیین کنیم که چه نوع شی را باید ایجاد کنیم. در واقع یک راه دشوار برای انجام این کار وجود دارد. احتمالاً به یاد دارید که جاوا 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
اما در اینجا یک جنبه است که ما در مورد آن صحبت نکرده ایم. در اسناد Oracle، خواهید دید که کلاس Class عمومی است! نوع پاک کردن - 3

https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html

مستندات می گوید: "T - نوع کلاسی که توسط این شی Class مدل شده است." با ترجمه این از زبان مستندسازی به گفتار ساده، متوجه می‌شویم که کلاس شی 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شی بسازیم! :) با آن، درس امروز به پایان می رسد. هنگام کار با ژنریک ها همیشه باید پاک کردن نوع را به خاطر بسپارید. این راه‌حل چندان راحت به نظر نمی‌رسد، اما باید بدانید که در زمان ایجاد، ژنریک‌ها بخشی از زبان جاوا نبودند. این ویژگی، که به ما کمک می‌کند مجموعه‌های پارامتری شده و خطاها را در حین کامپایل ایجاد کنیم، بعداً مورد استفاده قرار گرفت. در برخی از زبان های دیگر که شامل ژنریک های نسخه اول بودند، هیچ گونه پاک کردنی وجود ندارد (مثلاً در سی شارپ). در ضمن، ما هنوز مطالعه ژنریک را تمام نکرده ایم! در درس بعدی با چند ویژگی دیگر ژنریک آشنا خواهید شد. فعلاً خوب است که یکی دو کار را حل کنیم! :)
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION