Dart Tipler | Generics— Jenerikler #9

MÜŞERREF SELÇUK ÖZDEMIR
3 min readJan 3, 2024

--

Temel dizi türü olan List için dikkat edersiniz, türün aslında List<E> olduğunu görürsünüz. <…> notasyonu List’i jenerik (veya parametrelendirilmiş) bir tür olarak işaretler — resmi tür parametreleri olan bir tür. Geleneksel olarak, çoğu tür değişkeninin E, T, S, K ve V gibi tek harfli adları vardır.

Neden jenerik kullanmalı?

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

  • Genel tiplerin doğru şekilde belirtilmesi daha iyi kod üretilmesini sağlar.
  • Kod tekrarını azaltmak için jenerikleri kullanabilirsiniz.

Bir listenin yalnızca string içermesini istiyorsanız, listeyi List<String> olarak bildirebilirsiniz (bunu “string listesi” olarak okuyun). Bu şekilde siz, programcı arkadaşlarınız ve araçlarınız, listeye string olmayan bir atamanın muhtemelen bir hata olduğunu tespit edebilir. İşte bir örnek:

### XXXX statik analiz: hata/uyarı

var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
names.add(42); // Hata

Jeneriklerin kullanılmasının bir başka nedeni de kod tekrarını azaltmaktır. Jenerikler, statik analizden yararlanmaya devam ederken birçok tür arasında tek bir arayüzü ve uygulamayı paylaşmanıza olanak tanır. Örneğin, bir nesneyi önbelleğe almak için bir arayüz oluşturduğunuzu varsayalım:

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

Bu arayüzün dizeye özgü bir versiyonunu istediğinizi keşfedersiniz, bu nedenle başka bir arayüz oluşturursunuz:

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

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

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

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

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

Koleksiyon değişmezlerini kullanma

Liste, küme ve harita değişmezleri parametrelendirilebilir. Parametrelendirilmiş değişmezler daha önce gördüğünüz değişmezler gibidir, ancak açılış parantezinden önce <type> (listeler ve kümeler için) veya <keyType, valueType> (eşlemeler için) eklersiniz. İşte tiplendirilmiş değişmezlerin kullanımına bir örnek:

var names = <String>['Seth', 'Kathy', 'Lars'];
var uniqueNames = <String>{'Seth', 'Kathy', 'Lars'};
var pages = <String, String>{
'index.html': 'Homepage',
'robots.txt': 'Hints for web robots',
'humans.txt': 'We are people, not machines'
};

Parametrelendirilmiş türleri kurucularla kullanma

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

var nameSet = Set<String>.from(names);

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

var views = Map<int, View>();

Genel koleksiyonlar ve içerdikleri türler

Dart jenerik tipleri reified’dır, yani tip bilgilerini çalışma zamanında taşırlar. Örneğin, bir koleksiyonun türünü test edebilirsiniz:

var names = <String>[];
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true

Not: Bunun aksine, Java’daki jenerikler silme özelliğini kullanır; bu da jenerik tip parametrelerinin çalışma zamanında kaldırıldığı anlamına gelir. Java’da bir nesnenin Liste olup olmadığını test edebilirsiniz, ancak Liste<String> olup olmadığını test edemezsiniz.

Parametrelendirilmiş türü kısıtlama

Genel bir türü uygularken, argüman olarak sağlanabilecek türleri sınırlamak isteyebilirsiniz, böylece argüman belirli bir türün alt türü olmalıdır. Bunu extends kullanarak yapabilirsiniz.

Yaygın bir kullanım durumu, bir türü Object’in bir alt türü yaparak (varsayılan Object? yerine) boş bırakılamaz olmasını sağlamaktır.

class Foo<T extends Object> {
// T için Foo'ya sağlanan herhangi bir tür nullable olmamalıdır.
}

Extends öğesini Object dışında başka türlerle de kullanabilirsiniz. Aşağıda, SomeBaseClass’ın üyelerinin T türündeki nesneler üzerinde çağrılabilmesi için SomeBaseClass’ın genişletilmesine ilişkin bir örnek verilmiştir:

class Foo<T extends SomeBaseClass> {
// Uygulama burada...
String toString() => "Instance of 'Foo<$T>'";
}

class Extender extends SomeBaseClass {...}

SomeBaseClass veya alt türlerinden herhangi birini genel argüman olarak kullanmakta sorun yoktur:

var someBaseClassFoo = Foo<SomeBaseClass>();
var extenderFoo = Foo<Extender>();

Genel bir argüman belirtmemek de uygundur:

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

SomeBaseClass dışındaki herhangi bir türün belirtilmesi hataya neden olur:

### XXXX statik analiz: hata/uyarı
var foo = Foo<Object>();

Genel yöntemleri kullanma

Yöntemler ve işlevler ayrıca tür argümanlarına da izin verir:

T first<T>(List<T> ts) {
// Bazı ilk işleri veya hata kontrollerini yapın, sonra...
T tmp = ts[0];
// Bazı ek kontroller veya işlemler yapın...
return tmp;
}

Burada ilk olarak genel tür parametresi (<T>), T tür argümanını birkaç yerde kullanmanıza olanak tanır:

  • Fonksiyonun dönüş tipinde (T).
  • Bir bağımsız değişkenin türünde (List<T>).
  • Yerel bir değişkenin türünde (T tmp).

--

--