1-qism · Dart asoslari·01-bob
Dart tili asoslari va null safety
O'zgaruvchilar, tiplash tizimi, sound null safety, to'plamlar va tenglik — Senior Flutter intervyusida deyarli har doim so'raladigan Dart asoslari.
Flutter intervyusi deyarli har doim Dart tilining o'zidan boshlanadi — va bu
tasodif emas. Widget'larni yozish mumkin bo'lgan har qanday dasturchi
StatelessWidgetni ko'chirib yoza oladi, lekin nima uchun final o'zgaruvchi
runtime xatosidan himoya qiladi yoki nega ikkita bir xil ko'rinadigan
ro'yxat == orqali solishtirilganda false qaytaradi — buni tushunish
darajani ajratadi. Bu bobda intervyuyerlar eng ko'p tekshiradigan Dart
asoslarini — o'zgaruvchilar, tiplash, null safety, to'plamlar va tenglik
mantig'ini — chuqur ko'rib chiqamiz.
1. var, final, const — farqi nimada va nega muhim#
Uchalasi ham o'zgaruvchi e'lon qiladi, lekin ular butunlay boshqa kafolatlarni beradi:
var shahar = "Toshkent"; // tipi runtime'da emas, compile-time'da aniqlanadi
shahar = "Samarqand"; // ✅ mumkin — qiymatni qayta tayinlash mumkin
final yil = 2026; // qiymat FAQAT bir marta tayinlanadi
// yil = 2027; // ❌ compile xatosi
const pi = 3.14159; // compile-time konstant — qiymat build paytida ma'lum bo'lishi shartMuhim nozik farq: var — bu "tipni avtomatik xulosa qilish" (type
inference), dinamik tiplash EMAS. var shahar = "Toshkent" deganda Dart
shaharni butun umr String tipida deb belgilaydi — keyinroq unga int
tayinlashga urinsangiz, compile xatosi chiqadi. Chinakam dinamik tiplash
uchun maxsus dynamic kaliti kerak (pastda ko'ramiz).
final va const orasidagi farq esa qachon qiymat ma'lum bo'lishida:
| Qiymat qachon aniqlanadi | Runtime'da hisoblash mumkinmi | |
|---|---|---|
final | Runtime'da, lekin faqat bir marta | ✅ (masalan, final now = DateTime.now();) |
const | Compile-time'da, o'zgarmas | ❌ — faqat compile-time konstantalar (const now = DateTime.now(); xato beradi) |
2. Statik tiplash, dynamic va Object#
Dart — statik tiplangan til, lekin ikkita "chegara" mavjud: dynamic va
Object (yoki Object?).
Object obj = "matn"; // Object — HAR QANDAY qiymatni saqlashi mumkin, lekin
// metodlarga kirish uchun avval tip tekshirish/cast kerak
// obj.length; // ❌ compile xatosi — Object'da .length yo'q
if (obj is String) {
print(obj.length); // ✅ smart cast — Dart avtomatik String deb "tor"laydi
}
dynamic dyn = "matn";
print(dyn.length); // ✅ compile-time tekshiruvi YO'Q — runtime'da xato chiqishi mumkin
dyn = 42;
print(dyn.length); // 💥 runtime xatosi: int'da .length yo'qAsosiy farq: Object bilan compiler sizni himoya qiladi (cast qilmasdan
metod chaqira olmaysiz), dynamic bilan esa siz compiler tekshiruvini
butunlay o'chirasiz — bu moslashuvchanlik beradi, lekin xatolarni runtime'ga
suradi. Senior darajadagi javob: "dynamicni faqat chindan ham tipi oldindan
noma'lum bo'lgan holatlarda (masalan, xom JSON bilan ishlashda jsonDecode()
natijasi) ishlataman, va imkon qadar tezroq aniq tipga cast qilaman."
3. Sound null safety — Dart'ning eng katta arxitektura o'zgarishi#
Dart 2.12'dan boshlab til sound null safetyga o'tdi: har bir tip
sukut bo'yicha null bo'la olmaydi, agar siz buni aniq ? bilan
belgilamasangiz.
String ism = "Aziz"; // hech qachon null bo'la olmaydi — compiler kafolatlaydi
String? familiya; // null bo'lishi MUMKIN, boshlang'ich qiymati — null
// ism = null; // ❌ compile xatosi
familiya = null; // ✅ ruxsat berilgan, chunki tip String?"Sound" so'zi shuni anglatadi: bu shunchaki lint ogohlantirishi emas —
compiler matematik jihatdan kafolatlaydi String tipidagi o'zgaruvchi
hech qachon null bo'lmasligini, va bu kafolat runtime'da ham buzilmaydi
(reflection yoki native interop orqali ham). Shu sababli null safety
yoqilgan kodda NullPointerExceptionga o'xshash xatolar deyarli yo'qoladi.
?, !, ??, ??= va late — amaliy qo'llanish#
String? nomi;
// ?. — null bo'lsa, zanjirni to'xtatadi va null qaytaradi
print(nomi?.length); // null (xato tashlamaydi)
// ?? — null bo'lsa, standart qiymat beradi
String korsatiladigan = nomi ?? "Noma'lum";
// ??= — faqat hozirgi qiymat null bo'lsa, tayinlaydi
nomi ??= "Mehmon";
// ! — "men bilaman, bu null emas" deb compiler'ga ishontirish (xavfli!)
String? balki = getUserName();
String aniq = balki!; // agar balki chindan null bo'lsa — runtime xatosi
// late — "keyinroq initsializatsiya qilaman, lekin tip nullable emas"
late String konfiguratsiya;
void init() {
konfiguratsiya = yuklaKonfiguratsiya(); // shu yergacha o'qishga urinsa — LateInitializationError
}! operatori — eng ko'p suiiste'mol qilinadigan konstruksiya. U compiler
xatosini runtime xatosiga aylantiradi: agar noto'g'ri bo'lsa, dastur
ishlab chiqarishda (production'da) qulaydi. Senior dasturchi !ni faqat
haqiqatan ham null bo'lmasligiga 100% ishonch bo'lgan joyda (masalan, shu
zahotiyoq if (x != null) tekshiruvidan keyin, garchi bu holatda ko'pincha
smart cast avtomatik ishlaydi) ishlatadi, aks holda ?? yoki aniq xato
xabari bilan tekshiruvni afzal ko'radi.
4. Built-in tiplar va ularning nozik tomonlari#
int son = 42; // platformaga qarab 64-bit (web'da JS number cheklovi bor)
double kasr = 3.14;
num universalRaqam = 5; // int yoki double bo'lishi mumkin bo'lgan ota tip
String matn = 'bitta yoki "ikki" qo\'shtirnoq ham ishlaydi';
bool bayroq = true;
// int va double orasidagi avtomatik konversiya YO'Q
int a = 5;
double b = a.toDouble(); // aniq konversiya kerak
// double b = a; // ❌ xato (faqat literal uchun avtomatik ishlaydi: double c = 5;)Web'dagi maxsus holat — bu senior darajadagi savolларда tez-tez
chiqadi: Flutter Web JavaScript'ga kompilyatsiya qilinganda, int tipi
JS'ning 53-bitli xavfsiz butun son chegarasiga bog'lanadi. Ya'ni mobil/
desktop'da to'g'ri ishlaydigan juda katta int arifmetikasi (masalan,
kriptografik hisob-kitoblarda) web'da noto'g'ri natija berishi mumkin.
5. To'plamlar — List, Set, Map va zamonaviy sintaksis#
// List — tartiblangan, takrorlanuvchi elementlarga ruxsat beradi
final royxat = <int>[1, 2, 3];
final ozgarmas = List.unmodifiable(royxat); // runtime'da himoyalangan (const emas!)
final ozgaruvchan = List<int>.filled(3, 0, growable: true);
// Set — takrorlanmaydigan elementlar, tartib kafolatlanmaydi
final toplam = <String>{"olma", "nok", "olma"}; // {"olma", "nok"} — 2 ta element
// Map — kalit-qiymat juftliklari
final foydalanuvchi = <String, dynamic>{
"ism": "Aziz",
"yosh": 28,
};
// Spread operator (...) va null-aware spread (...?)
final asosiy = [1, 2, 3];
final kengaytirilgan = [0, ...asosiy, 4]; // [0, 1, 2, 3, 4]
List<int>? balkiNull;
final xavfsiz = [0, ...?balkiNull, 4]; // [0, 4] — null bo'lsa, o'tkazib yuboriladi
// Collection-if va collection-for — shartli va tsiklli elementlar
final royxat2 = [
"har doim",
if (bayroq) "shart bajarilsa",
for (var i in [1, 2, 3]) "element-$i",
];List.unmodifiable va const [] orasidagi farq — bu tez-tez chalkashadigan
joy: const [] compile-time'da o'zgarmas ro'yxat yaratadi (butun dastur
davomida bitta obyekt sifatida qayta ishlatiladi — xotira tejaydi), List. unmodifiable() esa runtime'da mavjud ro'yxatdan himoyalangan nusxa
yaratadi — unga yozishga urinish UnsupportedError tashlaydi, lekin bu
compile-time tekshiruvi emas.
6. String interpolatsiya va ko'p qatorli satrlar#
final ism = "Dilnoza";
final yosh = 25;
print("Salom, $ism! Siz $yosh yoshdasiz."); // oddiy interpolatsiya
print("Keyingi yil siz ${yosh + 1} yoshda bo'lasiz."); // ifoda uchun {}
final koPQatorli = """
Bu ko'p qatorli
matn — kavychalarni
escape qilish shart emas.
""";
final raw = r"C:\Users\Aziz\Documents"; // raw string — \n kabi escape'lar ishlamaydi7. Tenglik: ==, identical() va hashCode#
Bu — senior intervyularida eng ko'p "tutib qoladigan" mavzulardan biri.
class Nuqta {
final int x, y;
Nuqta(this.x, this.y);
}
final a = Nuqta(1, 2);
final b = Nuqta(1, 2);
print(a == b); // false! — chunki == override qilinmagan, u identical() ga tushadi
print(identical(a, b)); // false — ikki xil obyekt xotirada
class NuqtaTogri {
final int x, y;
NuqtaTogri(this.x, this.y);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is NuqtaTogri && runtimeType == other.runtimeType && x == other.x && y == other.y;
@override
int get hashCode => Object.hash(x, y);
}
final c = NuqtaTogri(1, 2);
final d = NuqtaTogri(1, 2);
print(c == d); // true — endi qiymat bo'yicha solishtiriladiNega hashCodeni == bilan birga override qilish shart? Chunki
Set va Map kabi kolleksiyalar ichida elementlarni joylashtirish uchun
avval hashCodedan foydalanadi (qaysi "bucket"ga qo'yishni aniqlash
uchun), so'ngra shu bucket ichida == bilan aniq solishtiradi. Agar ikki
teng obyekt turli hashCode qaytarsa, ular HashSet/HashMapda turli
bucket'larga tushib qoladi va hech qachon "teng" deb topilmaydi — bu
kontrakt buzilishi (Dart va Java'dagi kabi umumiy qoida: teng obyektlar
har doim teng hashCodega ega bo'lishi shart, lekin teskarisi shart
emas).
Equatable yoki freezed kabi paketlar aynan shu ikkala metodni (va
odatda toStringni ham) avtomatik generatsiya qilib beradi — shuning
uchun ular Flutter loyihalarida keng qo'llaniladi, ayniqsa Bloc state
klasslarida.
8. Ko'p uchraydigan xato javoblar#
| Savol | Xato (junior) javob | To'g'ri (senior) javob |
|---|---|---|
"final va const farqi nima?" | "Ikkalasi ham o'zgarmas" | const compile-time, final runtime'da bir marta tayinlanadigan qiymat; const obyektlar xotirada qayta ishlatiladi |
"dynamic xavfsizmi?" | "Ha, varga o'xshash" | Yo'q — var statik tip xulosasi, dynamic compile-time tekshiruvini butunlay o'chiradi |
"! operatori nima qiladi?" | "Null emasligini bildiradi" | Compiler'ga "ishonch" beradi; noto'g'ri bo'lsa runtime'da qulaydi — ehtiyotkorlik bilan ishlatiladi |
"Ikkita bir xil obyekt nega == bilan false?" | "Bug bo'lsa kerak" | Sukut bo'yicha == identical()ga teng — operator override qilinmagan bo'lishi mumkin |
Tez-tez so'raladigan intervyu savollari#
Savol: var, final va const orasidagi farqni tushuntiring. Qachon
qaysi birini ishlatasiz?
Javob: var — tip xulosasi bilan o'zgaruvchan o'zgaruvchi (qiymatni
qayta tayinlash mumkin). final — bir marta tayinlanadigan, lekin runtime'da
hisoblanishi mumkin bo'lgan qiymat (masalan, DateTime.now() yoki metod
parametri). const — compile-time'da ma'lum bo'lishi shart bo'lgan,
butunlay o'zgarmas qiymat; Dart bir xil const qiymatlarni xotirada bitta
obyekt sifatida qayta ishlatadi (canonicalization), bu esa xotira va
performance foyda beradi. Amaliyotda: widget'lar va konstantalar uchun
const, deyarli hamma boshqa joyda final (mutable holat kerak bo'lmasa),
varni esa faqat aniq kontekstda tip ko'rinib turgan joyda.
Savol: Sound null safety nima va u nima uchun "sound" deb ataladi?
Javob: Sound null safety — Dart compiler'i tip tizimi orqali null
xavfsizligini matematik jihatdan kafolatlaydigan tizim. "Sound" so'zi
shuni bildiradi: agar kod compile bo'lsa, String deb e'lon qilingan
o'zgaruvchi hech qachon, hech qanday yo'l bilan (shu jumladan native kod
bilan interop orqali ham) null bo'la olmaydi — bu shunchaki statik
tahlil ogohlantirishi emas, balki runtime kafolati. Bu Dart'ga ba'zi
runtime tekshiruvlarni butunlay olib tashlash va shu orqali performance'ni
oshirish imkonini beradi.
Savol: ! operatorini ishlatish qachon maqbul, qachon emas?
Javob: ! maqbul, agar null bo'lmasligi arxitektura darajasida
kafolatlangan bo'lsa-yu, lekin compiler buni "ko'ra olmasa" — masalan,
late bilan e'lon qilingan, initState()da to'ldirilishi kafolatlangan
maydonlar. Maqbul emas: tashqi manbadan (API javobi, foydalanuvchi kirishi)
kelgan qiymatlarga, chunki bu holatlarda null haqiqatan ham kelishi
mumkin va dastur production'da qulab tushadi. Bunday joylarda ?? bilan
standart qiymat yoki aniq if (x == null) tekshiruvi bilan xatoni
boshqarish kerak.
Savol: List.unmodifiable() va UnmodifiableListView orasidagi farq
bormi?
Javob: List.unmodifiable(source) — sourcedagi elementlarning
nusxasini yaratadi va uni himoyalaydi; asl source keyin o'zgarsa,
yaratilgan ro'yxat o'zgarmaydi. UnmodifiableListView(source) esa
source ustidan o'ram (wrapper) yaratadi — nusxa olmaydi, shuning
uchun tezroq va xotira tejamkor, lekin agar asl source boshqa joyda
o'zgartirilsa, view ham o'zgargan holatni ko'rsatadi. Bu farq katta
ro'yxatlar bilan ishlaganda performance jihatidan muhim.
Savol: Nega Dart'da int + double avtomatik ishlaydi, lekin
int variable = someDouble ishlamaydi?
Javob: num — int va doublening umumiy ota tipi, va arifmetik
operatorlar (+, * va h.k.) num darajasida ishlaydi, natija kontekstga
qarab int yoki double bo'ladi (masalan, 5 + 2.0 → 7.0, chunki
kamida bitta operand double). Lekin bu implicit downcasting emas —
intga double tayinlash aniq .toInt() yoki .round() kabi metod
talab qiladi, chunki bu ma'lumot yo'qotilishiga (kasr qismi) olib kelishi
mumkin va Dart buni ataylab yashirmaydi.
Savol: Set ichida custom klass obyektlarini ishlatmoqchisiz. Nimalarga
e'tibor berish kerak?
Javob: Set ichki tuzilishi hash-jadval bo'lgani uchun klass == va
hashCodeni birga override qilishi shart, aks holda Set obyektlarni
identifikatsiya (obyekt referensi) bo'yicha solishtiradi — natijada
"bir xil" ma'lumotli ikkita obyekt ikkita alohida element sifatida
saqlanadi. Shuningdek, Setga qo'shilgandan keyin obyektning hashCodega
ta'sir qiluvchi maydonlarini o'zgartirish xavfli — bu obyektni Set ichida
"yo'qolgan" holatga keltirishi mumkin (hash bucket noto'g'ri bo'lib qoladi).
Savol: late kalit so'zining ikki xil qo'llanilish holatini
tushuntiring.
Javob: Birinchisi — "kechiktirilgan majburiy initsializatsiya":
o'zgaruvchi e'lon qilinganda qiymat berilmaydi, lekin build() yoki
foydalanishdan oldin (masalan initState()da) albatta to'ldirilishi
kafolatlanadi; agar to'ldirilmay o'qilsa — LateInitializationError.
Ikkinchisi — "lazy (dangal) initsializatsiya": late final x = qimmatHisoblash(); yozilganda, o'ng tomondagi ifoda faqat x birinchi
marta o'qilganda hisoblanadi, keyingi o'qishlarda keshlangan qiymat
qaytariladi — bu qimmat hisob-kitoblarni kerak bo'lmasa umuman
bajarmaslik uchun ataylab ishlatiladi.
Savol: Object va dynamic orasidagi farqni misol bilan tushuntiring.
Javob: Ikkalasi ham "har qanday tip"ni saqlay oladi, lekin Object
bilan ishlashda compiler tip xavfsizligini saqlab qoladi — metodga
murojaat qilishdan oldin is bilan tip tekshirish yoki cast qilish talab
qilinadi. dynamic esa compiler'ga "bu o'zgaruvchi ustida hech qanday
statik tekshiruv qilma" deydi — istalgan metodni chaqirishga ruxsat
beradi, lekin agar u metod mavjud bo'lmasa, xato faqat runtime'da
chiqadi (NoSuchMethodError). Katta jamoa loyihalarida dynamicdan
qochish tavsiya etiladi, chunki u IDE'ning avtomatik to'ldirish va
refaktoring imkoniyatlarini ham yo'qotadi.
Savol: Collection-if va collection-for qanday muammoni hal qiladi?
Javob: Ular widget ro'yxatlari yoki ma'lumot to'plamlarini shartli/
tsiklli tarzda qurishda List.add() chaqiruvlari yoki oraliq o'zgaruvchilar
zanjirisiz, deklarativ tarzda yozish imkonini beradi. Masalan, Flutter'da
Column(children: [Text("sarlavha"), if (xatoBormi) ErrorBanner(), for (var item in royxat) ItemCard(item)]) — bu kod tashqi mutable state'siz,
build() metodi ichida to'g'ridan-to'g'ri o'qilishi mumkin bo'lgan
deklarativ UI qurish uslubiga mos keladi, bu esa Flutter'ning umumiy
falsafasi bilan bir xil.
Savol: Agar sizga xom JSON javobi bilan ishlash kerak bo'lsa (tipi oldindan noma'lum), qanday yondashuv tanlaysiz?
Javob: jsonDecode() odatda dynamic (aslida Map<String, dynamic>)
qaytaradi — bu chekli darajada muqarrar, lekin men buni chegara qatlamida
(masalan, fromJson factory constructor'da) darhol aniq tiplangan model
klasslariga aylantiraman va qolgan ilova kodi bu model bilan, dynamic
bilan emas, ishlaydi. Bu "parse, don't validate" tamoyili — tip xavfsizligi
chegaradan bir marta o'tgach butun ilova bo'ylab saqlanadi, har bir joyda
qayta tekshirish shart bo'lmaydi.
Null-xavfsiz konfiguratsiya klassi
AppConfig nomli klass yozing: apiUrl (majburiy, String), timeout
(ixtiyoriy, Duration?, standart holatda null) va retryCount (late final int, faqat birinchi murojaatda tasodifiy standart qiymat bilan
to'ldiriladigan). Klassga qiymat bo'yicha solishtiradigan == va
hashCode qo'shing, so'ngra ikkita bir xil konfiguratsiyani Set<AppConfig>ga
qo'shib, to'plamda faqat bitta element qolishini tasdiqlang.
Maslahatni ko'rsatish
required va nullable maydonlarni ажратинг; == va hashCode'ni qo'lda yoki Object.hash() bilan yozing; keyin bir xil qiymatli ikkita obyektni Set ichiga qo'shib tekshiring.