14-bo‘lim

Generiklar

Generik sinf va metodlar, cheklovlar, turni o'chirish (type erasure) va uning oqibatlari, joker belgilar hamda PECS qoidasi.

🕑 12 daqiqa o‘qish 📄 645 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Nega kerak
  2. Generik metod
  3. Generik sinf
  4. Cheklovlar
  5. Turni o'chirish
  6. Joker belgilar
  7. Generik va massiv
  8. Xulosa

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:

Java
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());
    }
}
Natija
Ishga tushganda quladi: ClassCastException
Generik bilan: MATN

Generiklar xatoni ishga tushishdan oldin topadi va aylantirish yozishni ham kerak qoldirmaydi.

Generik metod #

Java
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);
    }
}
Natija
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 #

Java
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;
    }
}
Natija
42
Namangan
Bo'shmi: true

Cheklovlar #

Cheklovsiz T ustida deyarli hech narsa qilib bo'lmaydi - faqat Object metodlari mavjud:

Java
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;
    }
}
Natija
9
olma
6.5

<T extends Comparable<T>> - "T solishtirilishi mumkin bo'lsin" degani. Shu tufayli compareTo ni chaqira oldik.

Java'da faqat extends

C# 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:

Java
<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:

Java
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()));
    }
}
Natija
Sinflari: ArrayList va ArrayList
Bir xil sinfmi: true
Tur ma'lumoti ishga tushganda yo'qoladi

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:

YozuvNega 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:

Java
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);
            }
        }
    }
}
Natija
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 #

Java
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);
    }
}
Natija
Butunlar: 6.0
Kasrlar:  4.0
To'ldirildi: [1, 2]
Obyektlar:   [1, 2]
PECS qoidasi

Bu qoidani yodlash osonroq: Producer Extends, Consumer Super.

MaqsadYozuvNima mumkin
Ro'yxatdan o'qiysiz? extends TO'qish mumkin, yozish yo'q
Ro'yxatga yozasiz? super TYozish mumkin, o'qish cheklangan
Ikkalasi hamT (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 #

Java
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));
    }
}
Natija
Ichma-ich ro'yxat: [[bir, ikki], [uch]]
Birinchi guruh: [bir, ikki]
Generik massiv yaratib bo'lmaydi

new List<String>[10] kompilyatsiya qilinmaydi:

Natija
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.

Amaliy topshiriq
  1. Generiksiz List ga turli tur qo'shib, ClassCastException ni ko'ring.
  2. Generik metod yozib, uni uch xil tur bilan chaqiring.
  3. Generik Quti<T> sinfini yarating.
  4. <T extends Comparable<T>> bilan eng kattasini topuvchi metod yozing.
  5. List<String> va List<Integer> sinflarini solishtiring.
  6. new T() yozishga urinib, kompilyator nima deyishini ko'ring.
  7. ? extends Number ga element qo'shishga urinib ko'ring.
  8. ? super Integer ga element qo'shing.
  9. PECS qoidasini o'z so'zingiz bilan yozing.
  10. 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 extends cheklovi bor, interfeys uchun ham.
  • Turni o'chirish: generik ma'lumot bayt-kodda saqlanmaydi, ishga tushganda List<String> oddiy ArrayList bo'ladi.
  • Shu sababli new T(), new T[] va instanceof List<String> mumkin emas.
  • PECS: o'qish uchun ? extends, yozish uchun ? super.
  • Generiklar kovariant emas - bu massivlardagi ArrayStoreException muammosining to'g'ri yechimi.
  • Generik massiv yaratib bo'lmaydi - List<List<T>> ishlating.

Keyingi bo'limda lambda va funksional interfeyslarni o'rganamiz.

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.