14-bo‘lim
Generiklar
Generik sinf va metodlar, cheklovlar, turni o'chirish (type erasure) va uning oqibatlari, joker belgilar hamda PECS qoidasi.
Ushbu bo‘lim mundarijasi
Generik - tur parametri bilan yoziladigan kod. List<String> va
List<Integer> - bitta sinf, ikki xil tur bilan.
Nega kerak #
Generiklardan oldin (Java 5 gacha) hamma narsa Object orqali
qilinardi:
import java.util.ArrayList;
import java.util.List;
public class Nega {
public static void main(String[] args) {
List xom = new ArrayList(); // generiksiz
xom.add("matn");
xom.add(42); // hech kim to'xtatmaydi
try {
String s = (String) xom.get(1);
System.out.println(s);
} catch (ClassCastException x) {
System.out.println("Ishga tushganda quladi: "
+ x.getClass().getSimpleName());
}
List<String> aniq = new ArrayList<>();
aniq.add("matn");
// aniq.add(42); <- kompilyatsiya xatosi
System.out.println("Generik bilan: " + aniq.get(0).toUpperCase());
}
}
Ishga tushganda quladi: ClassCastException
Generik bilan: MATN
Generiklar xatoni ishga tushishdan oldin topadi va aylantirish yozishni ham kerak qoldirmaydi.
Generik metod #
import java.util.List;
public class GenerikMetod {
public static void main(String[] args) {
System.out.println(birinchi(List.of("bir", "ikki")));
System.out.println(birinchi(List.of(10, 20, 30)));
System.out.println(birinchi(List.<String>of()));
}
static <T> String birinchi(List<T> royxat) {
if (royxat.isEmpty()) {
return "ro'yxat bo'sh";
}
return "birinchi: " + royxat.get(0);
}
}
birinchi: bir
birinchi: 10
ro'yxat bo'sh
Tur parametri <T> qaytish turidan oldin yoziladi. Chaqirishda
uni ko'rsatish odatda shart emas - kompilyator argumentdan aniqlaydi.
Generik sinf #
public class GenerikSinf {
public static void main(String[] args) {
Quti<Integer> sonlar = new Quti<>();
sonlar.qoy(42);
Quti<String> matnlar = new Quti<>();
matnlar.qoy("Namangan");
System.out.println(sonlar.ol());
System.out.println(matnlar.ol());
System.out.println("Bo'shmi: " + new Quti<Double>().bosh());
}
}
class Quti<T> {
private T ichidagi;
void qoy(T narsa) {
this.ichidagi = narsa;
}
T ol() {
return ichidagi;
}
boolean bosh() {
return ichidagi == null;
}
}
42
Namangan
Bo'shmi: true
Cheklovlar #
Cheklovsiz T ustida deyarli hech narsa qilib bo'lmaydi - faqat
Object metodlari mavjud:
import java.util.List;
public class Cheklov {
public static void main(String[] args) {
System.out.println(kattasi(List.of(5, 9, 3)));
System.out.println(kattasi(List.of("olma", "behi", "anor")));
System.out.println(yigindi(List.of(1, 2.5, 3L)));
}
static <T extends Comparable<T>> T kattasi(List<T> royxat) {
T natija = royxat.get(0);
for (T element : royxat) {
if (element.compareTo(natija) > 0) {
natija = element;
}
}
return natija;
}
static double yigindi(List<? extends Number> sonlar) {
double jami = 0;
for (Number son : sonlar) {
jami += son.doubleValue();
}
return jami;
}
}
9
olma
6.5
<T extends Comparable<T>> - "T solishtirilishi mumkin bo'lsin"
degani. Shu tufayli compareTo ni chaqira oldik.
extendsC# da where T : class, where T : new() kabi turli cheklovlar bor.
Java'da esa faqat bitta kalit so'z - extends, va u interfeys
uchun ham ishlatiladi:
<T extends Comparable<T>> // interfeys
<T extends Number> // sinf
<T extends Number & Comparable<T>> // ikkalasi
Java'da new T() yozib bo'lmaydi - keyingi bo'limda nega ekanini
ko'ramiz.
Turni o'chirish #
Bu Java generiklarining eng muhim va eng g'alati xususiyati:
import java.util.ArrayList;
import java.util.List;
public class Ochirish {
public static void main(String[] args) {
List<String> matnlar = new ArrayList<>();
List<Integer> sonlar = new ArrayList<>();
System.out.println("Sinflari: " + matnlar.getClass().getSimpleName()
+ " va " + sonlar.getClass().getSimpleName());
System.out.println("Bir xil sinfmi: "
+ (matnlar.getClass() == sonlar.getClass()));
}
}
Sinflari: ArrayList va ArrayList
Bir xil sinfmi: true
Java generiklari faqat kompilyatsiya paytida mavjud. Bayt-kodga o'tganda tur parametri o'chiriladi - bunga type erasure deyiladi.
Ya'ni List<String> va List<Integer> ishga tushganda aynan
bitta sinf: oddiy ArrayList.
Bu quyidagilarni imkonsiz qiladi:
| Yozuv | Nega ishlamaydi |
|---|---|
new T() | T ishga tushganda mavjud emas |
new T[10] | Xuddi shu sabab |
x instanceof List<String> | Tur tekshirib bo'lmaydi |
catch (MeningIstisnom<T> x) | Istisnolar generik bo'la olmaydi |
Nega shunday qilingan? Eski kod bilan moslik uchun. Java 5 da generiklar qo'shilganda, mavjud millionlab satr kod ishlashda davom etishi kerak edi.
C# boshqa yo'l tanlagan: u generiklarni ish vaqtida ham saqlaydi.
Shuning uchun C# da new T() mumkin, Java'da esa yo'q.
Oqibatini amalda ko'ramiz:
import java.util.ArrayList;
import java.util.List;
public class Oqibat {
public static void main(String[] args) {
List<String> matnlar = new ArrayList<>(List.of("bir", "ikki"));
System.out.println("List ekanini bilamiz: "
+ (matnlar instanceof List));
Object obyekt = matnlar;
if (obyekt instanceof List<?> royxat) {
System.out.println("Elementlar soni: " + royxat.size());
System.out.println("Lekin turini bilmaymiz - "
+ "har bir elementni tekshirish kerak");
for (Object element : royxat) {
System.out.println(" " + element.getClass().getSimpleName()
+ ": " + element);
}
}
}
}
List ekanini bilamiz: true
Elementlar soni: 2
Lekin turini bilmaymiz - har bir elementni tekshirish kerak
String: bir
String: ikki
List<?> - "noma'lum turdagi ro'yxat" degani. List<String> deb
tekshirib bo'lmaydi, chunki bu ma'lumot ishga tushganda mavjud emas.
Joker belgilar #
import java.util.ArrayList;
import java.util.List;
public class Joker {
public static void main(String[] args) {
List<Integer> butunlar = List.of(1, 2, 3);
List<Double> kasrlar = List.of(1.5, 2.5);
System.out.println("Butunlar: " + yigindi(butunlar));
System.out.println("Kasrlar: " + yigindi(kasrlar));
List<Number> sonlar = new ArrayList<>();
toldir(sonlar);
System.out.println("To'ldirildi: " + sonlar);
List<Object> obyektlar = new ArrayList<>();
toldir(obyektlar);
System.out.println("Obyektlar: " + obyektlar);
}
// o'qish uchun - extends
static double yigindi(List<? extends Number> sonlar) {
double jami = 0;
for (Number son : sonlar) {
jami += son.doubleValue();
}
return jami;
}
// yozish uchun - super
static void toldir(List<? super Integer> royxat) {
royxat.add(1);
royxat.add(2);
}
}
Butunlar: 6.0
Kasrlar: 4.0
To'ldirildi: [1, 2]
Obyektlar: [1, 2]
Bu qoidani yodlash osonroq: Producer Extends, Consumer Super.
| Maqsad | Yozuv | Nima mumkin |
|---|---|---|
| Ro'yxatdan o'qiysiz | ? extends T | O'qish mumkin, yozish yo'q |
| Ro'yxatga yozasiz | ? super T | Yozish mumkin, o'qish cheklangan |
| Ikkalasi ham | T (jokersiz) | Hammasi mumkin |
Nega ? extends Number ga yozib bo'lmaydi? Chunki u List<Integer>
ham, List<Double> ham bo'lishi mumkin. Kompilyator qaysi ekanini
bilmaydi, shuning uchun hech narsa qo'shishga ruxsat bermaydi - aks
holda List<Integer> ga Double tushib qolishi mumkin edi.
Bu 7-bo'limdagi massiv kovariantligi muammosining to'g'ri yechimi: massivlarda xato ishga tushganda chiqadi, generiklarda esa kompilyator uni oldindan to'xtatadi.
Generik va massiv #
import java.util.ArrayList;
import java.util.List;
public class GenerikMassiv {
public static void main(String[] args) {
List<String>[] notogri; // e'lon qilish mumkin
// notogri = new List<String>[10]; // yaratish - XATO
List<List<String>> togri = new ArrayList<>();
togri.add(List.of("bir", "ikki"));
togri.add(List.of("uch"));
System.out.println("Ichma-ich ro'yxat: " + togri);
System.out.println("Birinchi guruh: " + togri.get(0));
}
}
Ichma-ich ro'yxat: [[bir, ikki], [uch]]
Birinchi guruh: [bir, ikki]
new List<String>[10] kompilyatsiya qilinmaydi:
error: generic array creation
Sabab: massivlar ishga tushganda o'z turini biladi (7-bo'lim), generik turlar esa bilmaydi. Ikkalasi bir-biriga zid.
Yechim: massiv o'rniga List<List<String>> ishlating.
- Generiksiz
Listga turli tur qo'shib,ClassCastExceptionni ko'ring. - Generik metod yozib, uni uch xil tur bilan chaqiring.
- Generik
Quti<T>sinfini yarating. <T extends Comparable<T>>bilan eng kattasini topuvchi metod yozing.List<String>vaList<Integer>sinflarini solishtiring.new T()yozishga urinib, kompilyator nima deyishini ko'ring.? extends Numberga element qo'shishga urinib ko'ring.? super Integerga element qo'shing.- PECS qoidasini o'z so'zingiz bilan yozing.
new List<String>[10]yozib, xatoni ko'ring.
Xulosa #
- Generiklar xatolarni kompilyatsiya paytida topadi va aylantirishni keraksiz qiladi.
- Tur parametri metodda qaytish turidan oldin yoziladi:
<T>. - Java'da faqat
extendscheklovi bor, interfeys uchun ham. - Turni o'chirish: generik ma'lumot bayt-kodda saqlanmaydi, ishga
tushganda
List<String>oddiyArrayListbo'ladi. - Shu sababli
new T(),new T[]vainstanceof List<String>mumkin emas. - PECS: o'qish uchun
? extends, yozish uchun? super. - Generiklar kovariant emas - bu massivlardagi
ArrayStoreExceptionmuammosining to'g'ri yechimi. - Generik massiv yaratib bo'lmaydi -
List<List<T>>ishlating.
Keyingi bo'limda lambda va funksional interfeyslarni o'rganamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.