JavaRush /Java блогы /Random-KK /Өшіру түрлері

Өшіру түрлері

Топта жарияланған
Сәлеметсіз бе! Біз генериктерге арналған лекциялар топтамасын жалғастырамыз. Бұрын біз оның не екенін және не үшін қажет екенін жалпылама түрде түсіндік. Бүгін біз генериктердің кейбір ерекшеліктері туралы сөйлесеміз және олармен жұмыс істеу кезінде кейбір қателерді қарастырамыз. Бар! Соңғы дәрістеӨшіру түрлері - 1 біз жалпы типтер мен шикізат түрлерінің айырмашылығы туралы айттық . Егер сіз ұмытып қалсаңыз, Raw түрі оның түрі жойылған жалпы класс болып табылады.
List list = new ArrayList();
Міне, мысал. Бұл жерде бізде қандай нысандардың орналасатынын көрсетпейміз List. Егер біз біреуін жасауға Listжәне оған кейбір нысандарды қосуға тырыссақ, IDEa ішінде ескертуді көреміз:

“Unchecked call to add(E) as a member of raw type of java.util.List”.
Бірақ біз генериктердің тілдің Java 5 нұсқасында ғана пайда болғаны туралы да айттық.Ол шыққан кезде бағдарламашылар Raw Types көмегімен көптеген codeтар жазған болатын және оның жұмысын тоқтатпау үшін, Java-да шикізат түрлерін жасау және олармен жұмыс істеу сақталған. Алайда бұл мәселе әлдеқайда кең болып шықты. Java codeы, өзіңіз білетіндей, арнайы byte codeқа түрлендіріледі, содан кейін ол Java виртуалды машинасымен орындалады. Ал егер аударма процесінде біз byte codeқа параметр түрлері туралы ақпаратты орналастырсақ, ол бұрын жазылған барлық codeты бұзады, өйткені Java 5-ке дейін ешқандай параметр типтері болмаған! Генериктермен жұмыс істегенде, есте сақтау қажет бір маңызды мүмкіндік бар. Ол типті өшіру деп аталады. Оның мәні сынып ішінде оның параметр типі туралы ақпарат сақталмағанында жатыр. Бұл ақпарат жинақтау кезеңінде ғана қолжетімді және орындалу уақытында өшіріледі (қолжетімсіз болады). Егер сіз қате түрдегі нысанды өзіңіздің файлыңызға қоюға тырыссаңыз List<String>, компилятор қате жібереді. Дәл осыған тілді жасаушылар генериктерді жасау арқылы қол жеткізді - жинақтау кезеңінде тексерулер. Бірақ сіз жазған барлық Java codeы byte codeқа айналғанда, параметр түрлері туралы ақпарат болмайды. Байтеcodeтың ішінде сіздің List<Cat>мысықтар тізіміңіз жолдардан ерекшеленбейді List<String>. Байтеcodeта ешнәрсе catsбұл нысандардың тізімі екенін айтпайды Cat. Бұл туралы ақпарат компиляция кезінде жойылады және тек сіздің бағдарламаңызда белгілі бір тізім бар ақпарат byte codeына түседі 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. Бұл өте қарапайым: негізінен бұл an object жасалған кезде бірден орналастырылатын 2 нысанның шағын «жинағы». Оның өрістер ретінде 2 нысаны бар T. Әдіс орындалғанда, екі жіберілген нысан біздің түрімізге шығарылуы createAndAdd2Values()керек , содан кейін олар нысанға қосылады . Біз жасаған әдісте , яғни сапада бізде болады . Бірақ сонымен бірге әдіске сан мен нысанды береміз . Біздің бағдарлама жұмыс істейді деп ойлайсыз ба? Өйткені, біз параметр түрі ретінде көрсеттік , бірақ оны трансляциялау мүмкін емес ! Әдісті іске қосып , тексерейік. Консоль шығысы: 22.111 Сынақ жолы Күтпеген нәтиже! Неліктен бұл болды? Дәл түрі өшірілгендіктен. Кодты құрастыру кезінде біздің нысанның параметр типі туралы ақпарат жойылды. Ол айналды . Параметрлеріміз еш қиындықсыз түрлендірілді ( біз күткендей емес !) және оған тыныш қосылды . Міне, типті өшірудің тағы бір қарапайым, бірақ өте көрнекі мысалы: Object aObject bTTestClassmain()TestClass<Integer>TIntegercreateAndAdd2Values()DoubleStringIntegerStringIntegermain()IntegerTestClass<Integer> testTestClass<Object> testDoubleStringObjectIntegerTestClass
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қа түрлендіру кезінде барлық үш тізім де -ге айналды 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) {

       //ошибка компиляции! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
Бірақ бұл не үшін жасалды? Неліктен мұндай массивтерді құруға тыйым салынады? Мұның бәрі түр қауіпсіздігін қамтамасыз ету үшін. Егер компилятор бізге жалпы нысандардан осындай массивтер жасауға мүмкіндік берсе, біз көп қиындықтарға тап болуымыз мүмкін. Міне, Джошуа Блохтың «Тиімді 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рұқсат етіледі және компилятор шағымданбайды. Бұл жағдайда не істей аламыз: 1-жолда біз парақтар жиымын жасаймыз List<String>[] stringLists. Біздің массивте біреуі бар List<String>. 2-жолда біз сандар тізімін жасаймыз List<Integer>. 3-жолда массивімізді List<String>[]айнымалыға тағайындаймыз Object[] objects. Java тілі мұны істеуге мүмкіндік береді: нысандардың массивіне барлық еншілес сыныптардың нысандарын Xда, нысандарын да қоюға болады . Тиісінше, массивке кез келген нәрсені қоюға болады. 4-жолда массивтің жалғыз элементін тізіммен ауыстырамыз . Нәтижесінде біз тек сақтауға арналған массивімізге орналастырдық ! Біз қатені code 5-жолға жеткенде ғана кездестіреміз. Бағдарламаны орындау кезінде ерекше жағдай шығарылады . Сондықтан мұндай массивтерді құруға тыйым салу Java тіліне енгізілді - бұл бізге мұндай жағдайларды болдырмауға мүмкіндік береді. XХObjectsobjects (List<String>)List<Integer>List<Integer>List<String>ClassCastException

Түрді өшіруді қалай айналып өтуге болады?

Біз типті өшіру туралы білдік. Жүйені алдауға тырысайық! :) Тапсырма: Бізде жалпы сынып бар TestClass<T>. createNewT()Біз оған жаңа типтегі нысанды жасайтын және қайтаратын әдісті жасауымыз керек Т. Бірақ мұны істеу мүмкін емес, солай ма? Түр туралы барлық ақпарат Ткомпиляция кезінде жойылады, ал бағдарлама жұмыс істеп тұрған кезде an objectінің қандай түрін жасау керек екенін біле алмаймыз. Шындығында, бір қиын жол бар. 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
Бірақ бұл жерде біз айтпаған бір ерекшелігі бар. Oracle құжаттамасында Class жалпы класс екенін көресіз! Өшіру түрлері - 3Құжаттамада былай делінген: «T – осы Class нысанымен модельденген класс түрі». Егер біз оны құжаттама тілінен адам тіліне аударатын болсақ, бұл an object үшін класс 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);
Осының арқасында біз параметр түрі туралы ақпаратты сақтап, оны өшіруден қорғадық. Нәтижесінде біз an object жасай алдық T! :) Осымен бүгінгі лекция аяқталды. Түрді өшіру - генериктермен жұмыс істегенде әрқашан есте сақтау керек нәрсе. Бұл өте ыңғайлы емес сияқты, бірақ сіз генериктердің Java тілінің бір бөлігі болмағанын түсінуіңіз керек, ол жасалған кезде. Бұл терілген жинақтарды жасауға және құрастыру кезеңінде қателерді анықтауға көмектесетін кейінірек қосылған мүмкіндік. 1-нұсқадан бері генериктері бар кейбір басқа тілдерде типті өшіру жоқ (мысалы, C#). Дегенмен, біз генериктерді оқып біткен жоқпыз! Келесі дәрісте сіз олармен жұмыс істеудің тағы бірнеше мүмкіндіктерімен танысасыз. Осы арада бір-екі мәселені шешіп алсаңыз жақсы болар еді! :)
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION