Kirish
JSE 5.0 dan boshlab, generiklar Java tili arsenaliga qo'shildi.Java'da generiklar nima?
Jeneriklar (umumlashtirishlar) umumlashtirilgan dasturlashni amalga oshirish uchun Java tilining maxsus vositalaridir: ma'lumotlar va algoritmlarni tavsiflashning maxsus yondashuvi, bu sizga har xil turdagi ma'lumotlar bilan ularning tavsifini o'zgartirmasdan ishlash imkonini beradi. Oracle veb-saytida generiklarga alohida o'quv qo'llanma bag'ishlangan: " Dars: Generics ".
import java.util.*;
public class HelloWorld{
public static void main(String []args){
List list = new ArrayList();
list.add("Hello");
String text = list.get(0) + ", world!";
System.out.print(text);
}
}
Bu kod yaxshi ishlaydi. Ammo ular bizning oldimizga kelib, "Salom, dunyo!" iborasini aytishsa nima bo'ladi? kaltaklangan va siz faqat qaytishingiz mumkin Salom? Keling, koddan satr bilan bog'lanishni olib tashlaymiz ", world!"
. Aftidan, nima zararsizroq bo'lishi mumkin? Lekin, aslida, biz KOMPILATsiya vaqtida xatoga yo'l qo'yamiz : error: incompatible types: Object cannot be converted to String
Gap shundaki, bizning holatimizda Ro'yxat Ob'ekt turidagi ob'ektlar ro'yxatini saqlaydi. String Ob'ektning avlodi bo'lganligi sababli (barcha sinflar Java'da Ob'ektdan bilvosita meros qilib olinganligi sababli), u biz bajarmagan aniq tasvirni talab qiladi. Va birlashtirganda, statik usul String.valueOf(obj) ob'ektga chaqiriladi va u oxir-oqibat Ob'ektdagi toString usulini chaqiradi. Ya'ni Ro'yxatimizda Ob'ekt mavjud. Ma'lum bo'lishicha, bizga ob'ekt emas, balki ma'lum bir tur kerak bo'lsa, biz o'zimiz turdagi kastingni bajarishimiz kerak bo'ladi:
import java.util.*;
public class HelloWorld{
public static void main(String []args){
List list = new ArrayList();
list.add("Hello!");
list.add(123);
for (Object str : list) {
System.out.println((String)str);
}
}
}
Biroq, bu holatda, chunki List ob'ektlar ro'yxatini qabul qiladi, u nafaqat Stringni, balki butun sonni ham saqlaydi. Ammo eng yomoni shundaki, bu holda kompilyator hech qanday noto'g'ri narsani ko'rmaydi. Va bu erda biz IJRO QILISHDA xatoga duch kelamiz (ular xato "Runtime" da olinganligini ham aytishadi). Xato bo'ladi: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
rozi bo'ling, eng yoqimli emas. Va bularning barchasi, chunki kompilyator sun'iy intellekt emas va u dasturchi nazarda tutgan hamma narsani taxmin qila olmaydi. Kompilyatorga qanday turlardan foydalanishimiz haqida ko'proq ma'lumot berish uchun Java SE 5 generiklarni taqdim etdi . Keling, kompilyatorga nimani xohlayotganimizni aytib, versiyamizni to'g'rilaymiz:
import java.util.*;
public class HelloWorld {
public static void main(String []args){
List<String> list = new ArrayList<>();
list.add("Hello!");
list.add(123);
for (Object str : list) {
System.out.println(str);
}
}
}
Ko'rib turganimizdek, String uchun aktyorlar endi kerak emas. Bundan tashqari, endi bizda generiklarni ramkalash uchun burchakli qavslar mavjud. Endi biz ro'yxatga 123 qo'shimchasini olib tashlamagunimizcha, kompilyator sinfni kompilyatsiya qilishga ruxsat bermaydi, chunki bu butun son. U bizga shunday aytadi. Ko'p odamlar generiklarni "sintaktik shakar" deb atashadi. Va ular to'g'ri, chunki generiklar tuzilganda haqiqatan ham xuddi shu kastalarga aylanadi. Keling, kompilyatsiya qilingan sinflarning bayt kodini ko'rib chiqaylik: qo'lda quyish va generiklardan foydalanish:
Xom turlari yoki xom turlari
Jeneriklar haqida gapirganda, biz doimo ikkita toifaga egamiz: yoziladigan turlar (Umumiy turlar) va "xom" turlar (xom turlari). Xom turlar burchakli qavslarda "malakali" ko'rsatilmagan turlardir:<>
tushunchasi bilan ham bog'liq . Axir, kompilyator o'ngdagi <> ni ko'rib, chap tomonga qaraydi, bu erda qiymat berilgan o'zgaruvchining turi deklaratsiyasi joylashgan. Va bu qismdan u o'ngdagi qiymat qaysi turdagi terilganligini tushunadi. Aslida, agar chap tomonda umumiy belgi ko'rsatilgan bo'lsa va o'ng tomonda ko'rsatilmagan bo'lsa, kompilyator turni aniqlashi mumkin:
import java.util.*;
public class HelloWorld{
public static void main(String []args) {
List<String> list = new ArrayList();
list.add("Hello World");
String data = list.get(0);
System.out.println(data);
}
}
Biroq, bu yangi uslubning generiklar va ularsiz eski uslubning aralashmasi bo'ladi. Va bu juda istalmagan. Yuqoridagi kodni kompilyatsiya qilishda biz quyidagi xabarni olamiz: Note: HelloWorld.java uses unchecked or unsafe operations
. Aslida, nega bu erda olmos qo'shish kerakligi umuman tushunarsiz ko'rinadi. Lekin bu erda bir misol:
import java.util.*;
public class HelloWorld{
public static void main(String []args) {
List<String> list = Arrays.asList("Hello", "World");
List<Integer> data = new ArrayList(list);
Integer intNumber = data.get(0);
System.out.println(data);
}
}
Esda tutganimizdek, ArrayList-da to'plamni kiritish sifatida qabul qiluvchi ikkinchi konstruktor ham mavjud. Va bu erda yolg'on yotadi. Olmos sintaksisi bo'lmasa, kompilyator aldanayotganini tushunmaydi, ammo olmos bilan tushunadi. Shuning uchun, №1 qoida : agar biz terilgan turlardan foydalansak, har doim olmos sintaksisidan foydalaning. Aks holda, biz xom turini ishlatadigan joyni yo'qotish xavfi bor. Jurnalda "tekshirilmagan yoki xavfli operatsiyalarni ishlatadigan" ogohlantirishlarga yo'l qo'ymaslik uchun foydalanilayotgan usul yoki sinf bo'yicha maxsus izohni belgilashingiz mumkin: @SuppressWarnings("unchecked")
Suppress bostirish, ya'ni tom ma'noda ogohlantirishlarni bostirish deb tarjima qilinadi. Ammo o'ylab ko'ring, nega buni ko'rsatishga qaror qildingiz? Birinchi qoidani eslang va siz yozishni qo'shishingiz kerak bo'lishi mumkin.
Umumiy usullar
Generics sizga usullarni kiritish imkonini beradi. Oracle qo'llanmasida ushbu xususiyatga bag'ishlangan alohida bo'lim mavjud: " Umumiy usullar ". Ushbu qo'llanmadan sintaksisni eslab qolish muhim:- burchakli qavslar ichida yozilgan parametrlar ro'yxatini o'z ichiga oladi;
- terilgan parametrlar ro'yxati qaytarilgan usuldan oldin o'tadi.
import java.util.*;
public class HelloWorld{
public static class Util {
public static <T> T getValue(Object obj, Class<T> clazz) {
return (T) obj;
}
public static <T> T getValue(Object obj) {
return (T) obj;
}
}
public static void main(String []args) {
List list = Arrays.asList("Author", "Book");
for (Object element : list) {
String data = Util.getValue(element, String.class);
System.out.println(data);
System.out.println(Util.<String>getValue(element));
}
}
}
Agar siz Util sinfiga qarasangiz, unda ikkita terilgan usulni ko'ramiz. Turni aniqlash orqali biz to'g'ridan-to'g'ri kompilyatorga tur ta'rifini berishimiz yoki uni o'zimiz belgilashimiz mumkin. Ikkala variant ham misolda keltirilgan. Aytgancha, agar siz bu haqda o'ylab ko'rsangiz, sintaksis juda mantiqiy. Usulni yozayotganda, biz generic FORE usulini belgilaymiz, chunki agar biz usuldan keyin genericdan foydalansak, Java qaysi turdan foydalanishni aniqlay olmaydi. Shuning uchun, biz birinchi navbatda umumiy T dan foydalanishimizni e'lon qilamiz, keyin esa bu generikni qaytarmoqchimiz. Tabiiyki, Util.<Integer>getValue(element, String.class)
u xato bilan muvaffaqiyatsiz bo'ladi incompatible types: Class<String> cannot be converted to Class<Integer>
. Yozilgan usullardan foydalanganda siz har doim turni o'chirish haqida eslab qolishingiz kerak. Keling, bir misolni ko'rib chiqaylik:
import java.util.*;
public class HelloWorld {
public static class Util {
public static <T> T getValue(Object obj) {
return (T) obj;
}
}
public static void main(String []args) {
List list = Arrays.asList(2, 3);
for (Object element : list) {
System.out.println(Util.<Integer>getValue(element) + 1);
}
}
}
Bu ajoyib ishlaydi. Ammo kompilyator chaqirilgan usul Integer turiga ega ekanligini tushunsagina. Konsol chiqishini quyidagi qator bilan almashtiramiz: System.out.println(Util.getValue(element) + 1);
Va biz xatoga duch kelamiz: ikkilik operator '+' uchun noto'g'ri operand turlari, birinchi turdagi: Ob'ekt , ikkinchi turdagi: int Ya'ni, turlar o'chirildi. Kompilyator hech kim turni ko'rsatmaganligini ko'radi, turi Ob'ekt sifatida ko'rsatilgan va kodning bajarilishi xato bilan bajarilmaydi.
Umumiy turlari
Siz nafaqat usullarni, balki sinflarni ham yozishingiz mumkin. Oracle o'z qo'llanmasida bunga bag'ishlangan " Umumiy turlar " bo'limiga ega. Keling, bir misolni ko'rib chiqaylik:public static class SomeType<T> {
public <E> void test(Collection<E> collection) {
for (E element : collection) {
System.out.println(element);
}
}
public void test(List<Integer> collection) {
for (Integer element : collection) {
System.out.println(element);
}
}
}
Bu erda hamma narsa oddiy. Agar biz sinfdan foydalansak, generic sinf nomidan keyin ro'yxatga olinadi. Keling, asosiy usulda ushbu sinfning namunasini yaratamiz:
public static void main(String []args) {
SomeType<String> st = new SomeType<>();
List<String> list = Arrays.asList("test");
st.test(list);
}
Bu yaxshi ishlaydi. Kompilyator raqamlar ro'yxati va String tipidagi To'plam mavjudligini ko'radi. Ammo agar biz generiklarni o'chirib tashlasak va buni qilsak nima bo'ladi:
SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
Biz xatoga duch kelamiz: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
qayta o'chirishni yozing. Sinfda endi umumiy bo'lmaganligi sababli, kompilyator biz Ro'yxatni o'tkazganimizdan beri List<Integer> usuli ko'proq mos keladi, deb qaror qiladi. Va biz xato bilan yiqilib tushamiz. Shuning uchun 2-qoida: Agar sinf yozilsa, har doim genericda turni belgilang .
Cheklovlar
Biz generiklarda ko'rsatilgan turlarga cheklov qo'yishimiz mumkin. Masalan, biz konteyner faqat Raqamni kiritish sifatida qabul qilishini xohlaymiz. Ushbu xususiyat Oracle Qo'llanmasida Chegaralangan turdagi parametrlar bo'limida tasvirlangan . Keling, bir misolni ko'rib chiqaylik:import java.util.*;
public class HelloWorld{
public static class NumberContainer<T extends Number> {
private T number;
public NumberContainer(T number) { this.number = number; }
public void print() {
System.out.println(number);
}
}
public static void main(String []args) {
NumberContainer number1 = new NumberContainer(2L);
NumberContainer number2 = new NumberContainer(1);
NumberContainer number3 = new NumberContainer("f");
}
}
Ko'rib turganingizdek, biz umumiy turni Raqamlar sinfi/interfeysi va uning avlodlari bo'lish uchun cheklab qo'ydik. Qizig'i shundaki, siz nafaqat sinfni, balki interfeyslarni ham belgilashingiz mumkin. Misol uchun: public static class NumberContainer<T extends Number & Comparable> {
Generics shuningdek, Wildcard tushunchasiga ega https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html Ular, o'z navbatida, uch turga bo'linadi:
- Yuqori chegaralangan joker belgilar - < ? Raqamni kengaytiradi >
- Cheklanmagan joker belgilar - < ? >
- Pastki chegaralangan joker belgilar - < ? super tamsayı >
public static class TestClass {
public static void print(List<? extends String> list) {
list.add("Hello World!");
System.out.println(list.get(0));
}
}
public static void main(String []args) {
List<String> list = new ArrayList<>();
TestClass.print(list);
}
Ammo agar siz uzatmalarni super bilan almashtirsangiz, hammasi yaxshi bo'ladi. Ro'yxatni chiqarishdan oldin uni qiymat bilan to'ldirganimiz uchun u biz uchun iste'molchi, ya'ni iste'molchidir. Shuning uchun biz superdan foydalanamiz.
Meros olish
Jeneriklarning yana bir g'ayrioddiy xususiyati bor - ularning merosi. Jeneriklarni meros qilib olish Oracle qo'llanmasida " Generiklar, meros va kichik tiplar " bo'limida tasvirlangan . Asosiysi, quyidagilarni yodda tutish va amalga oshirishdir. Biz buni qila olmaymiz:List<CharSequence> list1 = new ArrayList<String>();
Chunki meros generiklar bilan boshqacha ishlaydi:
List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
Bu erda ham hamma narsa oddiy. List<String> List<Object> ning avlodi emas, garchi String Ob'ektning avlodi bo'lsa.
Final
Shunday qilib, biz generiklar haqidagi xotiramizni yangiladik. Agar ular barcha kuchlarida kamdan-kam ishlatilsa, ba'zi tafsilotlar xotiradan chiqib ketadi. Umid qilamanki, bu qisqa sharh xotirangizni yangilashga yordam beradi. Va kattaroq natijalarga erishish uchun men sizga quyidagi materiallarni o'qishni tavsiya qilaman:- Yuriy Tkach: Xom turlari - Generics №1 - Kengaytirilgan Java
- Meros va umumiy kengaytiruvchilar - Generics №2 - Kengaytirilgan Java
- Rekursiv tip kengaytmasi - Generics №3 - Kengaytirilgan Java
- Aleksandr Matorin - Aniq bo'lmagan generiklar
- Java tiliga kirish. Umumiy. Joker belgilar | Technostream
- O'Reilly: Java umumiy va to'plamlari
GO TO FULL VERSION