JavaRush /وبلاگ جاوا /Random-FA /انواع پاک کردن

انواع پاک کردن

در گروه منتشر شد
سلام! ما مجموعه سخنرانی های خود را در مورد ژنریک ادامه می دهیم. قبلاً به طور کلی فهمیدیم که چیست و چرا لازم است. امروز در مورد برخی از ویژگی های ژنریک ها صحبت خواهیم کرد و به برخی از مشکلات هنگام کار با آنها نگاه خواهیم کرد. برو! انواع پاک کردن - 1در آخرین سخنرانی، در مورد تفاوت بین انواع عمومی و انواع خام صحبت کردیم . در صورتی که فراموش کرده اید، Raw Type یک کلاس عمومی است که نوع آن حذف شده است.
List list = new ArrayList();
در اینجا یک مثال است. در اینجا ما مشخص نمی کنیم که چه نوع اشیایی در ما قرار می گیرد List. اگر بخواهیم یکی بسازیم Listو چند آبجکت به آن اضافه کنیم، یک هشدار در IDEa خواهیم دید:

“Unchecked call to add(E) as a member of raw type of java.util.List”.
اما ما همچنین در مورد این واقعیت صحبت کردیم که ژنریک ها فقط در نسخه جاوا 5 این زبان ظاهر می شوند. در زمان انتشار آن، برنامه نویسان کدهای زیادی را با استفاده از Raw Types نوشته بودند و برای اینکه کار خود را متوقف نکنند، توانایی ایجاد و کار با Raw Types در جاوا حفظ شد. با این حال، معلوم شد که این مشکل بسیار گسترده تر است. کد جاوا همانطور که می دانید به بایت کد خاصی تبدیل می شود که سپس توسط ماشین مجازی جاوا اجرا می شود. و اگر در طول فرآیند ترجمه اطلاعاتی در مورد انواع پارامترها در بایت کد قرار دهیم، تمام کدهای نوشته شده قبلی را می شکند، زیرا قبل از جاوا 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نوع ما ریخته شوند Tو پس از آن به شی اضافه می شوند TestClass. در روشی main()که ایجاد می کنیم TestClass<Integer>، یعنی در کیفیت Tخواهیم داشت Integer. اما در همان زمان، createAndAdd2Values()یک عدد Doubleو یک شی را به متد ارسال می کنیم String. آیا فکر می کنید برنامه ما کار خواهد کرد؟ پس از همه، ما به عنوان یک نوع پارامتر مشخص کردیم Integer، اما Stringمطمئناً نمی توان آن را به cast فرستاد 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
از آنجا که تفاوت زیادی بین آرایه ها و ژنریک ها وجود دارد، آنها می توانند مشکلات سازگاری داشته باشند. اول از همه، شما نمی توانید یک آرایه از اشیاء عمومی یا حتی فقط یک آرایه تایپ شده ایجاد کنید. کمی گیج کننده به نظر می رسد؟ بیایید نگاه دقیق تری بیندازیم. به عنوان مثال، شما نمی توانید هیچ یک از این موارد را در جاوا انجام دهید:
new List<T>[]
new List<String>[]
new T[]
اگر بخواهیم آرایه ای از لیست ها را ایجاد کنیم List<String>، یک خطای عمومی ایجاد آرایه دریافت می کنیم:
import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       //ошибка компиляции! 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و هم اشیاء همه کلاس های فرزند را در آرایه ای از اشیاء قرار دهید Х. بر این اساس، Objectsشما می توانید هر چیزی را در آرایه قرار دهید. در خط 4 عنصر واحد آرایه را objects (List<String>)با یک لیست جایگزین می کنیم List<Integer>. در نتیجه، ما List<Integer>در آرایه خود قرار دادیم که فقط برای ذخیره سازی در نظر گرفته شده بود List<String>! فقط زمانی که کد به خط 5 برسد با خطا مواجه می شویم. در طول اجرای برنامه یک استثنا ایجاد می شود ClassCastException. بنابراین، ممنوعیت ایجاد چنین آرایه هایی به زبان جاوا معرفی شد - این به ما امکان می دهد از چنین موقعیت هایی اجتناب کنیم.

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

خوب، ما در مورد پاک کردن نوع یاد گرفته ایم. بیایید سعی کنیم سیستم را فریب دهیم! :) وظیفه: ما یک کلاس عمومی داریم TestClass<T>. ما باید یک متد در آن ایجاد کنیم createNewT()که یک شی جدید از نوع ایجاد و برگرداند Т. اما انجام این کار غیرممکن است، درست است؟ تمام اطلاعات مربوط به نوع Тدر طول کامپایل پاک می شود و در حالی که برنامه در حال اجرا است، نمی توانیم بفهمیم که چه نوع شی را باید ایجاد کنیم. در واقع، یک راه دشوار وجود دارد. احتمالاً به یاد دارید که یک کلاس در جاوا وجود دارد 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مستندات می گوید: "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;
       //ошибка компиляции!
       Class<String> classInt2 = Integer.class;


       Class<String> classString = String.class;
       //ошибка компиляции!
       Class<Double> classString2 = String.class;
   }
}
و حالا با استفاده از این دانش می توانیم از نوع پاک کردن دور بزنیم و مشکل خود را حل کنیم! بیایید سعی کنیم اطلاعاتی در مورد نوع پارامتر به دست آوریم. نقش آن را کلاس ایفا می کند MySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("Объект секретного класса успешно создан!");
   }
}
در اینجا نحوه استفاده از راه حل خود در عمل آمده است:
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();

   }
}
خروجی کنسول:

Объект секретного класса успешно создан!
ما به سادگی پارامتر کلاس مورد نیاز را به سازنده کلاس عمومی خود منتقل کردیم:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
با تشکر از این، ما اطلاعات مربوط به نوع پارامتر را ذخیره کردیم و از پاک شدن آن محافظت کردیم. در نتیجه، ما توانستیم یک شی بسازیم T! :) این سخنرانی امروز را به پایان می رساند. پاک کردن نوع همیشه چیزی است که هنگام کار با ژنریک ها باید در نظر داشت. این خیلی راحت به نظر نمی رسد، اما باید بدانید که در زمان ایجاد، ژنریک ها بخشی از زبان جاوا نبودند. این ویژگی بعدی اضافه شده است که به ما کمک می کند تا مجموعه های تایپ شده را ایجاد کنیم و خطاها را در مرحله کامپایل بگیریم. برخی از زبان‌های دیگر که در آن‌ها ژنریک‌ها از نسخه ۱ وجود داشته‌اند، پاک کردن نوع ندارند (مثلاً C#). با این حال، ما مطالعه ژنریک را تمام نکرده ایم! در سخنرانی بعدی با چندین ویژگی دیگر کار با آنها آشنا خواهید شد. در ضمن حل یکی دو تا مشکل خوبه! :)
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION