Dart'ta Tip Dönüşümleri ve Static Değişkenler
Son güncelleme:
6 dk okuma

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.
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 intgibi 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 vetryParseyerineparseile dönüştürmek. as intyazarak bir metni sayıya çevirebileceğini sanmak./sonucunuintdeğ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 doubleile okumak;numüzerindentoDouble()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.
İlgili Yazılar
Flutter'da Kamerayı Kullanıcıya Bırakmak: Yön Zorlamadan Video Çekmek
Flutter camera paketinde ters dönen önizleme, kayıt başlarken donma ve kilitlenen kamera: Recapday'de çözdüğüm üç hatanın kodunu anlatıyorum.
Dart'ta Interface ve Implements: Sınıflara Ekstra Özellik
Dart'ta implements ile sözleşme kurmak, Dart 3'ün interface class ve abstract interface class yapıları, mixin ile with farkı ve test edilebilir kod örneği.
Dart'ta Kalıtım (Inheritance) ve Polymorphism Rehberi
Dart'ta extends, super ve @override ile kalıtım, polymorphism, abstract sınıflar, güvenli tip kontrolü ve Dart 3'ün sealed, final, base sınıf niteleyicileri.