↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 2 — Konu 9: Null Safety Temelleri (`?`, `!`, `late`'e Giriş)

4 dk okuma #dart
Dizi · 9/64 Dart Türkçe Tutorial
  1. Bölüm 1 — Konu 1: Dart Nedir, Nerede Kullanılır, Neden Flutter Bu Dili Seçti
  2. Bölüm 1 — Konu 2: Ortam Kurulumu (Dart SDK, DartPad, Terminal ile Çalıştırma)
  3. Bölüm 1 — Konu 3: İlk Program (`main()`, `print()`, Dosya Yapısı)
  4. Bölüm 1 — Konu 4: Yorum Satırları, Temel Sözdizimi Kuralları
  5. Bölüm 2 — Konu 5: Değişken Tanımlama — `var`, `final`, `const` Farkı
  6. Bölüm 2 — Konu 6: Temel Tipler — `int`, `double`, `String`, `bool`
  7. Bölüm 2 — Konu 7: Tip Çıkarımı (Type Inference) ve Açık Tip Belirtme
  8. Bölüm 2 — Konu 8: `dynamic` ve `Object` — Ne Zaman Kullanılır, Ne Zaman Kaçınılır
  9. Bölüm 2 — Konu 9: Null Safety Temelleri (`?`, `!`, `late`'e Giriş)
  10. Bölüm 3 — Konu 10: Aritmetik, Atama, Karşılaştırma, Mantıksal Operatörler
  11. Bölüm 3 — Konu 11: `if / else if / else`
  12. Bölüm 3 — Konu 12: `switch` / `switch expression` (Modern Dart)
  13. Bölüm 3 — Konu 13: Ternary Operatör, `??`, `??=`, `?.`
  14. Bölüm 3 — Konu 14: `for`, `while`, `do-while` Döngüleri
  15. Bölüm 4 — Konu 16: `List` — Oluşturma, Erişim, Temel Metodlar
  16. Bölüm 4 — Konu 17: `Set` — Benzersiz Eleman Mantığı
  17. Bölüm 4 — Konu 18: `Map` — Key-Value Yapılar
  18. Bölüm 4 — Konu 19: Koleksiyon Üzerinde `for-in`, `forEach`
  19. Bölüm 4 — Konu 20: Spread Operatörü (`...`, `...?`) ve Collection If/For
  20. Bölüm 5 — Konu 21: Fonksiyon Tanımlama, Parametreler (Positional, Named, Optional)
  21. Bölüm 5 — Konu 22: Varsayılan Parametre Değerleri (Derinlemesine)
  22. Bölüm 5 — Konu 23: Arrow Function (`=>`) Sözdizimi
  23. Bölüm 5 — Konu 24: Fonksiyonlar Birinci Sınıf Vatandaş — Değişkene Atama, Parametre Olarak Geçme
  24. Bölüm 5 — Konu 25: Anonim Fonksiyonlar ve Closure Kavramı (Derinlemesine)
  25. Bölüm 5 — Konu 26: Recursion (Özyineleme)
  26. Bölüm 6 — Konu 27: Class Tanımlama, Constructor (Varsayılan, Named, Factory)
  27. Bölüm 6 — Konu 28: Alanlar (Fields), Metodlar, `this` Kullanımı (Derinlemesine)
  28. Bölüm 6 — Konu 29: Initializer List, Constructor Kısayolları (Derinlemesine)
  29. Bölüm 6 — Konu 30: Getter / Setter
  30. Bölüm 6 — Konu 31: Statik Üyeler (`static`)
  31. Bölüm 7 — Konu 32: Kalıtım (`extends`), `super` Kullanımı
  32. Bölüm 7 — Konu 33: Metod Override Etme, `@override`
  33. Bölüm 7 — Konu 34: Soyut Sınıflar (`abstract class`)
  34. Bölüm 7 — Konu 35: Interface Mantığı (`implements`)
  35. Bölüm 7 — Konu 36: Mixin (`with`)
  36. Bölüm 7 — Konu 37: `enum` — Basit ve Gelişmiş (Metotlu Enum'lar)
  37. Bölüm 8 — Konu 38: `try / catch / finally`, `throw`
  38. Bölüm 8 — Konu 39: Özel Exception Sınıfları Yazma
  39. Bölüm 8 — Konu 40: Sound Null Safety Derinlemesine — `late`, `required`, Non-Nullable Varsayılan Davranış
  40. Bölüm 8 — Konu 41: `assert` ile Geliştirme Zamanı Kontrolleri
  41. Bölüm 9 — Konu 42: Generic Sınıflar ve Fonksiyonlar
  42. Bölüm 9 — Konu 43: Generic Sınırlamalar (`<T extends ...>`)
  43. Bölüm 9 — Konu 44: Dart'ın Built-in Generic Koleksiyonları Nasıl Çalışır
  44. Bölüm 10 — Konu 45: `Future` Kavramı, `async`/`await`
  45. Bölüm 10 — Konu 46: `Future.then`, Hata Yönetimi (`catchError`)
  46. Bölüm 10 — Konu 47: `Stream` Temelleri, `async*`, `yield`
  47. Bölüm 10 — Konu 48: `StreamController`, Broadcast Stream
  48. Bölüm 11 — Konu 49: `map`, `where`, `reduce`, `fold`
  49. Bölüm 11 — Konu 50: `sort`, Custom Comparator ile Sıralama
  50. Bölüm 11 — Konu 51: Iterable Kavramı Derinlemesine (Lazy Evaluation)
  51. Bölüm 11 — Konu 52: Cascade Notasyonu (`..`)
  52. Bölüm 12 — Konu 53: `pubspec.yaml`, pub.dev'den Paket Ekleme
  53. Bölüm 12 — Konu 54: Kütüphane Oluşturma, `import`/`export`, `part`/`part of`
  54. Bölüm 12 — Konu 55: Dart'ta Test Yazımı (`test` Paketi, Unit Test Mantığı)
  55. Bölüm 12 — Konu 56: Extension Methods
  56. Bölüm 12 — Konu 57: Dart'ın Derleme Modelleri (JIT vs AOT) ve Bunun Flutter'a Etkisi
  57. Bölüm 13 — Konu 59: Dart 3 Pattern Matching — Records, Destructuring, Sealed Classes, Exhaustive Switch
  58. Bölüm 13 — Konu 60: `Never` Tipi, `covariant` ve Generic Variance Detayları
  59. Bölüm 13 — Konu 61: Event Loop Derinlemesine — Microtask Queue vs Event Queue
  60. Bölüm 13 — Konu 62: Memory Model & Performans — Garbage Collection, `const` Nesnelerin Bellek Avantajı
  61. Bölüm 13 — Konu 63: Isolate'ler Arası Mesajlaşmanın Maliyeti ve `compute()`'un İç İşleyişi
  62. Bölüm 13 — Konu 64: FFI (Foreign Function Interface) — C Koduna Erişim
  63. Bölüm 13 — Konu 65: Sunucu Tarafında Dart — `dart:io`, `shelf` Paketiyle Basit Bir Backend/CLI Aracı Yazma
  64. Bölüm 13 — Konu 66: Derleyici & Analiz Araçları — `dart analyze`, Custom Lint Kuralları, `build_runner` Mimarisi
Dizinin sayfası →
İçindekiler 14 başlık
  1. "Null Reference" Sorunu Nedir?
  2. Varsayılan Olarak Hiçbir Şey Null Olamaz
  3. ? — Bir Tipin Null Olabileceğini Belirtmek
  4. Nullable Bir Değişkenle Çalışırken Dikkat Edilmesi Gerekenler
  5. 1. Null kontrolü (if) ile
  6. 2. ?. — Null-aware erişim operatörü
  7. 3. ?? — Varsayılan değer belirleme
  8. 4. ! — "Bana Güven, Bu Null Değil" (Null Assertion)
  9. late — "Şimdi Değil Ama Kesinlikle Sonra Atanacak"
  10. Özet Tablo — Null Safety Araçları
  11. Neden Bu Kadar Önemli?
  12. 🎯 Bu Dersten Çıkarılması Gerekenler
  13. 📝 Ödevler
  14. 🏁 Bölüm 2 Tamamlandı!

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:

metin
NullPointerException: Cannot invoke method on null object

Bu 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:

dart
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:

dart
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

dart
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

dart
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ü

dart
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

dart
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)

dart
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:

dart
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:

dart
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:

dart
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) bir RuntimeError oluştur, hatayı oku ve yorumla.
  • [ ] late ile bir değişken tanımla, değer atamadan önce kullanmayı dene, LateInitializationError hatası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.