Sparround

Generics və tip sistemi

Generic — tipi parametr kimi qəbul edən sinif və ya funksiya. Məqsəd: eyni məntiqi müxtəlif tiplər üçün tip təhlükəsizliyini itirmədən təkrar istifadə etmək.

  • Generic sinif: class Cache<T> { T? get(String key); void put(String key, T value); }
  • Generic funksiya: T firstOr<T>(List<T> items, T fallback) => items.isEmpty ? fallback : items.first;
  • Bir neçə parametr: class Pair<A, B> { ... }

Alternativ — hər şeyi dynamic etmək — kodu qısaldır, amma bütün səhvləri runtime-a köçürür. Generic isə çağırış yerində konkret tipi bilir: Cache<User> üçün get User? qaytarır və compiler bunu yoxlayır.

Bounded generic tip parametrinə məhdudiyyət qoyur: T extends Comparable<T> — yalnız müqayisə oluna bilən tiplər. Bu, generic gövdəsində həmin tipin metodlarını çağırmağa imkan verir. Məhdudiyyət olmasa, T yalnız Object? kimi davranır və üzərində demək olar ki, heç nə etmək olmur.

TipNə qəbul edirÜzərində metod çağırmaqYoxlama
`Object``null` xaric hər şeyYalnız `Object` metodları (`toString`, `hashCode`)Statik — `is` yoxlaması tələb olunur
`Object?``null` daxil hər şeyƏvvəlcə null yoxlaması, sonra `is`Statik
`dynamic`Hər şeyİstənilən metod — yoxlanmırYoxdur, səhv runtime-da
`T` (bound-suz)Hər şey`Object?` səviyyəsindəStatik, çağırış yerində konkretləşir
`T extends X`Yalnız `X` və alt tipləri`X`-in bütün metodlarıStatik

Kovariantlıq və onun bədəli. Dart-da generic-lər kovariant-dır: List<int> List<num>-un alt tipi sayılır. Bu, gündəlik kodu rahat edir (List<int>-i List<num> gözləyən funksiyaya ötürmək olur), amma yazma əməliyyatı üçün təhlükəsiz deyil.

Ssenari: List<int> ints = [1, 2]; List<num> nums = ints; nums.add(3.5); — statik olaraq hər şey qanunidir, çünki 3.5 num-dur. Runtime-da isə obyekt hələ də List<int>-dir və add TypeError atır.

Dart bunu "sound" saxlamaq üçün runtime-da implicit downcast yoxlaması əlavə edir — məhz buna görə xəta compile-time-da yox, add çağırışında baş verir. Yəni kovariantlıq rahatlıq üçün seçilmiş kompromisdir və bədəli kiçik runtime yoxlamasıdır.

Praktik nəticə: paylaşılan kolleksiyanı daha geniş tiplə (List<num>) qəbul edən API yazanda ya yalnız oxu (Iterable<num>), ya da öz nüsxəni (List<num>.from(...)) götür.

`is`, `as` və promotion. is — runtime tip yoxlaması; uğurlu olarsa, flow analysis dəyişəni promote edir və as yazmağa ehtiyac qalmır. as — cast; səhvdirsə TypeError atır. is! — inkar formasıdır.

Qayda: `as` yazmadan əvvəl `is` ilə yoxlamağın mümkün olub-olmadığını düşün. if (x is User) { x.name } as cast-dan həm təhlükəsiz, həm də oxunaqlıdır. Dart 3-də pattern-lər bunu bir addım da irəli aparır: if (x case User(:final name)).

as-in qanuni istifadə yerləri: jsonDecode nəticəsi kimi dynamic sərhədləri, generic-lərdə tipin çağırış yerində məlum olduğu hallar, və platform interop. Hər as üçün sual ver: bu cast səhv olsa, xəta harada üzə çıxacaq — burada, yoxsa üç ekran sonra?

Diqqət: dynamic üzərində cast heç bir yoxlama vermirdynamic d = 'text'; final n = d as int; yalnız runtime-da atır, analyzer isə susur.

Interview ipucu. "Object, Object?dynamic fərqi nədir?" — bu üçlüyü ayırd etmək orta səviyyənin ölçüsüdür. Güclü cavab: "`Object` — `null` xaric hər şey, `Object?` — `null` daxil hər şey; hər ikisində tip yoxlaması işləyir, metod çağırmaq üçün `is` lazımdır. `dynamic` isə tip deyil, yoxlamanın söndürülməsidir — istənilən metod çağırışı compile olunur və runtime-da `NoSuchMethodError` verə bilər." Sonra praktik seçimi əlavə et: "hər şeyi qəbul edən" parametr üçün Object?, tipsiz JSON sərhədi üçün dynamic — və dərhal real tipə çevirmək.

İkinci güclü mövzu kovariantlıqdır. List<int>-i List<num>-a mənimsədib add(3.5) çağırmağın niyə runtime-da atdığını izah edə bilmək senior siqnalıdır.

📚 Mənbələr və sənədlər