İçeriğe geç / Skip to content / Zum Inhalt

Dart'ta Tip Dönüşümleri ve Static Değişkenler

Ahmet Balaman

Son güncelleme:

6 dk okuma

FlutterDartProgramlama TemelleriTip Dönüşümü
Dart'ta Tip Dönüşümleri ve Static Değişkenler

Flutter'a başlayan birine ilk derslerde hep aynı şeyi söylerim: widget'lardan önce Dart'ın birkaç temel davranışını oturtmak, sonraki haftalarda saatler kazandırır. Bu yazıda bunlardan ikisini ele alıyoruz. Birincisi tip dönüşümleri, çünkü Flutter'da kullanıcıdan aldığınız her şey (bir TextField içeriği, bir JSON cevabı) çoğu zaman istediğiniz tipte gelmez. İkincisi static üyeler, çünkü "bu değer nesneye mi, sınıfa mı ait?" sorusunu doğru cevaplamak sınıf tasarımının ilk adımıdır.

Canlı Demo: Dart Tip Dönüşümleri

Tip dönüşümleri ve static kavramlarını interaktif olarak deneyin:

Dart Neden Otomatik Dönüştürmez?

Dart statik tipli bir dildir ve tipler arasında sessiz dönüşüm yapmaz. JavaScript'ten gelen öğrencilerin ilk şaşırdığı yer burasıdır:

void main() {
  // int sayi = "3";        // Derleme hatası: String, int'e atanamaz
  // print("Yaş: " + 25);   // Derleme hatası: String'e int eklenemez

  final yas = 25;
  print("Yaş: $yas");       // Doğrusu: string interpolation
}

Bu katılık bir engel değil, bir güvencedir: dönüşümü siz açıkça yazarsınız, dolayısıyla hatanın nerede çıkabileceğini de bilirsiniz.

String'den Sayıya: parse ve tryParse

Metni sayıya çevirmek için her sayı tipinin kendi parse metodu vardır:

void main() {
  final a = int.parse('42');          // 42
  final b = double.parse('3.14');     // 3.14
  final c = num.parse('7');           // 7 (int)
  final d = num.parse('7.5');         // 7.5 (double)

  final onaltilik = int.parse('ff', radix: 16); // 255
  final ikilik = int.parse('1010', radix: 2);   // 10

  print('$a $b $c $d $onaltilik $ikilik');
}

parse metodları, metin geçerli bir sayı değilse FormatException fırlatır. int.parse('3a') da int.parse('3.5') da hata verir; ikincisi öğrencileri en çok şaşırtanıdır, çünkü "3.5" bir sayıdır ama tam sayı değildir.

Güvenemediğiniz verilerde tryParse kullanın. Hata fırlatmak yerine null döndürür, siz de ?? ile varsayılan bir değer verirsiniz:

void main() {
  final sayi = int.tryParse('3a');        // null
  final guvenli = int.tryParse('3a') ?? 0; // 0
  print('$sayi $guvenli');
}

Hata yakalamayı tercih ettiğiniz durumlar da vardır; bunun için Dart'ta try-catch ve async/await yazısına bakabilirsiniz. Kural olarak: geçersiz girdi beklenen bir durumsa tryParse, gerçekten istisnai bir durumsa parse ve try-catch.

Kullanıcı Girdisini Dönüştürmek

Flutter'da TextField size her zaman String verir. Yaş hesaplayan bir fonksiyon şöyle yazılabilir:

String gelecekYasMesaji(String girdi) {
  final yas = int.tryParse(girdi.trim());

  if (yas == null) return 'Lütfen yaşınızı rakamla yazın.';
  if (yas < 0 || yas > 130) return 'Geçerli bir yaş girin.';

  // Bu satırda yas artık int? değil, int: null kontrolü tipi daralttı.
  return '5 yıl sonra ${yas + 5} yaşında olacaksınız.';
}

void main() {
  print(gelecekYasMesaji(' 25 '));  // 5 yıl sonra 30 yaşında olacaksınız.
  print(gelecekYasMesaji('yirmi')); // Lütfen yaşınızı rakamla yazın.
}

Burada üç alışkanlık var: girdiyi trim() ile temizlemek, tryParse ile dönüştürmek ve dönüşümden sonra değerin mantıklı aralıkta olup olmadığına bakmak. Sonuncusu sıkça unutulur; -5 geçerli bir tam sayıdır ama geçerli bir yaş değildir.

Son satırdaki yorum da önemli: yas == null kontrolünden sonra Dart, değişkenin int? değil int olduğunu kendisi anlar. Buna tip daraltma (type promotion) denir ve ! operatörüne ihtiyacı ortadan kaldırır.

Sayıdan String'e

Ters yön daha kolaydır, ama biçimlendirme genelde işin asıl kısmıdır:

void main() {
  final fiyat = 1249.5;

  print(fiyat.toString());                 // 1249.5
  print(fiyat.toStringAsFixed(2));         // 1249.50
  print('Toplam: ${fiyat.toStringAsFixed(2)} TL');
  print(7.toString().padLeft(2, '0'));     // 07 (saat/dakika gösterimi için)
}

toStringAsFixed bir String döndürür; sonucu tekrar hesaplamaya sokmayın. Para birimi ve binlik ayraç gibi yerel biçimler gerekiyorsa intl paketindeki NumberFormat sınıfı bu iş için yazılmıştır.

int ile double Arasında

Sayı literal'lerinde Dart esnektir: double x = 3; geçerlidir. Ama bir int değişkeni double değişkene doğrudan atanamaz:

void main() {
  int adet = 3;
  // double oran = adet;       // Derleme hatası
  double oran = adet.toDouble();

  const deger = 3.7;
  print(deger.toInt());   // 3  (ondalık kısmı atar)
  print(deger.round());   // 4  (en yakına yuvarlar)
  print(deger.floor());   // 3  (aşağı)
  print(deger.ceil());    // 4  (yukarı)

  print((-3.7).toInt());  // -3 (sıfıra doğru keser)
  print((-3.7).floor());  // -4
  print(-3.7.floor());    // -3 (dikkat: önce 3.7.floor(), sonra eksi işareti)
  print(oran);
}

Son satır, derslerde en sık gördüğüm yanılgılardan biridir: eksi işareti metot çağrısından sonra uygulanır. Negatif sayılarla çalışırken parantezi unutmayın.

Bölme işleminde de benzer bir tuzak var. / operatörü her zaman double döndürür:

void main() {
  final toplam = 7;
  final adet = 2;

  // int ortalama = toplam / adet;  // Derleme hatası: sonuç double
  print(toplam / adet);   // 3.5
  print(toplam ~/ adet);  // 3  (tam sayı bölmesi)
  print(toplam % adet);   // 1  (kalan)
}

as, is ve Pattern'ler: Dönüşüm Değil, Kontrol

Öğrencilerin çok sık karıştırdığı bir nokta: as bir dönüşüm operatörü değildir. Değeri başka bir tipe çevirmez, yalnızca derleyiciye "bu değer zaten şu tipte" dersiniz. Yanılırsanız çalışma zamanında TypeError alırsınız:

void main() {
  Object deger = '42';

  // final sayi = deger as int;   // TypeError: String, int değildir

  if (deger is String) {
    // Bu blokta deger otomatik olarak String'e daraldı.
    final sayi = int.parse(deger);
    print(sayi + 1); // 43
  }
}

Yani '42' metninden 42 sayısını as ile değil, int.parse ile elde edersiniz. is ise tipi kontrol eder ve blok içinde değişkeni o tipe daraltır.

Gerçek hayatta bu durum en çok JSON'da karşınıza çıkar. jsonDecode sonucu Map<String, dynamic> olur ve bir fiyat alanı bazen 20, bazen 19.9 olarak gelir. İlki int, ikincisi double olarak çözülür; json['fiyat'] as double yazan kod ilk durumda patlar. Dart 3'teki switch ifadeleri ve pattern'ler bunu temiz biçimde çözer:

double fiyatOku(Object? ham) {
  return switch (ham) {
    int n => n.toDouble(),
    double n => n,
    String s => double.tryParse(s) ?? 0.0,
    _ => 0.0,
  };
}

void main() {
  final json = <String, dynamic>{'ad': 'Kalem', 'fiyat': 20};
  print(fiyatOku(json['fiyat']));  // 20.0
  print(fiyatOku('19.9'));         // 19.9
  print(fiyatOku(null));           // 0.0
}

Daha kısa bir yol da var: sayı olduğundan emin olduğunuz bir alanda (json['fiyat'] as num).toDouble() hem int hem double gelen değeri kabul eder.

Dart 3.0 ile bool.parse ve bool.tryParse da eklendi. Varsayılan olarak yalnızca 'true' ve 'false' metinlerini tanırlar; büyük/küçük harf duyarsız çalışmak için caseSensitive: false verilir.

Static Üyeler: Nesneye Değil, Sınıfa Ait

Normal bir alan (instance field) her nesnede ayrı bir kopya olarak yaşar. static bir alan ise sınıfa aittir ve kaç nesne oluşturursanız oluşturun tek bir kopyası vardır.

Sayac sınıfının static toplam alanı tek kutudur ve üç nesne onu paylaşır, deger alanı ise her nesnede ayrıdır

Diyagramdaki durumu koda dökelim:

class Sayac {
  static int toplam = 0; // Sınıfa ait, tek kopya
  int deger = 0;         // Her nesnede ayrı

  void artir() {
    deger++;
    toplam++; // Sınıfın içinden önek yazmadan erişilebilir
  }
}

void main() {
  final s1 = Sayac();
  final s2 = Sayac();

  s1.artir();
  s1.artir();
  s2.artir();

  print(s1.deger);     // 2
  print(s2.deger);     // 1
  print(Sayac.toplam); // 3
  // print(s1.toplam); // Derleme hatası: static üyeye nesne üzerinden erişilmez
}

Sınıfın dışından static bir üyeye yalnızca sınıf adıyla (Sayac.toplam) ulaşırsınız. Java'dan gelenler nesne üzerinden erişmeye alışkın olabilir; Dart buna izin vermez.

static const, static final ve static değişken

Üçü de sınıfa aittir, farkları değerin ne zaman belirlendiği ve değişip değişemeyeceğidir:

class Ayarlar {
  static const maksKullaniciAdi = 20;                  // Derleme zamanında bilinir
  static const istekZamanAsimi = Duration(seconds: 10);
  static final baslangic = DateTime.now();             // İlk erişimde bir kez hesaplanır
  static int acilisSayisi = 0;                         // Değiştirilebilir
}
  • static const: Değer derleme zamanında bilinmelidir. Sabit limitler, süreler, anahtar isimleri için doğru tercihtir.
  • static final: Değer çalışma zamanında, alana ilk erişildiği anda bir kez hesaplanır ve sonra değişmez. Dart'ta static alanlar zaten kullanılana kadar başlatılmaz.
  • static int gibi değiştirilebilir alanlar: Tüm uygulamanın paylaştığı bir durum yaratır. Küçük örneklerde zararsızdır, ama Flutter uygulamasında ekranın göstereceği veriyi burada tutmak, arayüzün değişiklikten haberdar olmaması gibi sorunlara yol açar. O iş için Provider gibi state management araçları vardır.

Bu yazının eski sürümlerinde static late int deger; gibi bir örnek vardı. late burada derleyiciye "okumadan önce mutlaka atayacağım" sözü verir; söz tutulmazsa LateInitializationError alırsınız. Başlangıç değeri verebiliyorsanız (static int deger = 0;) late kullanmanıza gerek yoktur.

Static Metotlar

Static metotlar da sınıfa aittir ve bir nesne üzerinde çalışmazlar. Bu yüzden içlerinde this yoktur ve normal alanlara erişemezler:

class Sicaklik {
  final double celsius;
  const Sicaklik(this.celsius);

  static double fahrenheitten(double f) => (f - 32) * 5 / 9;

  // static void yazdir() => print(celsius); // Hata: static metotta this yok
}

void main() {
  print(Sicaklik.fahrenheitten(212)); // 100.0
}

Her Şeyi Static Bir Sınıfa Koymayın

Java veya C# geçmişi olan öğrenciler yardımcı fonksiyonları çoğu zaman şöyle toplar:

class Hesaplama {
  static double topla(double a, double b) => a + b;
  static double cikar(double a, double b) => a - b;
}

Dart'ta bu gereksizdir, çünkü fonksiyonlar bir sınıfın içinde olmak zorunda değildir. Effective Dart rehberi de yalnızca static üye içeren sınıflardan kaçınmayı önerir. Aynı işi üst seviye (top-level) fonksiyonlar daha sade yapar:

double topla(double a, double b) => a + b;
double cikar(double a, double b) => a - b;

void main() {
  print(topla(5, 3)); // 8.0
}

Rehberdeki istisna, birbiriyle ilişkili sabitleri gruplamaktır; yukarıdaki Ayarlar sınıfı bu tür bir kullanım. Birbirine bağlı sabit bir değer kümesi ise çoğu zaman bir enum olarak daha iyi ifade edilir.

Sık Yapılan Hatalar

  • Kullanıcı girdisini trim() etmeden ve tryParse yerine parse ile dönüştürmek.
  • as int yazarak bir metni sayıya çevirebileceğini sanmak.
  • / sonucunu int değişkene atamaya çalışmak; tam sayı bölmesi için ~/ kullanılır.
  • Negatif sayılarda parantezi unutmak: -3.7.floor() ile (-3.7).floor() farklı sonuç verir.
  • JSON'dan gelen sayıyı as double ile okumak; num üzerinden toDouble() daha güvenlidir.
  • Ekranda gösterilen durumu değiştirilebilir static alanlarda tutmak.

Sonraki Adım

Tipler ve static üyeler oturduktan sonra doğal devam, sabit değer kümelerini modelleyen enum'lar ve composition konusudur. Dart temeli tamamlandığında Flutter'ın widget ağacı ve layout mantığı ile arayüz tarafına geçebilirsiniz.

Kaynaklar


Benimle iletişime geçin:

Sık Sorulan Sorular

int.parse ile int.tryParse arasındaki fark nedir?

int.parse geçersiz bir metinle karşılaşınca FormatException fırlatır. int.tryParse ise hata fırlatmak yerine null döndürür; kullanıcı girdisi gibi güvenemediğiniz verilerde bu yüzden daha pratiktir.

as ile bir String'i int'e çevirebilir miyim?

Hayır. as bir dönüşüm yapmaz, yalnızca değerin zaten o tipte olduğunu iddia eder. '42' as int çalışma zamanında TypeError fırlatır; metinden sayı elde etmek için int.parse veya int.tryParse kullanılır.

Static bir değişkene nesne üzerinden erişebilir miyim?

Hayır. Dart'ta static üyelere sınıf dışından yalnızca sınıf adıyla (Sayac.toplam) erişilir; nesne üzerinden erişmeye çalışırsanız kod derlenmez. Sınıfın kendi metotları içinde ise önek yazmadan kullanılabilir.

static final ile static const arasında ne fark var?

const değerin derleme zamanında bilinmesini ister. static final ise çalışma zamanında, ilk erişildiği anda bir kez hesaplanır ve sonra değişmez.

Yorumlar