Jenerikler (Generics)

Eğer temel dizi tipi olan List'in API belgelerine göz atarsanız, tipin aslında List<E> olduğunu göreceksiniz. <...> gösterimi, List'in bir jenerik (veya parametreli) tip olduğunu belirtir - yani, formal tip parametreleri olan bir tiptir. Genellikle, çoğu tip değişkeni tek harfli isimlere sahiptir, örneğin E, T, S, K ve V gibi.

Jenerik Türleri Neden Kullanmalıyız?

Jenerikler genellikle tip güvenliği için gereklidir, ancak kodunuzun çalışmasına izin vermenin ötesinde daha fazla faydası vardır:

  • Doğru jenerik tipleri belirlemek, daha iyi oluşturulmuş kodlara neden olur.

  • Jenerikleri kullanarak kod tekrarını azaltabilirsiniz.

Eğer bir listenin sadece dizeleri içermesini istiyorsanız, onu List<String> olarak bildirebilirsiniz (bu, "dize listesi" olarak okunur). Bu şekilde, bir dize olmayan bir değeri listeye atamanın muhtemelen bir hata olduğunu, sizin ve diğer programcıların araçlarının tespit edebileceği bir şekilde ifade etmiş olursunuz. İşte bir örnek:

// Statik analiz: hata
var isimler = <String>[];
isimler.addAll(['Seth', 'Kathy', 'Lars']);
isimler.add(42); // Hata

Jenerikleri kullanmanın bir başka nedeni de kod tekrarını azaltmaktır. Jenerikler, birçok tür arasında tek bir arayüzü ve uygulamayı paylaşmanıza olanak tanırken hala statik analiz avantajlarından faydalanmanıza olanak tanır. Örneğin, bir nesneyi önbelleğe alma arayüzü oluşturduğunuzu varsayalım:

abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}

Bu arayüzün string özel bir versiyonunu istediğinizi fark ederseniz, başka bir arayüz oluşturabilirsiniz:

abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}

Daha sonra, bu arayüzün bir sayı özel versiyonunu istediğinize karar verirseniz... Fikri anladınız.

Jenerik tipler, tüm bu arayüzleri oluşturmanın zahmetinden sizi kurtarabilir. Bunun yerine, bir tür parametre alan tek bir arayüz oluşturabilirsiniz:

abstract class Cache<T> {
  T getByKey(String key);
  void setByKey(String key, T value);
}

Bu kodda, T stand-in türdür. Bu, geliştiricinin daha sonra tanımlayacağı bir tür olarak düşünebileceğiniz bir yer tutucudur.

Toplama Litaralleri Kullanma

List, Set ve Map literalleri parametreli olabilir. Parametreli literaller, zaten gördüğünüz literaller gibi, ancak açılış parantezinin önüne <type> (listeler ve setler için) veya <keyType, valueType> (Maps) için eklersiniz. İşte tip belirtilmiş literalleri kullanmanın bir örneği:

var isimler = <String>['Bayram', 'Hidayet', 'Mustafa', 'Yunus', 'Hakan'];
var benzersizIsimler = <String>{'Bayram', 'Hidayet', 'Mustafa', 'Yunus', 'Hakan'};
var sayfalar = <String, String>{
  'index.html': 'Ana Sayfa',
  'robots.txt': 'Web robotları için ipuçları',
  'insanlar.txt': 'Biz insanlarız, makine değiliz'
};

Kurucularla (Constructors) Parametreli Türleri Kullanma

Bir veya daha fazla tür belirtmek istediğinizde bir constructor kullanırken, türleri sınıf adından hemen sonra açıkhız (<...>) içine koyun. Örneğin:

var isimSeti = Set<String>.from(isimler);

Aşağıdaki kod, tamsayı anahtarları olan ve Gorunum türünde değerlere sahip bir harita oluşturur:

var gorunumler = Map<int, Gorunum>();

Jenerik Koleksiyonlar ve İçerdikleri Türler

Dart jenerik tipleri, tür bilgilerini çalışma zamanında taşıdıkları anlamına gelir. Örneğin, bir koleksiyonun türünü test edebilirsiniz:

var isimler = <String>[];
isimler.addAll(['Bayram', 'Hidayet', 'Mustafa', 'Yunus', 'Hakan']);
print(isimler is List<String>); // true

Not: Buna karşılık, Java'da jenerik tipler silinir, bu da jenerik tür parametrelerinin çalışma zamanında kaldırıldığı anlamına gelir. Java'da bir nesnenin bir List olup olmadığını test edebilirsiniz, ancak bir List<String> olup olmadığını test edemezsiniz.

Parametreli Türü Sınırlama

Generik bir türü uygularken, belirli bir türün alt türü olan argümanları sağlamak isteyebilirsiniz, böylece argümanın belirli bir türün alt türü olması gerekmelidir. Bu, extends kullanarak yapılabilir.

Bir türün null olmayan bir alt tür olduğundan emin olmak için genellikle extends kullanılır (varsayılan olarak Object yerine). Örneğin:

class Foo<T extends Object> {
  // Foo için T'ye sağlanan herhangi bir tür, null olmayan bir alt tür olmalıdır.
}

Extends ile başka türler de kullanabilirsiniz. İşte TemelSinif'i genişleterek TemelSinif'in üyelerinin T türündeki nesnelerde çağrılmasına izin veren bir örnek:

class Foo<T extends TemelSinif> {
  // Uygulama buraya geliyor...
  String toString() => "Foo<$T>'nin örneği";
}

class Extender extends TemelSinif {...}

TemelSinif veya alt türlerini genişletmek için şu şekilde kullanabilirsiniz:

var temelSinifFoo = Foo<TemelSinif>();
var extenderFoo = Foo<Extender>();

Ayrıca hiçbir jenerik argüman belirtmek de mümkündür:

var foo = Foo();
print(foo); // 'Foo<TemelSinif>' örneği

Herhangi bir TemelSinif olmayan tür belirtmek bir hataya yol açar:

// Statik analiz: hata
var foo = Foo<Object>();

Jenerik Metotları Kullanma

Metodlar ve fonksiyonlar da tür argümanlarına izin verir:

T ilk<T>(List<T> ts) {
  // İlk işlemleri veya hata kontrolünü yapın, sonra...
  T tmp = ts[0];
  // Ek kontrol veya işlemleri yapın...
  return tmp;
}

Buradaki jenerik tür parametresi ilk (<T>), tür argümanını T'yi birkaç yerde kullanmanıza olanak tanır:

  • Fonksiyonun dönüş türünde (T).

  • Bir argümanın türünde (List<T>).

  • Yerel bir değişkenin türünde (T tmp).

Last updated