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.
Ushbu bo‘lim mundarijasi
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?
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);
}
}
Talaba[ism=Husanboy, yosh=25]
Teng-mi: true
Qirq qatordan ortiq kod - ikkita maydon uchun.
record - bir qatorda #
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) {
}
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()vayosh()metodlari;- mazmunli
toString(); - qiymat bo'yicha
equals(); - mos
hashCode().
record da maydonlar o'zgarmasrecord 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:
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) {
}
equals siz: 2 ta element
record bilan: 1 ta element
HashSet va HashMap avval hashCode() ni hisoblaydi, keyin
mos "savat" ichida equals() bilan solishtiradi.
Shundan uchta qoida kelib chiqadi:
- Ikki obyekt teng bo'lsa, ularning
hashCode()i ham bir xil bo'lishi shart; hashCode()bir xil bo'lsa, obyektlar teng bo'lishi shart emas;- Faqat
equals()yozib,hashCode()ni unutsangiz - obyektHashSetga ikki marta tushadi vaHashMapdan 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:
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();
}
}
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 #
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);
}
}
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:
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;
}
}
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
| Imkoniyat | Nima qiladi |
|---|---|
| Maydon va konstruktor | Har bir qiymatga ma'lumot biriktiradi |
| Metodlar | Xatti-harakat qo'shadi |
values() | Barcha qiymatlar massivi |
valueOf("NOM") | Nomdan qiymat oladi |
ordinal() | Tartib raqami (0 dan) |
name() | Nomi satr ko'rinishida |
ordinal() ga tayanmangordinal() - 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 birgaswitch ifodasi enum bilan ishlaganda to'liqlikni tekshiradi:
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
}
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:
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 {
}
Naqd: 50000 so'm
Karta 8600: 120000
O'tkazma (Malika): 75000
sealed ning asosiy foydasiDiqqat 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'z | Ma'nosi |
|---|---|
sealed | Faqat ruxsat etilganlar meros oladi |
permits | Ruxsat etilganlar ro'yxati |
final | Vorislik butunlay taqiqlanadi |
non-sealed | Cheklov shu vorisda ochiladi |
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 #
| Vosita | Qachon |
|---|---|
record | Ma'lumot tashiydi, o'zgarmas, mazmuni bo'yicha solishtiriladi |
enum | Cheklangan, oldindan ma'lum qiymatlar to'plami |
sealed | Cheklangan turlar to'plami, lekin har birida turli maydonlar |
Oddiy class | O'zgaruvchan holat yoki murakkab xatti-harakat |
- Ikki maydonli oddiy sinf yozib, unga
equalsvahashCodeqo'shing. - Xuddi shuni
recordbilan bir qatorda yozing. equalssiz sinfniHashSetga ikki marta qo'shib ko'ring.recordga ixcham konstruktor bilan tekshiruv qo'shing.recordga metod vastaticdoimiy qo'shing.- Maydonli va metodli
enumyozing. values(),valueOf()vaordinal()ni sinang.enumbo'yichadefaultsizswitchyozing.enumga yangi qiymat qo'shib, kompilyator nima deyishini ko'ring.sealedinterfeys va uchtarecordvoris yozing.
Xulosa #
recordkonstruktor,toString,equalsvahashCodeni o'zi yozadi.recordmaydonlari o'zgarmas - bu afzallik.equalsyozsangiz,hashCodeham shart - aks holdaHashSetvaHashMapjimgina noto'g'ri ishlaydi.- Ixcham konstruktor (
Ball {) tekshiruv qo'shish uchun. - Java'da
enummaydon, konstruktor va metodga ega bo'la oladi. ordinal()ni saqlamang -name()yoki o'z maydoningizni ishlating.enumvasealedbilanswitchto'liqlikni tekshiradi -defaultyozmang.sealedvorislari bir paketda bo'lishi shart.
Keyingi bo'limda kolleksiyalarni 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.