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.
| Tip | Nə qəbul edir | Üzərində metod çağırmaq | Yoxlama |
|---|---|---|---|
| `Object` | `null` xaric hər şey | Yalnı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ır | Yoxdur, 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 vermir — dynamic d = 'text'; final n = d as int; yalnız runtime-da atır, analyzer isə susur.
Interview ipucu. "Object, Object? və 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
- Genericsrəsmidart.dev