Bölüm 2 — Konu 9: Null Safety Temelleri (`?`, `!`, `late`'e Giriş)
Dizi · 9/9 Dart Türkçe Tutorial
- Bölüm 1 — Konu 1: Dart Nedir, Nerede Kullanılır, Neden Flutter Bu Dili Seçti
- Bölüm 1 — Konu 2: Ortam Kurulumu (Dart SDK, DartPad, Terminal ile Çalıştırma)
- Bölüm 1 — Konu 3: İlk Program (`main()`, `print()`, Dosya Yapısı)
- Bölüm 1 — Konu 4: Yorum Satırları, Temel Sözdizimi Kuralları
- Bölüm 2 — Konu 5: Değişken Tanımlama — `var`, `final`, `const` Farkı
- Bölüm 2 — Konu 6: Temel Tipler — `int`, `double`, `String`, `bool`
- Bölüm 2 — Konu 7: Tip Çıkarımı (Type Inference) ve Açık Tip Belirtme
- Bölüm 2 — Konu 8: `dynamic` ve `Object` — Ne Zaman Kullanılır, Ne Zaman Kaçınılır
- Bölüm 2 — Konu 9: Null Safety Temelleri (`?`, `!`, `late`'e Giriş)
Bölüm 2'nin son konusundayız — ve bu, Dart'ın en önemli özelliklerinden birine giriş: sound null safety. Bu konu, Dart'ın diğer birçok dile göre öne çıktığı en kritik noktalardan biri, o yüzden dikkatlice ilerleyelim.
"Null Reference" Sorunu Nedir?
Programlamanın en yaygın hatalarından biri, olmayan (null) bir değer üzerinde işlem yapmaya çalışmaktır. Java, JavaScript gibi birçok dilde şuna benzer bir hata sıkça karşına çıkar:
NullPointerException: Cannot invoke method on null objectBu hatalar genelde çalışma zamanında, hiç beklenmedik bir anda (genelde üretimde, kullanıcı karşısında) ortaya çıkar. Bu o kadar yaygın bir problemdir ki, bunu icat eden bilgisayar bilimci Tony Hoare bunu "bir milyar dolarlık hatam" olarak tanımlamıştır.
Dart, 2.12 sürümünden itibaren (2021) bu problemi derleme zamanında çözen bir sistem getirdi: sound null safety.
Varsayılan Olarak Hiçbir Şey Null Olamaz
Dart'ta, null safety ile birlikte, bir değişken varsayılan olarak null değer alamaz:
void main() {
String isim = "Ahmet";
isim = null; // ❌ HATA! String tipi null kabul etmez
}Bu kod derlenmez. String isim yazdığında, Dart bunu "bu değişken kesinlikle bir String tutacak, asla null olmayacak" olarak yorumlar. Bu, birçok hatayı daha kod çalışmadan yakalamanı sağlar.
? — Bir Tipin Null Olabileceğini Belirtmek
Eğer gerçekten bir değişkenin null olabilmesini istiyorsan, tipin sonuna ? eklersin:
void main() {
String? isim = "Ahmet"; // isim, String VEYA null olabilir
isim = null; // ✅ şimdi geçerli
print(isim); // null
}String? ile String birbirinden tamamen farklı tiplerdir aslında — String?, "ya String ya da null" anlamına gelir. Bu, Dart'ın tip sisteminde Null tipinin de Object? hiyerarşisine dahil edilmesiyle mümkün olur (bir önceki derste gördüğümüz tip ağacını hatırla).
Nullable Bir Değişkenle Çalışırken Dikkat Edilmesi Gerekenler
void main() {
String? isim = "Ahmet";
print(isim.length); // ❌ HATA! isim null olabilir, doğrudan .length çağıramazsın
}Dart, isim değişkeninin potansiyel olarak null olabileceğini bildiği için, üzerinde doğrudan metod/özellik çağırmana izin vermez — çünkü eğer gerçekten null olsaydı, bu çalışma zamanında çökerdi. Dart bunu, çalışmadan önce sana bildiriyor.
Bunu güvenli şekilde kullanmanın birkaç yolu var:
1. Null kontrolü (if) ile
void main() {
String? isim = "Ahmet";
if (isim != null) {
print(isim.length); // ✅ artık isim, bu blok içinde String (null değil) olarak biliniyor
}
}Bu, Dart'ın type promotion (tip daraltma) özelliğidir — if (isim != null) kontrolünden sonra, o blok içinde Dart, isim'in artık kesinlikle String (null olmayan) olduğunu bilir.
2. ?. — Null-aware erişim operatörü
void main() {
String? isim = "Ahmet";
print(isim?.length); // isim null değilse .length'i çağırır, null ise sonucu null olur
isim = null;
print(isim?.length); // null — hata vermez, sadece null döner
}?., "eğer bu değer null değilse şu işlemi yap, null ise tüm ifadeyi null olarak değerlendir" anlamına gelir. Bu, çok satırlı if kontrolleri yazmak yerine tek satırda güvenli erişim sağlar.
3. ?? — Varsayılan değer belirleme
void main() {
String? isim;
print(isim ?? "İsimsiz"); // isim null olduğu için "İsimsiz" yazdırılır
isim = "Ahmet";
print(isim ?? "İsimsiz"); // isim null değil, "Ahmet" yazdırılır
}??, sol taraftaki değer null ise sağ taraftaki değeri kullanır. Bu, bir önceki dersimizde (Bölüm 3'te göreceğiz aslında, burada önizleme) operatörler konusunda daha detaylı işlenecek ama null safety ile doğrudan bağlantılı olduğu için burada da bahsetmek gerekiyordu.
4. ! — "Bana Güven, Bu Null Değil" (Null Assertion)
void main() {
String? isim = "Ahmet";
print(isim!.length); // "isim'in null olmadığından eminim" diyorsun
}! operatörü (null assertion / bang operator), Dart'a "bu değerin null olmadığından eminim, kontrolü atla" demektir. Bu tehlikeli bir operatördür — eğer yanılıyorsan (değer gerçekten null ise), program çalışma zamanında çöker:
void main() {
String? isim;
print(isim!.length); // ❌ RUNTIME HATASI! isim gerçekten null, ! seni kurtaramaz
}Genel kural: ! operatöründen mümkün olduğunca kaçın. Bunu kullanmak, aslında null safety'nin sana sağladığı güvenlik ağını manuel olarak devre dışı bırakmak demektir. ! kullanman gerektiğini düşündüğün her yerde, önce "gerçekten bu değerin null olamayacağından %100 emin miyim, yoksa ?. veya ?? ile daha güvenli bir yol var mı" diye sor.
late — "Şimdi Değil Ama Kesinlikle Sonra Atanacak"
Bazen bir değişkeni tanımladığın anda değer atayamazsın, ama yine de o değişkenin non-nullable (null olmayan) bir tip olmasını istersin. İşte late burada devreye girer:
class Kullanici {
late String isim; // şimdi değer yok ama String (null değil) olacak
void ismiAyarla(String yeniIsim) {
isim = yeniIsim;
}
}late ile işaretlenen bir değişken, tanımlandığı anda değer almak zorunda değildir, ama kullanılmadan önce mutlaka bir değer atanmış olmalıdır. Eğer değer atanmadan kullanmaya çalışırsan:
void main() {
late String mesaj;
print(mesaj); // ❌ RUNTIME HATASI! mesaj hiç atanmadı
}Bu hata alınır: LateInitializationError.
late'in gerçek kullanım senaryolarını (özellikle sınıflarda constructor ile ilişkili kullanımını) Bölüm 6'da sınıflar konusuna geldiğimizde çok daha net göreceğiz — çünkü late'in en yaygın kullanım alanı, bir sınıfın alanının constructor'da değil, başka bir metodda (örneğin initState benzeri bir yerde, Flutter'da sıkça karşına çıkacak) atanmasıdır.
Özet Tablo — Null Safety Araçları
| Araç | Ne Yapar | Risk Seviyesi |
|---|---|---|
Tip? |
Değişkenin null olabileceğini belirtir | Güvenli — açık ve dürüst |
if (x != null) |
Null kontrolü yapar, type promotion sağlar | Çok güvenli |
?. |
Null ise işlemi atlar, sonucu null yapar | Güvenli |
?? |
Null ise varsayılan değer kullanır | Güvenli |
! |
"Null değil" diye zorla kabul ettirir | ⚠️ Riskli — dikkatli kullan |
late |
Geç atamaya izin verir ama non-nullable kalır | Kullanım yerine göre değişir |
Neden Bu Kadar Önemli?
Bu konuyu bu kadar detaylı işlememizin sebebi şu: null safety, Dart'ın günlük yazdığın hemen hemen her satırını etkiler. Flutter'da her widget, her API cevabı, her kullanıcı girdisi bu sistemle iç içe çalışacak. Bu temeli şimdi sağlam kurmak, ileride "neden bu hata çıkıyor" diye saatlerce debug etmeni engelleyecek.
🎯 Bu Dersten Çıkarılması Gerekenler
- Dart'ta varsayılan olarak hiçbir tip null olamaz; nullable yapmak için tipin sonuna
?eklenir. ?.: null-aware erişim, null ise işlemi atlar.??: null ise varsayılan değer sağlar.!: "bu null değil" diye zorlar — riskli, dikkatli kullanılmalı.late: değişkeni hemen atamadan tanımlamana izin verir ama kullanılmadan önce mutlaka atanmış olmalıdır.
📝 Ödevler
- [ ] Nullable bir
String?değişken tanımla,if (x != null)ile kontrol ederek güvenli şekilde.lengthözelliğine eriş. - [ ] Aynı işlemi
?.operatörüyle tekrar yaz, iki yaklaşımı karşılaştır. - [ ]
??operatörünü kullanarak, null olan bir kullanıcı adı yerine "Misafir" yazdıran bir örnek yaz. - [ ]
!operatörünü bilerek yanlış kullanarak (gerçekten null olan bir değişkende) birRuntimeErroroluştur, hatayı oku ve yorumla. - [ ]
lateile bir değişken tanımla, değer atamadan önce kullanmayı dene,LateInitializationErrorhatasını gözlemle; sonra değer atayıp düzelt.
🏁 Bölüm 2 Tamamlandı!
Artık değişken tanımlama yollarını (var/final/const), temel tipleri, tip çıkarımını, dynamic/Object farkını ve null safety temellerini biliyorsun. Bu, Dart'ın en kritik temel taşlarıydı.
Sıradaki durak Bölüm 3 — Operatörler & Kontrol Akışı: aritmetik/mantıksal operatörler, if/else, switch, döngüler ile devam edeceğiz — burada gerçek anlamda "mantık kurmaya" başlayacağız.