11-bo‘lim

record, enum va yopiq sinflar

record turlari va ixcham konstruktorlar, equals va hashCode shartnomasi, enum imkoniyatlari, sealed interfeyslar va namuna moslashtirish bilan birga ishlatish.

🕑 12 daqiqa o‘qish 📄 839 so‘z 👁 2 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo: ma'lumot saqlash uchun juda ko'p kod
  2. record - bir qatorda
  3. equals va hashCode shartnomasi
  4. Ixcham konstruktor
  5. record ga metod qo'shish
  6. enum
  7. Yopiq sinflar
  8. Uchtasini qachon ishlatish
  9. Xulosa

Java'ga so'nggi yillarda qo'shilgan uchta vosita kodni sezilarli qisqartiradi: record, kuchaytirilgan enum va yopiq sinflar.

Muammo: ma'lumot saqlash uchun juda ko'p kod #

Oddiy "ism va yosh" turini yozish uchun Java'da nima kerak edi?

Java
public class Eski {
    public static void main(String[] args) {
        Talaba a = new Talaba("Husanboy", 25);
        Talaba b = new Talaba("Husanboy", 25);

        System.out.println(a);
        System.out.println("Teng-mi: " + a.equals(b));
    }
}

class Talaba {
    private final String ism;
    private final int yosh;

    Talaba(String ism, int yosh) {
        this.ism = ism;
        this.yosh = yosh;
    }

    String ism() {
        return ism;
    }

    int yosh() {
        return yosh;
    }

    @Override
    public String toString() {
        return "Talaba[ism=" + ism + ", yosh=" + yosh + "]";
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Talaba t)) {
            return false;
        }
        return yosh == t.yosh && ism.equals(t.ism);
    }

    @Override
    public int hashCode() {
        return java.util.Objects.hash(ism, yosh);
    }
}
Natija
Talaba[ism=Husanboy, yosh=25]
Teng-mi: true

Qirq qatordan ortiq kod - ikkita maydon uchun.

record - bir qatorda #

Java
public class Yangi {
    public static void main(String[] args) {
        Talaba a = new Talaba("Husanboy", 25);
        Talaba b = new Talaba("Husanboy", 25);

        System.out.println(a);
        System.out.println("Teng-mi: " + a.equals(b));
        System.out.println("Xesh bir xilmi: "
                + (a.hashCode() == b.hashCode()));
        System.out.println("Ism: " + a.ism() + ", yosh: " + a.yosh());
    }
}

record Talaba(String ism, int yosh) {
}
Natija
Talaba[ism=Husanboy, yosh=25]
Teng-mi: true
Xesh bir xilmi: true
Ism: Husanboy, yosh: 25

Bitta qator uchun biz quyidagilarni bepul oldik:

  • konstruktor;
  • ism() va yosh() metodlari;
  • mazmunli toString();
  • qiymat bo'yicha equals();
  • mos hashCode().
record da maydonlar o'zgarmas

record ning barcha maydonlari avtomatik private final bo'ladi. setIsm(...) yozib bo'lmaydi - obyekt yaratilgandan keyin o'zgarmaydi.

Bu cheklov emas, afzallik: o'zgarmas obyektlarni ko'p oqimda xavfsiz ishlatish mumkin va ular kutilmagan joyda o'zgarib qolmaydi.

O'zgaruvchan holat kerak bo'lsa - oddiy class ishlating.

equals va hashCode shartnomasi #

Nega bu ikkalasi birga yuradi? Amalda ko'ramiz:

Java
import java.util.HashSet;
import java.util.Set;

public class Shartnoma {
    public static void main(String[] args) {
        Set<NotoGri> a = new HashSet<>();
        a.add(new NotoGri("Husanboy"));
        a.add(new NotoGri("Husanboy"));
        System.out.println("equals siz: " + a.size() + " ta element");

        Set<Togri> b = new HashSet<>();
        b.add(new Togri("Husanboy"));
        b.add(new Togri("Husanboy"));
        System.out.println("record bilan: " + b.size() + " ta element");
    }
}

class NotoGri {
    private final String ism;

    NotoGri(String ism) {
        this.ism = ism;
    }
}

record Togri(String ism) {
}
Natija
equals siz: 2 ta element
record bilan: 1 ta element
equals yozsangiz, hashCode ham shart

HashSet va HashMap avval hashCode() ni hisoblaydi, keyin mos "savat" ichida equals() bilan solishtiradi.

Shundan uchta qoida kelib chiqadi:

  1. Ikki obyekt teng bo'lsa, ularning hashCode() i ham bir xil bo'lishi shart;
  2. hashCode() bir xil bo'lsa, obyektlar teng bo'lishi shart emas;
  3. Faqat equals() yozib, hashCode() ni unutsangiz - obyekt HashSet ga ikki marta tushadi va HashMap dan topilmaydi.

Uchinchisi eng xavfli: kod kompilyatsiya qilinadi, testlar ham o'tishi mumkin, lekin kolleksiya jimgina noto'g'ri ishlaydi.

record bu muammoni butunlay yo'q qiladi - u ikkalasini ham o'zi yozadi. Ma'lumot saqlaydigan turlar uchun har doim record ni tanlang.

Ixcham konstruktor #

record ga tekshiruv qo'shish mumkin:

Java
public class Tekshiruv {
    public static void main(String[] args) {
        System.out.println(new Ball("Husanboy", 92));

        try {
            new Ball("Malika", 150);
        } catch (IllegalArgumentException x) {
            System.out.println("Xato: " + x.getMessage());
        }

        try {
            new Ball("  ", 80);
        } catch (IllegalArgumentException x) {
            System.out.println("Xato: " + x.getMessage());
        }
    }
}

record Ball(String ism, int qiymat) {
    Ball {
        if (ism == null || ism.isBlank()) {
            throw new IllegalArgumentException("Ism bo'sh bo'lmasin");
        }
        if (qiymat < 0 || qiymat > 100) {
            throw new IllegalArgumentException(
                    "Ball 0..100 oralig'ida: " + qiymat);
        }
        ism = ism.strip();
    }
}
Natija
Ball[ism=Husanboy, qiymat=92]
Xato: Ball 0..100 oralig'ida: 150
Xato: Ism bo'sh bo'lmasin

Diqqat: Ball { - qavssiz. Bu ixcham konstruktor. Unda parametrlarni o'zgartirish mumkin (ism = ism.strip()), maydonlarni esa Java o'zi o'rnatadi.

record ga metod qo'shish #

Java
public class Metodli {
    public static void main(String[] args) {
        Nuqta n = new Nuqta(3, 4);

        System.out.println(n);
        System.out.printf("Masofa: %.1f%n", n.masofa());
        System.out.println("Siljitilgan: " + n.siljit(1, 1));
        System.out.println("Boshi: " + Nuqta.BOSH);
    }
}

record Nuqta(double x, double y) {
    static final Nuqta BOSH = new Nuqta(0, 0);

    double masofa() {
        return Math.sqrt(x * x + y * y);
    }

    Nuqta siljit(double dx, double dy) {
        return new Nuqta(x + dx, y + dy);
    }
}
Natija
Nuqta[x=3.0, y=4.0]
Masofa: 5.0
Siljitilgan: Nuqta[x=4.0, y=5.0]
Boshi: Nuqta[x=0.0, y=0.0]

record ga metodlar, static maydonlar va interfeyslarni amalga oshirish qo'shish mumkin. Faqat oddiy (nostatik) maydon qo'shib bo'lmaydi.

enum #

Java'da enum oddiy doimiylar ro'yxatidan ancha kuchliroq:

Java
public class Enum1 {
    public static void main(String[] args) {
        for (Daraja d : Daraja.values()) {
            System.out.printf("%-11s %d ball, %s%n",
                    d, d.engKam(), d.otdimi() ? "o'tdi" : "o'tmadi");
        }

        System.out.println();
        System.out.println("87 ball -> " + Daraja.aniqla(87));
        System.out.println("45 ball -> " + Daraja.aniqla(45));
        System.out.println("Nomdan: " + Daraja.valueOf("ALO"));
        System.out.println("Tartib raqami: " + Daraja.YAXSHI.ordinal());
    }
}

enum Daraja {
    QONIQARSIZ(0),
    QONIQARLI(60),
    YAXSHI(75),
    ALO(90);

    private final int engKam;

    Daraja(int engKam) {
        this.engKam = engKam;
    }

    int engKam() {
        return engKam;
    }

    boolean otdimi() {
        return this != QONIQARSIZ;
    }

    static Daraja aniqla(int ball) {
        Daraja natija = QONIQARSIZ;
        for (Daraja d : values()) {
            if (ball >= d.engKam) {
                natija = d;
            }
        }
        return natija;
    }
}
Natija
QONIQARSIZ  0 ball, o'tmadi
QONIQARLI   60 ball, o'tdi
YAXSHI      75 ball, o'tdi
ALO         90 ball, o'tdi

87 ball -> YAXSHI
45 ball -> QONIQARSIZ
Nomdan: ALO
Tartib raqami: 2
ImkoniyatNima qiladi
Maydon va konstruktorHar bir qiymatga ma'lumot biriktiradi
MetodlarXatti-harakat qo'shadi
values()Barcha qiymatlar massivi
valueOf("NOM")Nomdan qiymat oladi
ordinal()Tartib raqami (0 dan)
name()Nomi satr ko'rinishida
ordinal() ga tayanmang

ordinal() - bu qiymatning e'londagi tartib raqami. U vasvasaga soladi: uni bazaga saqlash yoki fayl formatida ishlatish oson tuyuladi.

Muammo: kimdir enum ga o'rtaga yangi qiymat qo'shsa, barcha keyingi raqamlar siljiydi va eski ma'lumot noto'g'ri o'qiladi.

Saqlash kerak bo'lsa name() ni yoki o'zingiz belgilagan maydonni ishlating - ular barqaror.

enum va switch birga

switch ifodasi enum bilan ishlaganda to'liqlikni tekshiradi:

Java
public class EnumSwitch {
    public static void main(String[] args) {
        for (Holat h : Holat.values()) {
            String izoh = switch (h) {
                case YANGI -> "hali boshlanmagan";
                case JARAYONDA -> "ishlanmoqda";
                case YAKUNLANGAN -> "tugatilgan";
            };
            System.out.println(h + ": " + izoh);
        }
    }
}

enum Holat {
    YANGI, JARAYONDA, YAKUNLANGAN
}
Natija
YANGI: hali boshlanmagan
JARAYONDA: ishlanmoqda
YAKUNLANGAN: tugatilgan

Diqqat: default yozilmagan. Barcha holat qamralgani uchun bu mumkin.

Endi enum ga yangi qiymat qo'shsangiz, kompilyator shu switch ni xato deb belgilaydi va siz uni unutib qo'ymaysiz. default yozsangiz, bu himoya yo'qoladi.

Yopiq sinflar #

sealed - "faqat men ruxsat berganlar meros olsin" degani:

Java
public class Yopiq {
    public static void main(String[] args) {
        Tolov[] tolovlar = {
                new Naqd(50000),
                new Karta("8600", 120000),
                new Otkazma("Malika", 75000)
        };

        for (Tolov t : tolovlar) {
            System.out.println(tavsif(t));
        }
    }

    static String tavsif(Tolov t) {
        return switch (t) {
            case Naqd n -> "Naqd: " + n.miqdor() + " so'm";
            case Karta k -> "Karta " + k.oxirgiRaqamlar() + ": " + k.miqdor();
            case Otkazma o -> "O'tkazma (" + o.kimga() + "): " + o.miqdor();
        };
    }
}

sealed interface Tolov permits Naqd, Karta, Otkazma {
    long miqdor();
}

record Naqd(long miqdor) implements Tolov {
}

record Karta(String oxirgiRaqamlar, long miqdor) implements Tolov {
}

record Otkazma(String kimga, long miqdor) implements Tolov {
}
Natija
Naqd: 50000 so'm
Karta 8600: 120000
O'tkazma (Malika): 75000
sealed ning asosiy foydasi

Diqqat qiling: tavsif metodidagi switch da default yo'q.

Kompilyator Tolov ning barcha mumkin bo'lgan turlarini biladi - chunki permits ro'yxati yopiq. Uchtasi ham qamralgani uchun default kerak emas.

Endi to'rtinchi to'lov turi qo'shsangiz, kompilyator shu switch ni darhol xato deb belgilaydi.

sealed siz esa default yozishga majbur bo'lardingiz va yangi tur jimgina "boshqa" tarmog'iga tushib ketardi.

Bu enum ning kuchini bir nechta maydonli turlarga kengaytiradi.

Kalit so'zMa'nosi
sealedFaqat ruxsat etilganlar meros oladi
permitsRuxsat etilganlar ro'yxati
finalVorislik butunlay taqiqlanadi
non-sealedCheklov shu vorisda ochiladi
Vorislar bir joyda bo'lishi shart

sealed interfeysning barcha vorislari bir xil paketda (yoki bir modulda) bo'lishi kerak.

Bu ataylab shunday: kompilyator ro'yxatning to'liqligiga ishonch hosil qila olishi kerak. Aks holda boshqa kutubxona yangi voris qo'shib, switch ni buzardi.

Uchtasini qachon ishlatish #

VositaQachon
recordMa'lumot tashiydi, o'zgarmas, mazmuni bo'yicha solishtiriladi
enumCheklangan, oldindan ma'lum qiymatlar to'plami
sealedCheklangan turlar to'plami, lekin har birida turli maydonlar
Oddiy classO'zgaruvchan holat yoki murakkab xatti-harakat
Amaliy topshiriq
  1. Ikki maydonli oddiy sinf yozib, unga equals va hashCode qo'shing.
  2. Xuddi shuni record bilan bir qatorda yozing.
  3. equals siz sinfni HashSet ga ikki marta qo'shib ko'ring.
  4. record ga ixcham konstruktor bilan tekshiruv qo'shing.
  5. record ga metod va static doimiy qo'shing.
  6. Maydonli va metodli enum yozing.
  7. values(), valueOf() va ordinal() ni sinang.
  8. enum bo'yicha default siz switch yozing.
  9. enum ga yangi qiymat qo'shib, kompilyator nima deyishini ko'ring.
  10. sealed interfeys va uchta record voris yozing.

Xulosa #

  • record konstruktor, toString, equals va hashCode ni o'zi yozadi.
  • record maydonlari o'zgarmas - bu afzallik.
  • equals yozsangiz, hashCode ham shart - aks holda HashSet va HashMap jimgina noto'g'ri ishlaydi.
  • Ixcham konstruktor (Ball {) tekshiruv qo'shish uchun.
  • Java'da enum maydon, konstruktor va metodga ega bo'la oladi.
  • ordinal() ni saqlamang - name() yoki o'z maydoningizni ishlating.
  • enum va sealed bilan switch to'liqlikni tekshiradi - default yozmang.
  • sealed vorislari bir paketda bo'lishi shart.

Keyingi bo'limda kolleksiyalarni 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.