JavaRush /جاوا بلاگ /Random-UR /مٹانے والی اقسام

مٹانے والی اقسام

گروپ میں شائع ہوا۔
ہیلو! ہم جنرکس پر لیکچرز کا سلسلہ جاری رکھتے ہیں۔ اس سے پہلے ، ہم نے عام اصطلاحات میں پتہ لگایا کہ یہ کیا ہے اور اس کی ضرورت کیوں ہے۔ آج ہم جنرک کی کچھ خصوصیات کے بارے میں بات کریں گے اور ان کے ساتھ کام کرتے وقت کچھ نقصانات کو دیکھیں گے۔ جاؤ! پچھلے لیکچرСтирание типов - 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>، مرتب کرنے والا ایک غلطی پھینک دے گا۔ یہ بالکل وہی ہے جو زبان کے تخلیق کاروں نے generics تخلیق کرکے حاصل کیا - تالیف کے مرحلے پر جانچ پڑتال۔ لیکن جب آپ کے لکھے ہوئے تمام جاوا کوڈ بائیک کوڈ میں بدل جاتے ہیں، تو پیرامیٹر کی اقسام کے بارے میں کوئی معلومات نہیں ہوں گی۔ بائیک کوڈ کے اندر، آپ کی بلیوں کی فہرست تاروں 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 an object, которые помещаются туда сразу же при создании an object. В качестве полей у него 2 an object T. При выполнении метода createAndAdd2Values() должно произойти приведение двух переданных an objectов Object a и Object b к нашему типу T, после чего они будут добавлены в an object TestClass. В методе main() мы создаем TestClass<Integer>, то есть в качестве T у нас будет Integer. Но при этом в метод createAndAdd2Values() мы передаем число Double и an object String. Как ты думаешь, сработает ли наша программа? Ведь в качестве типа-параметра мы указали Integer, а String точно нельзя привести к Integer! Давай запустим метод main() и проверим. Вывод в консоль: 22.111 Test String Неожиданный результат! Почему такое произошло? Именно из-за стирания типов. Во время компиляции codeа информация о типе-параметре Integer нашего an object TestClass<Integer> test стерлась. Он превратился в TestClass<Object> test. Наши параметры Double и String без проблем преобразовались в Object (а не в Integer, How мы того ожидали!) и спокойно добавorсь в TestClass. Вот еще один простой, но очень показательный пример стирания типов:
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. Но во время преобразования в byte-code все три списка превратorсь в List<Object>, поэтому при выполнении программа говорит нам, что во всех трех случаях у нас используется один и тот же класс.

Стирание типов при работе с массивами и дженериками

Есть один очень важный момент, который необходимо четко понимать при работе с массивами и дженериками (например, List). Также его стоит учитывать при выборе структуры данных для твоей программы. Дженерики подвержены стиранию типов. Информация о типе-параметре недоступна во время выполнения программы. В отличие от них, массивы знают и могут использовать информацию о своем типе данных во время выполнения программы. Попытка поместить в массив meaning неверного типа приведет к исключению:
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
Из-за того, что между массивами и дженериками есть такая большая разница, у них могут возникнуть проблемы с совместимостью. Прежде всего, ты не можешь создать массив an objectов-дженериков or даже просто типизированный массив. Звучит немного непонятно? Давай рассмотрим наглядно. К примеру, ты не сможешь сделать в Java ничего из этого:
new List<T>[]
new List<String>[]
new T[]
Если мы попытаемся создать массив списков List<String>, получим ошибку компиляции generic array creation:
import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       //ошибка компиляции! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
Но для чего это сделано? Почему создание таких массивов запрещено? Это все — для обеспечения типобезопасности. Если бы компилятор позволял нам создавать такие массивы из an objectов-дженериков, мы могли бы заработать кучу проблем. Вот простой пример из книги Джошуа Блоха “Effective Java”:
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 было бы разрешено, и компилятор бы не ругался. Вот Howих дел мы могли бы наворотить в этом случае: В строке 1 мы создаем массив листов List<String>[] stringLists. Наш массив вмещает в себя один List<String>. В строке 2 мы создаем список чисел List<Integer>. В строке 3 мы присваиваем наш массив List<String>[] в переменную Object[] objects. Язык Java позволяет это делать: в массив an objectов X можно помещать и an objectы X, и an objectы всех дочерних классов Х. Соответственно, в массив Objects можно поместить вообще все что угодно. В строке 4 мы подменяем единственный элемент массива objects (List<String>) на список List<Integer>. В результате мы поместor List<Integer> в наш массив, который предназначался только для хранения List<String>! С ошибкой же мы столкнемся только когда code дойдет до строки 5. Во время выполнения программы будет выброшено исключение ClassCastException. Поэтому запрет на создание таких массивов и был введен в язык Java — это позволяет нам избегать подобных ситуаций.

Как можно обойти стирание типов?

What ж, стирание типов мы изучor. Давай попробуем обмануть систему! :) Задача: У нас есть класс-дженерик TestClass<T>. Нам нужно создать в нем метод createNewT(), который будет создавать и возвращать новый an object типа Т. Но ведь это невозможно сделать, так? Вся информация о типе Т будет стерта во время компиляции, и в процессе работы программы мы не сможем узнать, an object Howого именно типа нам нужно создать. На самом деле, есть один хитрый способ. Ты наверняка помнишь, что в Java есть класс Class. Используя его, мы можем получить класс любого нашего an object:
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
Но вот одна особенность, о которой мы не говорor. В documentации Oracle ты увидишь, что класс Class — это дженерик! Стирание типов - 3В documentации написано: “Т — это тип класса, моделируемого этим an objectом Class”. Если перевести это с языка documentации на человеческий, это означает, что классом для an object Integer.class является не просто Class, а Class<Integer>. Типом an object 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("Объект секретного класса успешно создан!");
   }
}
А вот How мы используем на практике наше решение:
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);
Благодаря этому мы сохранor информацию о типе-параметре и уберегли ее от стирания. В итоге мы смогли создать an object T! :) На этом сегодняшняя лекция подходит к концу. О стирании типов всегда необходимо помнить при работе с дженериками. Выглядит это дело не очень удобно, но нужно понимать — дженерики не были частью языка Java при его создании. Это позже прикрученная возможность, которая помогает нам создавать типизированные коллекции и отлавливать ошибки на этапе компиляции. В некоторых других языках, где дженерики появлялись с первой версии, стирание типов отсутствует (например, в C#). Впрочем, мы не закончor изучение дженериков! На следующей лекции ты познакомишься с еще несколькими особенностями работы с ними. А пока было бы неплохо решить пару задач! :)
تبصرے
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION