aidoc.uz

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 shart

Muhim 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 aniqlanadiRuntime'da hisoblash mumkinmi
finalRuntime'da, lekin faqat bir marta✅ (masalan, final now = DateTime.now();)
constCompile-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'q

Asosiy 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 ishlamaydi

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

Nega 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#

SavolXato (junior) javobTo'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.

Amaliy topshiriq

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.