JavaRush /Java блогу /Random-KY /Өчүрүү түрлөрү

Өчүрүү түрлөрү

Группада жарыяланган
Салам! Биз генериктер боюнча лекцияларыбызды улантабыз. Буга чейин биз бул эмне экенин жана эмне үчүн керек экенин жалпысынан түшүндүк. Бүгүн биз генериктердин кээ бир өзгөчөлүктөрү жөнүндө сүйлөшөбүз жана алар менен иштөөдө кээ бир тузактарды карап чыгабыз. Go! Акыркы лекциядаТазалоо түрлөрү - 1 биз жалпы типтер менен чийки түрлөрдүн ортосундагы айырмачылык жөнүндө сүйлөштүк . Эгер сиз унутуп калсаңыз, чийки түрү - бул анын түрү алынып салынган жалпы класс.
List list = new ArrayList();
Бул жерде бир мисал. Бул жерде биз кандай типтеги an objectтер биздин List. Эгер биз бирөөнү түзүп List, ага бир нече an objectти кошууга аракет кылсак, IDEaда эскертүү көрөбүз:

“Unchecked call to add(E) as a member of raw type of java.util.List”.
Бирок биз генериктердин тилдин Java 5 versionсында гана пайда болгондугу жөнүндө да сөз кылдык.Ал жарыкка чыкканга чейин программисттер Raw Types аркылуу көп code жазышкан жана ал иштебей калбашы үчүн, түзүү жана Java чийки түрлөрү менен иштөө сакталып калган. Бирок, бул маселе алда канча кеңири болуп чыкты. Белгилүү болгондой, Java codeу атайын bytecodeго айландырылат, ал андан кийин Java виртуалдык машинасы тарабынан аткарылат. Ал эми котормо процессинде биз bytecodeго параметр типтери жөнүндө маалыматты жайгаштырсак, анда ал мурда жазылган codeдун баарын бузуп салат, анткени Java 5ке чейин эч кандай параметр түрлөрү болгон эмес! генериктер менен иштөөдө, сиз эстен чыгарбоо керек болгон бир маанилүү өзгөчөлүк бар. Бул түрү өчүрүү деп аталат. Анын маңызы класстын ичинде анын параметр түрү жөнүндө эч кандай маалымат сакталбаганында турат. Бул маалымат компиляция стадиясында гана жеткorктүү жана иштөө убагында өчүрүлөт (жеткorксиз болуп калат). Эгер сиз туура эмес түрдөгү an objectти өзүңүздүн ичине коюуга аракет кылсаңыз List<String>, компилятор ката кетирет. Дал ушуга тилди жаратуучулар генериктерди түзүү аркылуу жетишти - компиляция этабында текшерүү. Бирок сиз жазган бардык Java codeу bytecodeго айланганда, параметр түрлөрү жөнүндө маалымат болбойт. Байтcodeдун ичинде сиздин List<Cat>мышыктардын тизмеси саптардан айырмаланbyte List<String>. Байтcodeдогу эч нерсе catsбул an objectтердин тизмеси деп айтпайт 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. Бул абдан жөнөкөй: негизи бул 2 an objectтен турган чакан "жыйноо" болуп саналат, алар an object түзүлгөндө дароо ошол жерге жайгаштырылат. Анын талаа катары 2 an objectиси бар T. Метод аткарылгандан кийин, өткөн эки an object биздин түргө чыгарылыш createAndAdd2Values()керек , андан кийин алар an objectке кошулат . Биз түзгөн ыкмада , башкача айтканда сапатта бизде болот . Бирок ошол эле учурда биз санды жана an objectти методго өткөрүп беребиз . Биздин программа ишке ашат деп ойлойсузбу? Акыры, биз параметр түрү катары көрсөттүк , бирок аны, албетте, чыгарууга болбойт ! Келгиле, методду иштетип , текшерип көрөлү. Console чыгарылышы: 22.111 Test String Күтүлбөгөн натыйжа! Эмне үчүн мындай болду? Так себеби түрү өчүрүлгөн. Кодду түзүү учурунда биздин an objectтин параметр түрү жөнүндө маалымат өчүрүлдү. Ал айланды . Параметрлерибиз эч кандай көйгөйсүз өзгөртүлдү ( жана биз күткөндөй эмес !) жана . Бул жерде дагы бир жөнөкөй, бирок өтө эле иллюстративдик үлгүнү өчүрүү: 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. Бирок bytecodeго айландыруу учурунда үч тизме тең ге айланды List<Object>, ошондуктан аткарылганда программа үч учурда тең бир классты колдонуп жатканыбызды айтат.

Массивдер жана генериктер менен иштөөдө өчүрүүнү териңиз

Массивдер жана генериктер (мисалы, ) менен иштөөдө так түшүнүү керек болгон бир маанилүү жагдай бар List. Программаңыз үчүн маалымат структурасын тандоодо да эске алуу зарыл. Генериктердин түрү өчүрүлөт. Параметрдин түрү жөнүндө маалымат программаны аткаруу учурунда жеткorктүү эмес. Ал эми массивдер программанын аткарылышы учурунда алардын маалымат түрү жөнүндө маалыматты бorшет жана колдоно алышат. Массивге туура эмес түрдөгү маанини коюуга аракет кылуу өзгөчө учурду жаратат:
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тердин массивдерин же жөн гана терилген массивди түзө албайсыз. Бир аз түшүнүксүз угулат? Келгиле, жакыныраак карап көрөлү. Мисалы, 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];
   }
}
Бирок бул эмне үчүн жасалган? Эмне үчүн мындай массивдерди түзүүгө тыюу салынган? Мунун баары типтин коопсуздугун камсыз кылуу үчүн. Эгерде компилятор жалпы an objectтерден ушундай массивдерди түзүүгө уруксат берсе, биз көп кыйынчылыктарга дуушар болушубуз мүмкүн. Бул жерде Жошуа Блоктун "Эффективдүү 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уруксат берилет жана компилятор даттанbyte деп элестетип көрөлү. Бул учурда биз эмне кылсак болот: 1-сапта биз барактардын массивин түзөбүз List<String>[] stringLists. Биздин массивде бирөө бар List<String>. 2-сапта биз сандардын тизмесин түзөбүз List<Integer>. 3-сапта биз массивибизди List<String>[]өзгөрмөгө дайындайбыз Object[] objects. Java тor муну жасоого мүмкүндүк берет: an objectтердин массивине бардык бала класстардын an objectилерин Xда, an objectтерин да сала аласыз . Демек, сиз массивге каалаган нерсени сала аласыз. 4-сапта массивдин жалгыз элементин тизме менен алмаштырабыз . Натыйжада, биз сактоо үчүн гана арналган массивибизге жайгаштырдык ! Код 5-сапка жеткенде гана катага туш болобуз. Программанын аткарылышы учурунда өзгөчө жагдай пайда болот . Ошондуктан, мындай массивдерди түзүүгө тыюу салуу Java тorне киргизилген - бул бизге мындай кырдаалдардан качууга мүмкүндүк берет. XХObjectsobjects (List<String>)List<Integer>List<Integer>List<String>ClassCastException

Типти өчүрүүнү кантип айланып өтсөм болот?

Ооба, биз типти өчүрүү жөнүндө үйрөндүк. Келгиле, системаны алдаганга аракет кылалы! :) Тапшырма: Бизде жалпы класс бар TestClass<T>. createNewT()Биз анын ичинде жаңы типтеги an objectти түзө турган жана кайтара турган ыкманы түзүшүбүз керек Т. Бирок бул мүмкүн эмес, туурабы? Тип жөнүндө бардык маалыматтар Ткомпиляция учурунда өчүрүлөт жана программа иштеп жатканда биз an objectтин кандай түрүн түзүшүбүз керектигин таба албайбыз. Чынында, бир татаал жолу бар. Жавада класс бар экенин эсиңизде болсо керек 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
Бирок бул жерде биз сөз кылбаган бир өзгөчөлүк бар. Oracle documentтеринде Класс жалпы класс экенин көрөсүз! Тазалоо түрлөрү - 3Документте мындай деп айтылат: "T - бул Класс an objectи тарабынан моделделген класстын түрү." Эгер биз муну documentация тorнен адам тorне которсок, бул 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;
   }
}
Эми, бул бorмди колдонуп, биз түрүн өчүрүүнү айланып өтүп, көйгөйүбүздү чече алабыз! Келгиле, параметр түрү жөнүндө маалымат алууга аракет кылалы. Анын ролун класс аткарат 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 тorнин бир бөлүгү болгон эмес экенин түшүнүшүңүз керек. Бул терилген коллекцияларды түзүүгө жана компиляция баскычында каталарды табууга жардам берген кийинчерээк кошулган функция. 1-versionдан бери генериктери бар кээ бир башка тилдерде типти өчүрүү жок (мисалы, C#). Бирок, биз генериктерди изилдеп бүтө элекпиз! Кийинки лекцияда сиз алар менен иштөөнүн дагы бир нече өзгөчөлүктөрү менен таанышасыз. Ал ортодо бир-эки маселени чечсе жакшы болмок! :)
Комментарийлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION