JavaRush /Java blogi /Random-UZ /Mushuklar uchun generiklar
Viacheslav
Daraja

Mushuklar uchun generiklar

Guruhda nashr etilgan
Mushuklar uchun generiklar - 1

Kirish

Bugun Java haqida bilganlarimizni eslash uchun ajoyib kun. Eng muhim hujjatga ko'ra, ya'ni. Java tili spetsifikatsiyasi (JLS - Java Language Specifiaction), Java " 4-bob. Turlar, qiymatlar va o'zgaruvchilar " bo'limida tasvirlanganidek, qattiq terilgan tildir . Bu qanday ma'nono bildiradi? Aytaylik, bizda asosiy usul bor:
public static void main(String[] args) {
String text = "Hello world!";
System.out.println(text);
}
Kuchli terish shuni ta'minlaydiki, ushbu kod tuzilganda, kompilyator matn o'zgaruvchisining turini String sifatida belgilagan bo'lsak, uni boshqa turdagi o'zgaruvchi sifatida (masalan, butun son sifatida) hech qanday joyda ishlatishga harakat qilmasligimizni tekshiradi. . Misol uchun, agar biz matn o'rniga qiymatni saqlashga harakat qilsak 2L(ya'ni String o'rniga uzun), kompilyatsiya vaqtida xatoga duch kelamiz:

Main.java:3: error: incompatible types: long cannot be converted to String
String text = 2L;
Bular. Kuchli terish ob'ektlar ustidagi operatsiyalar faqat ushbu ob'ektlar uchun qonuniy bo'lganda bajarilishini ta'minlash imkonini beradi. Bu shuningdek, turdagi xavfsizlik deb ataladi. JLS da ta'kidlanganidek, Java-da ikkita tur toifasi mavjud: ibtidoiy tiplar va mos yozuvlar turlari. Ko'rib chiqish maqolasidan ibtidoiy turlar haqida eslab qolishingiz mumkin: " Java'da ibtidoiy tiplar: ular unchalik ibtidoiy emas ." Malumot turlari sinf, interfeys yoki massiv bilan ifodalanishi mumkin. Va bugun biz mos yozuvlar turlariga qiziqamiz. Va massivlardan boshlaylik:
class Main {
  public static void main(String[] args) {
    String[] text = new String[5];
    text[0] = "Hello";
  }
}
Ushbu kod xatosiz ishlaydi. Ma'lumki (masalan, " Oracle Java Tutorial: Massivlar " dan) massiv faqat bitta turdagi ma'lumotlarni saqlaydigan konteynerdir. Bunday holda - faqat chiziqlar. Keling, qatorga String o'rniga long qo'shishga harakat qilaylik:
text[1] = 4L;
Keling, ushbu kodni ishga tushiramiz (masalan, Repl.it Online Java Compiler da ) va xatoga yo'l qo'ying:
error: incompatible types: long cannot be converted to String
Tilning massiv va turdagi xavfsizligi bizga turga mos kelmaydigan narsani massivda saqlashga imkon bermadi. Bu turdagi xavfsizlikning namoyonidir. Bizga: "Xatoni tuzating, lekin shu vaqtgacha men kodni tuzmayman" deb aytishdi. Va buning eng muhimi shundaki, bu dastur ishga tushirilganda emas, balki kompilyatsiya paytida sodir bo'ladi. Ya'ni, biz xatolarni "bir kun" emas, balki darhol ko'ramiz. Va biz massivlar haqida eslaganimiz sababli, Java Collections Framework haqida ham eslaylik . U yerda turli tuzilmalarimiz bor edi. Masalan, ro'yxatlar. Keling, misolni qayta yozamiz:
import java.util.*;
class Main {
  public static void main(String[] args) {
    List text = new ArrayList(5);
    text.add("Hello");
    text.add(4L);
    String test = text.get(0);
  }
}
Uni kompilyatsiya qilishda biz testo'zgaruvchini ishga tushirish satrida xatoga duch kelamiz:
incompatible types: Object cannot be converted to String
Bizning holatda, List har qanday ob'ektni (ya'ni Object tipidagi ob'ektni) saqlashi mumkin. Shuning uchun kompilyator bunday mas'uliyat yukini o'z zimmasiga olmaydi, deydi. Shuning uchun biz ro'yxatdan oladigan turni aniq ko'rsatishimiz kerak:
String test = (String) text.get(0);
Ushbu ko'rsatkich turni o'zgartirish yoki turni quyish deb ataladi. Va biz 1-indeksdagi elementni olishga harakat qilmagunimizcha, hamma narsa yaxshi ishlaydi, chunki u Long turiga kiradi. Va biz adolatli xatoga duch kelamiz, lekin dastur ishlayotganda (Runtime'da):

type conversion, typecasting
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
Ko'rib turganimizdek, bu erda bir nechta muhim kamchiliklar mavjud. Birinchidan, biz ro'yxatdan olingan qiymatni String sinfiga "tashlash" ga majburmiz. Qabul qiling, bu xunuk. Ikkinchidan, xato bo'lsa, biz buni faqat dastur bajarilganda ko'ramiz. Agar bizning kodimiz murakkabroq bo'lsa, biz bunday xatoni darhol aniqlay olmasligimiz mumkin. Va ishlab chiquvchilar bunday vaziyatlarda ishni qanday osonlashtirish va kodni yanada aniqroq qilish haqida o'ylay boshladilar. Va ular tug'ildi - Generics.
Mushuklar uchun generiklar - 2

Umumiy

Shunday qilib, generiklar. Bu nima? Umumiy - ishlatiladigan turlarni tavsiflashning maxsus usuli bo'lib, kod kompilyatori o'z ishida tur xavfsizligini ta'minlash uchun foydalanishi mumkin. Bu shunday ko'rinadi:
Mushuklar uchun generiklar - 3
Mana qisqa misol va tushuntirish:
import java.util.*;
class Main {
  public static void main(String[] args) {
    List<String> text = new ArrayList<String>(5);
    text.add("Hello");
    text.add(4L);
    String test = text.get(1);
  }
}
Bu misolda bizda faqat List, lekin ListFAQAT String tipidagi ob'ektlar bilan ishlaydigan , borligini aytamiz. Va boshqalar yo'q. Qavslar ichida nima ko'rsatilgan bo'lsa, biz uni saqlashimiz mumkin. Bunday "qavslar" "burchakli qavslar" deb ataladi, ya'ni. burchakli qavslar. Kompilyator bizni satrlar ro'yxati bilan ishlashda xatolikka yo'l qo'yganimizni tekshiradi (ro'yxat matn deb ataladi). Kompilyator biz Long ni String ro'yxatiga qo'yishga shafqatsizlarcha harakat qilayotganimizni ko'radi. Va kompilyatsiya vaqtida u xato beradi:
error: no suitable method found for add(long)
String CharSequence avlodi ekanligini eslagan bo'lishingiz mumkin. Va shunga o'xshash narsani qilishga qaror qiling:
public static void main(String[] args) {
	ArrayList<CharSequence> text = new ArrayList<String>(5);
	text.add("Hello");
	String test = text.get(0);
}
Ammo bu mumkin emas va biz xatoga duch kelamiz: error: incompatible types: ArrayList<String> cannot be converted to ArrayList<CharSequence> Bu g'alati tuyuladi, chunki. qatorda CharSequence sec = "test";xatolik yo'q. Keling, buni aniqlaylik. Ular bu xatti-harakat haqida shunday deyishadi: "Generiklar o'zgarmasdir". "Invariant" nima? Menga bu haqda Vikipediyada " Kovariatsiya va kontravariatsiya " maqolasida aytilgani yoqadi :
Mushuklar uchun generiklar - 4
Shunday qilib, o'zgarmaslik - olingan turlar orasidagi merosning yo'qligi. Agar mushuk hayvonlarning kichik turi bo'lsa, u holda To'plam<Mushuklar> to'plam<Hayvonlar> to'plamining pastki turi emas va to'plam<Mushuklar> to'plamining pastki turi emas. Aytgancha, Java SE 7-dan boshlab "Olmos operatori " paydo bo'lganligini aytish kerak. Chunki ikki burchakli qavs <> olmosga o'xshaydi. Bu bizga quyidagi kabi generiklardan foydalanishga imkon beradi:
public static void main(String[] args) {
  List<String> lines = new ArrayList<>();
  lines.add("Hello world!");
  System.out.println(lines);
}
Ushbu kodga asoslanib, kompilyator tushunadi, agar biz chap tomonda String tipidagi ob'ektlarni o'z ichiga olishini ko'rsatgan bo'lsak , o'ng tomonda biz yangi ArrayListni o'zgaruvchiga saqlashni Listxohlaymiz , u ham ob'ektni saqlaydi. lineschap tomonda ko'rsatilgan turdagi. Shunday qilib, chap tomondagi kompilyator o'ng tomonning turini tushunadi yoki xulosa qiladi. Shuning uchun bu xatti-harakat ingliz tilida "Type Inference" yoki "Type Inference" deb ataladi. E'tiborga loyiq yana bir qiziqarli narsa - RAW turlari yoki "xom turlari". Chunki Jeneriklar har doim ham mavjud bo'lmagan va Java iloji boricha orqaga qarab muvofiqlikni saqlashga harakat qiladi, keyin generiklar qandaydir tarzda kod bilan ishlashga majbur bo'ladi, bu erda hech qanday umumiy belgi ko'rsatilmaydi. Keling, misolni ko'rib chiqaylik:
List<CharSequence> lines = new ArrayList<String>();
Esda tutganimizdek, bunday chiziq generiklarning o'zgarmasligi sababli tuzilmaydi.
List<Object> lines = new ArrayList<String>();
Va bu ham xuddi shu sababga ko'ra kompilyatsiya qilinmaydi.
List lines = new ArrayList<String>();
List<String> lines2 = new ArrayList();
Bunday chiziqlar kompilyatsiya qilinadi va ishlaydi. Aynan ularda Xom turlari qo'llaniladi, ya'ni. aniqlanmagan turlari. Yana bir bor ta'kidlash joizki, xomashyo turlari zamonaviy kodda qo'llanilmasligi kerak.
Mushuklar uchun generiklar - 5

Yozilgan sinflar

Shunday qilib, yozildi sinflar. Keling, qanday qilib o'zimiz yozgan sinfimizni yozishimiz mumkinligini ko'rib chiqaylik. Masalan, bizda sinf ierarxiyasi mavjud:
public static abstract class Animal {
  public abstract void voice();
}

public static class Cat extends Animal {
  public void voice(){
    System.out.println("Meow meow");
  }
}

public static class Dog extends Animal {
  public void voice(){
    System.out.println("Woof woof");
  }
}
Biz hayvonlar konteynerini amalga oshiradigan sinf yaratmoqchimiz. Har qanday sinfni o'z ichiga olgan sinf yozish mumkin edi Animal. Bu oddiy, tushunarli, LEKIN... it va mushukni aralashtirish yomon, ular bir-biri bilan do'st emas. Bundan tashqari, agar kimdir bunday idishni qabul qilsa, u noto'g'ri konteynerdan mushuklarni itlar to'plamiga tashlashi mumkin ... va bu hech qanday yaxshilikka olib kelmaydi. Va bu erda generiklar bizga yordam beradi. Masalan, amaliyotni quyidagicha yozamiz:
public static class Box<T> {
  List<T> slots = new ArrayList<>();
  public List<T> getSlots() {
    return slots;
  }
}
Bizning sinfimiz T nomli generic tomonidan belgilangan turdagi ob'ektlar bilan ishlaydi. Bu taxallusning bir turi. Chunki Umumiy sinf nomida ko'rsatilgan, keyin biz uni sinfni e'lon qilishda olamiz:
public static void main(String[] args) {
  Box<Cat> catBox = new Box<>();
  Cat murzik = new Cat();
  catBox.getSlots().add(murzik);
}
Ko'rib turganimizdek, bizda borligini ko'rsatdik Box, bu faqat bilan ishlaydi Cat. Kompilyator catBoxgeneric o'rniga umumiy nomi ko'rsatilgan joyda Tturni almashtirish kerakligini tushundi : CatT
Mushuklar uchun generiklar - 6
Bular. Box<Cat>derleyici tufayli u slotsaslida nima bo'lishi kerakligini tushunadi List<Cat>. Chunki Box<Dog>ichida bo'ladi slots, o'z ichiga olgan List<Dog>. Tur deklaratsiyasida bir nechta generiklar bo'lishi mumkin, masalan:
public static class Box<T, V> {
Umumiy nom har qanday bo'lishi mumkin, garchi ba'zi aytilmagan qoidalarga rioya qilish tavsiya etiladi - "Tur parametrlarini nomlash qoidalari": Element turi - E, kalit turi - K, raqam turi - N, T - turi uchun, V - uchun qiymat turi. Aytgancha, esda tutingki, biz generiklar o'zgarmasdir, ya'ni. meros ierarxiyasini saqlamang. Aslida, biz bunga ta'sir qilishimiz mumkin. Ya'ni, bizda generiklarni COvariant qilish imkoniyati mavjud, ya'ni. merosni bir xil tartibda saqlash. Ushbu xatti-harakatlar "Cheklangan tur" deb ataladi, ya'ni. cheklangan turlari. Misol uchun, bizning sinfimiz Boxbarcha hayvonlarni o'z ichiga olishi mumkin, keyin biz shunday umumiylikni e'lon qilamiz:
public static class Box<T extends Animal> {
Ya'ni, biz sinfning yuqori chegarasini o'rnatdik Animal. Kalit so'zdan keyin bir nechta turlarni ham ko'rsatishimiz mumkin extends. Bu shuni anglatadiki, biz ishlaydigan tur qaysidir sinfning avlodi bo'lishi va shu bilan birga ba'zi interfeyslarni amalga oshirishi kerak. Masalan:
public static class Box<T extends Animal & Comparable> {
Bunday holda, agar biz Boxmerosxo'r bo'lmagan Animalva amalga oshirmaydigan biror narsani qo'yishga harakat qilsak Comparable, kompilyatsiya paytida biz xatoga duch kelamiz:
error: type argument Cat is not within bounds of type-variable T
Mushuklar uchun generiklar - 7

Yozish usuli

Jeneriklar nafaqat turlarda, balki individual usullarda ham qo'llaniladi. Usullarni qo'llashni rasmiy qo'llanmada ko'rish mumkin: " Generics Methods ".

Fon:

Mushuklar uchun generiklar - 8
Keling, ushbu rasmga qaraylik. Ko'rib turganingizdek, kompilyator usul imzosiga qaraydi va biz kirish sifatida aniqlanmagan sinfni qabul qilayotganimizni ko'radi. Bu biz qandaydir ob'ektni qaytarib berayotganimizni imzo bilan aniqlamaydi, ya'ni. Ob'ekt. Shuning uchun, agar biz ArrayList ni yaratmoqchi bo'lsak, buni qilishimiz kerak:
ArrayList<String> object = (ArrayList<String>) createObject(ArrayList.class);
Chiqish ArrayList bo'lishini aniq yozishingiz kerak, bu xunuk va xato qilish imkoniyatini qo'shadi. Masalan, biz bunday bema'ni narsalarni yozishimiz mumkin va u tuzadi:
ArrayList object = (ArrayList) createObject(LinkedList.class);
Biz kompilyatorga yordam bera olamizmi? Ha, generiklar bizga buni qilishga imkon beradi. Keling, xuddi shu misolni ko'rib chiqaylik:
Mushuklar uchun generiklar - 9
Keyin, biz oddiygina shunday ob'ekt yaratishimiz mumkin:
ArrayList<String> object = createObject(ArrayList.class);
Mushuklar uchun generiklar - 10

Wildcard

Oracle’ning Generics bo‘yicha o‘quv qo‘llanmasiga ko‘ra, xususan, “ Joker belgilar ” bo‘limida biz “noma’lum tur”ni savol belgisi bilan tasvirlashimiz mumkin. Joker belgilar generiklarning ayrim cheklovlarini yumshatish uchun qulay vositadir. Masalan, yuqorida aytib o'tganimizdek, generiklar o'zgarmasdir. Bu shuni anglatadiki, barcha sinflar Ob'ekt turining avlodlari (kichik tiplari) bo'lsa-da, u List<любой тип>kichik tur emas List<Object>. LEKIN, List<любой тип>bu kichik tur List<?>. Shunday qilib, biz quyidagi kodni yozishimiz mumkin:
public static void printList(List<?> list) {
  for (Object elem: list) {
    System.out.print(elem + " ");
  }
  System.out.println();
}
Oddiy generiklar kabi (ya'ni joker belgilardan foydalanmasdan), joker belgilarga ega generiklar cheklanishi mumkin. Yuqori chegaralangan joker belgi tanish ko'rinadi:
public static void printCatList(List<? extends Cat> list) {
  for (Cat cat: list) {
    System.out.print(cat + " ");
  }
  System.out.println();
}
Lekin siz uni Quyi chegaradagi joker belgisi bilan ham cheklashingiz mumkin:
public static void printCatList(List<? super Cat> list) {
Shunday qilib, usul barcha mushuklarni, shuningdek, ierarxiyada yuqori (ob'ektgacha) qabul qilishni boshlaydi.
Mushuklar uchun generiklar - 11

Oʻchirish-ni yozing

Jeneriklar haqida gapirganda, "Tipni o'chirish" haqida bilishga arziydi. Darhaqiqat, turdagi o'chirish - bu generiklar kompilyator uchun ma'lumot ekanligi haqida. Dasturni bajarish jarayonida generiklar haqida boshqa ma'lumot yo'q, bu "o'chirish" deb ataladi. Bu o'chirish umumiy turning o'ziga xos turga almashtirilishiga olib keladi. Agar umumiy chegara bo'lmasa, u holda Ob'ekt turi almashtiriladi. Agar chegara ko'rsatilgan bo'lsa (masalan <T extends Comparable>, ), u almashtiriladi. Mana Oracle qoʻllanmasidan misol: “ Umumiy turlarni oʻchirish ”:
Mushuklar uchun generiklar - 12
Yuqorida aytib o'tilganidek, bu misolda umumiy To'z chegarasiga o'chiriladi, ya'ni. oldin Comparable.
Mushuklar uchun generiklar - 13

Xulosa

Generiklar juda qiziq mavzu. Umid qilamanki, bu mavzu sizni qiziqtiradi. Xulosa qilib aytishimiz mumkinki, generiklar bir tomondan turdagi xavfsizlikni va boshqa tomondan moslashuvchanlikni ta'minlash uchun kompilyatorga qo'shimcha ma'lumot berish uchun ishlab chiqaruvchilar olgan ajoyib vositadir. Va agar siz qiziqsangiz, men sizga yoqqan manbalarni ko'rib chiqishni maslahat beraman: #Viacheslav
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION