---
title: 'Genellikler ve Varyans'
source: 'https://academia.sh/tr/kurslar/kotlin/genellikler-ve-varyans'
course: 'Kotlin Dili'
language: tr
updated: '2026-10-21T05:02:40+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Genel bir sınıf ve üst sınırlı genel bir fonksiyon yazmak'
    - '`out` ve `in` ile bir tip parametresinin kovaryans ve kontravaryansını belirtmek'
    - 'Değiştirilebilir bir listenin invaryant, salt okunur bir listenin kovaryant olduğunu ölçmek'
    - 'Tip silmeyi ve genel bir veri sınıfında eşitliğin değere bakmasını açıklamak'
---

# Genellikler ve Varyans

Genel sınıf, genel fonksiyon, üst sınır kısıtı, `out` ve `in` varyansı ile tip silmeyi ölçer; çalışma zamanında tip parametresinin silindiğini, eşitliğin değere baktığını gösterir.

Önceki derste bir uzantının statik tipe göre seçildiğini gördük: karar değişkenin bildirilmiş tipinde veriliyordu. Bu derste o bildirilmiş tipin içine bir parametre koyuyoruz. `List<Int>` ile `List<String>` iki ayrı tip gibi görünür; soru, çalışma zamanında bu ikisinin gerçekten ayrı olup olmadığı ve bir `List<Int>`'in bir `List<Any>` yerine geçip geçemeyeceği.

**Genellik (generic)**, bir sınıfı ya da fonksiyonu, tipi sonradan belirlenen bir **tip parametresi** (type parameter) ile yazma yoludur: `class Holder<T>`. Parametre, çağrı sırasında somut bir tiple doldurulur. **Kısıt (constraint)**, tip parametresinin hangi tiplerle doldurulabileceğini sınırlayan koşuldur: `T : Comparable<T>` yalnız karşılaştırılabilir tipleri kabul eder.

Ölçü bu kursun her dersindeki gibi: bu iki değeri eşit yapan ne? M08/K02'de bir kararı hangi tipin verdiğine bakmıştık; burada soru farklı, tip parametresinin eşitliğe etki edip etmediği ölçülüyor.

## Ölçüm Düzeneği

`Criterion` her derste ilk blokta aynı biçimde durur. Her gözlem üç değer taşır: `first`, `twin` (aynı tarifle ayrıca kurulan değer) ve `other` (yalnız bir bakımdan farklı değer). Kural sonucu görür, gerekçeyi görmez.

```kotlin
// Criterion.kt — iki degeri esit yapan olcutu siniflandirir
class Criterion(val label: String, first: Any?, twin: Any?, other: Any?) {
    val sameObject = first === twin
    val equalTwin = first == twin
    val equalOther = first == other

    val name: String = when {
        sameObject -> "tekil"
        !equalTwin -> "kimlik"
        !equalOther -> "icerik"
        else -> "kismi-icerik"
    }

    fun report() {
        println("$label: ayni-nesne=$sameObject, ikiz=$equalTwin, farkli=$equalOther -> $name")
    }
}

fun summarize(checks: List<Criterion>) {
    for (name in listOf("tekil", "kimlik", "icerik", "kismi-icerik")) {
        println("$name: ${checks.count { it.name == name }}")
    }
}
```

**TN35.** Bu derste `Criterion`'ın parametreleri `Any?` olduğu için, tip parametreli değerler de ona geçerken sıradan nesneler olarak görünür; ölçüm statik tipi değil çalışma zamanındaki nesneyi görür.

## Genel Sınıf, Fonksiyon ve Kısıt

Aşağıdaki blok üç genel tanım yazar. `Holder<T>` herhangi bir tipten bir değer tutar. `largest`, `T : Comparable<T>` kısıtıyla yalnız karşılaştırılabilir tiplerde çalışır. `sumInts` iki kısıtı birlikte ister: `where` bölümü, tek bir parametreye birden fazla kısıt koymanın yoludur.

```kotlin
// Main.kt — genel sinif, ust sinir kisiti ve where
class Holder<T>(val item: T) {
    fun describe(): String = "kutu:" + item
}

fun <T : Comparable<T>> largest(first: T, second: T): T =
    if (first >= second) first else second

fun <T> sumInts(first: T, second: T): Int where T : Number, T : Comparable<T> =
    first.toInt() + second.toInt()

fun main() {
    println(Holder(7).describe())
    println(Holder("ada").describe())
    println(largest(3, 9))
    println(largest("elma", "armut"))
    println(sumInts(2, 3))
}
```

```
kutu:7
kutu:ada
9
elma
5
```

Aynı `Holder` bir `Int` ve bir `String` ile kullanıldı; tip parametresi her çağrıda çıkarıldı; yazıya hiç tip eklenmedi. `largest` hem sayılarla hem dizgilerle çalıştı, çünkü ikisi de `Comparable`. `sumInts` yalnız hem `Number` hem `Comparable` olan tiplerde derlenir; `Int` ikisini de sağlıyor.

**TN36.** Kısıtı sağlamayan bir tipin `largest`'e verilince derlenmediği ayrıca ölçülmedi; yalnız kısıtı sağlayan çağrılar yazıldı.

## Çalışma Zamanında Tip Parametresi Yok

Bir genel sınıfın tip parametresi derleme zamanında denetlenir ve çalışma zamanına taşınmaz. Buna **tip silme (type erasure)** denir. Sonuç, aynı genel tipin farklı parametrelerle kurulmuş iki değerinin çalışma zamanında aynı sınıftan olmasıdır. Daha çarpıcısı, bazı standart fonksiyonların tek bir ortak nesne döndürmesidir: boş bir liste her eleman tipi için aynı nesne olabilir. Bunu ölçüyoruz.

Üç küçük aracı da yan yana koyuyoruz. **Yıldız yansıtması (star projection)**, `List<*>` biçiminde, tip parametresini bilmeden okumaya izin verir. `inline` bir fonksiyonda `reified` işaretli parametre ise çalışma zamanında okunabilir hâle gelir; silmenin tek istisnası budur, çünkü derleyici o fonksiyonun gövdesini çağrı noktasına açar.

```kotlin
// Main.kt — tip silme ve reified
class Holder<T>(val item: T)

fun countAll(values: List<*>): Int = values.size

inline fun <reified T> nameOf(): String = T::class.simpleName ?: "adsiz"

fun main() {
    val ints = Holder(1)
    val texts = Holder("ada")
    println(ints::class == texts::class)
    println(countAll(listOf(1, 2, 3)))
    println(countAll(listOf("a", "b")))
    println(nameOf<Int>())
    println(nameOf<String>())
    val checks = listOf(
        Criterion("bos-listeler", emptyList<Int>(), emptyList<String>(), listOf(1)),
        Criterion("kutular", ints, texts, Holder(2)),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
true
3
2
Int
String
bos-listeler: ayni-nesne=true, ikiz=true, farkli=false -> tekil
kutular: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
tekil: 1
kimlik: 1
icerik: 0
kismi-icerik: 0
```

`ints` ve `texts` farklı tip parametreleriyle kurulmuştu ama sınıfları aynı çıktı. `countAll` iki listenin elemanlarını bilmeden boyutlarını okudu. `nameOf` ise silmenin sınırını gösterdi: `reified` ile tip parametresi çalışma zamanında okunabildi ve `Int`, `String` adları basıldı.

Ölçüt tablosunun ilk satırı `tekil`: `emptyList<Int>()` ile `emptyList<String>()` aynı nesneye çözüldü. İki ayrı tip parametresiyle çağrılmış olmalarına rağmen elde tek bir boş liste vardı; çünkü eleman tipi nesnede saklanmıyor. İkinci satır `kimlik`: `Holder` `equals`'ü yeniden tanımlamayan sıradan bir sınıf. Tip parametresi eşitliği bir yönde değiştirmedi.

## Varyans: out ve in

Tip parametresi nesnenin içinde silinse de, derleyici onu atamalarda denetler. **Varyans (variance)**, `Kutu<Alt>` değerinin `Kutu<Üst>` yerine geçip geçemeyeceğini söyler. Üç hâli var. **Kovaryans (covariance)**, alt tipli kutunun üst tipli kutu yerine geçmesidir ve `out` ile işaretlenir: parametre yalnız çıktı konumunda, yani dönüş tipinde kullanılabilir. **Kontravaryans (contravariance)**, tersidir ve `in` ile işaretlenir: parametre yalnız girdi konumunda, yani fonksiyon parametresinde kullanılabilir. **İnvaryans (invariance)**, hiçbirinin geçmediği hâldir ve işaretsiz tip parametresinin varsayılanıdır.

```kotlin
// Main.kt — out ve in ile atama
class Source<out T>(private val value: T) {
    fun get(): T = value
}

class Sink<in T> {
    fun accept(item: T): String = "kabul:" + item
}

fun main() {
    val ints: List<Int> = listOf(1, 2)
    val anys: List<Any> = ints
    println(anys.size)
    val source: Source<Any> = Source<String>("ada")
    println(source.get())
    val sink: Sink<String> = Sink<Any>()
    println(sink.accept("ali"))
    Criterion("kovaryant-atama", ints, anys, listOf<Any>("x")).report()
}
```

```
2
ada
kabul:ali
kovaryant-atama: ayni-nesne=true, ikiz=true, farkli=false -> tekil
```

`List<Int>` bir `List<Any>` değişkenine atandı, çünkü `List` salt okunurdur ve `out T` ile bildirilmiştir; o atamada yeni bir nesne kurulmadı, aynı nesne iki ayrı tiple okundu ve ölçüt `tekil` çıktı. `Source<String>` de `Source<Any>` yerine geçti. `Sink` ise ters yönde çalıştı: bir `Sink<Any>`, `String` kabul eden bir `Sink<String>` yerine geçti, çünkü her `Any` kabul eden bir alıcı `String`'i de kabul eder.

Değiştirilebilir liste ise kovaryant olamaz. Önce bunu doğruladık: aşağıdaki parça Kotlin 2.4.21'de derlenmedi, bu yüzden çıktısız kalıyor.

```kotlin
val numbers: MutableList<Int> = mutableListOf(1)
val anys: MutableList<Any> = numbers
```

**TN37.** Reddin nedeni, `MutableList<Any>`'a bir `String` eklenebilseydi `MutableList<Int>`'in bozulacağıdır; bu akıl yürütme dilin tanımından geliyor ve burada ayrı bir blokla ölçülmedi. Yalnız atamanın derlenmediği doğrulandı.

## Silmenin Sınırları

Silme, tip parametresinin çalışma zamanında okunamaması demektir. Bunun üç sonucu var. Birincisi, bir değer `List<*>` mi diye sorulabilir ama `List<String>` mi diye sorulamaz; ikincisi, `List<String>`'e denetimsiz bir dönüşüm yapılabilir ve hata ancak bir eleman okunduğunda çıkar; üçüncüsü, varyans yalnız sınıf bildiriminde değil, kullanım yerinde de belirtilebilir. Aşağıdaki blok üçünü ölçer.

`numbers` aslında `Int` içeren bir listedir ama `Any` tipiyle tutulur. `texts` o listenin `List<String>` olarak denetimsiz görünümüdür. `sink` ise `MutableList<in Int>` olarak bildirilir: tip parametresi kullanım yerinde kontravaryant yapılmıştır.

```kotlin
// Main.kt — is List<*>, denetimsiz donusum ve kullanim yeri varyansi
fun main() {
    val numbers: Any = listOf(1, 2, 3)
    println(numbers is List<*>)
    @Suppress("UNCHECKED_CAST")
    val texts = numbers as List<String>
    println(texts.size)
    val outcome = try {
        val first: String = texts[0]
        "okundu " + first
    } catch (e: ClassCastException) {
        "sinif-hatasi"
    }
    println(outcome)
    val sink: MutableList<in Int> = mutableListOf<Any>("x")
    sink.add(5)
    println(sink.size)
    println(sink[1])
    Criterion("belirsiz-liste", numbers, texts, listOf("a")).report()
}
```

```
true
3
sinif-hatasi
2
5
belirsiz-liste: ayni-nesne=true, ikiz=true, farkli=false -> tekil
```

`numbers is List<*>` doğru çıktı: yıldız yansıtması, tip parametresini bilmeden yapılan sorudur. `texts.size` `3` yazdı. Dönüşüm denetimsiz olduğundan, boyutu okumak elemanlara dokunmadığı için patlamadı. Hata, ilk eleman `String` olarak okunduğunda çıktı: `sinif-hatasi` satırı, `ClassCastException`'ın yakalandığını gösteriyor. Derleyici `texts`'in `List<String>` olduğuna inandı, çalışma zamanı ise elemanın gerçekte `Int` olduğunu ancak okuma anında gördü.

Bu, silmenin bedelidir. Tip parametresi çalışma zamanında nesnede olmadığı için dönüşümün doğruluğu orada denetlenemez; denetim eleman okunana kadar ertelenir. Ölçüt satırı `tekil`: `numbers` ile `texts` aynı nesnedir. Dönüşüm yeni bir liste kurmadı, aynı nesneye yeni bir tip etiketi verdi.

`sink` kullanım yeri varyansını gösterdi. `MutableList<in Int>`, içine `Int` eklenebilen ama okunduğunda yalnız `Any?` veren bir görünümdür. `sink.add(5)` sonrasında boyut `2` oldu ve `sink[1]` `5` yazdı; ilk eleman `"x"` olarak listede kalmıştı. Sınıf bildirimindeki `out`/`in` ile aynı kural, tek bir değişken için geçerli kılındı.

**TN38.** Denetimsiz dönüşümün derleyici uyarısı verdiği iddiasını yazmıyoruz; uyarı bu derste denetlenmiyor. `@Suppress` işaretini yalnız dönüşümü açıkça kabul ettiğimizi göstermek için yazdık.



## Ölçümlerden Çıkan Silme Kuralı

Bu dersin ölçümleri tek bir olguya dayanır: tip parametresi çalışma zamanında nesnede yoktur. Bundan çıkan üç sonuç ölçüldü. Boş iki liste, tip parametreleri farklı olsa da aynı nesne çıktı. `is List<*>` sorusu cevaplandı, ama `List<String>` sorusu sorulamadı. Denetimsiz dönüşüm de hemen patlamadı: `texts.size` `3` yazdı ve hata ancak eleman okunduğunda çıktı.

Sık yapılan hata, dönüşümün güvenli olduğunu varsaymaktır. `as List<String>` yazan kişi, derleyicinin listeyi artık `List<String>` saydığını görür ve sonraki kodda her şey düzgün görünür. Oysa nesnenin içeriği değişmedi; yalnız onu okuyan koda verilen söz değişti. Söz tutulmadığında hata, dönüşümün yapıldığı satırda değil, elemanın okunduğu satırda çıkar. Bu iki satır birbirinden uzaksa hatanın kaynağını bulmak zorlaşır. Bu yüzden dışarıdan gelen bir listeyi dönüştürmek yerine, elemanları tek tek denetleyerek yeni bir liste kurmak daha sağlamdır; bu ders onu ölçmedi, yalnız dönüşümün sınırını gösterdi.

Varyans iki ayrı yerde belirtilebilir. Sınıf bildiriminde `out` ya da `in` yazılırsa kural sınıfın bütün kullanımlarında geçerlidir; kullanım yerinde `MutableList<in Int>` yazılırsa kural yalnız o değişken için geçerlidir. Sonuncusunun bedeli okuma yönündedir: `sink[1]` `5` yazdı, ama değişkenin tipi `Any?` olduğu için okunan değerle bir işlem yapabilmek için önce tipini denetlemek gerekir.

Kısıtın ve `reified`'ın rolü de silme ile birlikte okunur. Kısıt, derleme zamanında tip parametresinin neye benzemesi gerektiğini söyler; çalışma zamanında hiçbir şey denetlemez. `reified` ise yalnız satır içi (inline) fonksiyonlarda silmeyi aşar, çünkü derleyici tip bilgisini çağrı yerine yazar. Bu iki araç silmenin sınırlarını daraltır, ama kaldırmaz.

## Ölçmeden Önce Tahmin Etmek

Silme konusunda tahmin, "çalışma zamanında hangi bilgi var" sorusuna iner. Bir nesnenin sınıfı vardır; tip parametresi yoktur. Bu yüzden `numbers is List<*>` doğru, `texts.size` `3` ve ilk eleman `String` olarak okunduğunda sınıf hatası beklenir. Üç sonucun üçü de ölçümle tutuyor. Tutmayan tahmin, "dönüşüm satırı patlar" tahminidir: dönüşüm sırasında nesneye bakan bir denetim yoktur.

Kullanım yeri varyansında tahmin yönlere ayrılır. `MutableList<in Int>` değişkenine yazma yönünde `Int` kabul edilir; okuma yönünde elemanın tipi `Any?` olur. Boyutun `2` olacağını söylemek için listede başlangıçta bir eleman olduğunu ve bir eleman eklendiğini bilmek yeterlidir. Okunan `5` ise, eklenen değerin konumunu bilmeye bağlıdır.

Ölçüt satırı için tahmin basittir. `numbers` ile `texts` aynı listeye bakar, dönüşüm yeni nesne kurmaz; bu yüzden `ayni-nesne` doğru olur ve ölçüt `tekil` kalır. Bu satırları tekil yapan şey, tip bilgisinin nesnede olmamasıdır: bilgi nesneye değil, kodu okuyan derleyiciye aittir.

Bu tahminlerin hiçbiri bir alışkanlık değildir; hepsi tek bir cümleden türer. Çalışma zamanı nesnenin sınıfını bilir, tip parametresini bilmez. Bir ifadenin sonucu bu cümleden çıkarılamıyorsa, ifade derleme zamanında çözülen bir şeyi, örneğin atamanın geçerliliğini sorguluyordur. Derleme zamanı sorusu ile çalışma zamanı sorusunu ayırmak, genellikler konusunda yapılan hataların büyük kısmını önler.

## Sınırlayıcı Ölçüm

Tip parametresi çalışma zamanında silindiğine göre, genel bir tipin iki değerini eşit yapan şey tip parametresi olamaz: eleman değerleri olmalı. Aşağıdaki blok bunu iki yolla ölçer. Genel bir veri sınıfı olan `Wrapper`'a `1` ve `1L` verilir, yani `Int` ve `Long`. İkinci satır `Any` tipli iki listeyi karşılaştırır.

```kotlin
// Main.kt — genel veri sinifinda esitlik degere bakar
data class Wrapper<T>(val value: T)

fun main() {
    val checks = listOf(
        Criterion("sarmalayici", Wrapper(1), Wrapper(1), Wrapper(1L)),
        Criterion("liste-1-1L", listOf<Any>(1), listOf<Any>(1), listOf<Any>(1L)),
        Criterion("bos-ve-dolu", emptyList<Int>(), emptyList<Int>(), listOf(1)),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
sarmalayici: ayni-nesne=false, ikiz=true, farkli=false -> icerik
liste-1-1L: ayni-nesne=false, ikiz=true, farkli=false -> icerik
bos-ve-dolu: ayni-nesne=true, ikiz=true, farkli=false -> tekil
tekil: 1
kimlik: 0
icerik: 2
kismi-icerik: 0
```

İlk iki satır `icerik` çıktı: aynı tarifle ayrıca kurulan iki değer eşitti, `1` yerine `1L` konunca eşitlik bozuldu. `Wrapper(1)` ile `Wrapper(1L)` eşit değil, çünkü `value` alanları `Int` ve `Long` değerleridir ve `equals` onlara bakar; bu karşılaştırma tip parametresine değil, çalışma zamanındaki değerlere dayanır. Üçüncü satır `tekil`: aynı boş liste nesnesi.

## Özet

- Genellik, bir sınıfı ya da fonksiyonu tip parametresiyle yazdırır; `T : Comparable<T>` bir kısıt, `where` birden fazla kısıt koyma yoludur.
- Tip parametresi çalışma zamanında silinir; `emptyList<Int>()` ve `emptyList<String>()` aynı nesnedir, `reified` ise silmeyi bir fonksiyonda aşar.
- `out` kovaryans, `in` kontravaryans verir; işaretsiz parametre invaryanttır ve `MutableList<Int>`, `MutableList<Any>` değişkenine atanamaz.
- Salt okunur `List<Int>`, `List<Any>`'a atanır; atamada yeni nesne oluşmaz.
- Genel bir veri sınıfında eşitlik tip parametresine değil, alanların çalışma zamanı değerlerine bakar.
- Bu dersin ölçütü: **tekil**. Tip parametresi silindiği için farklı parametrelerle çağrılan iki boş liste aynı nesnedir.

## Sonraki Adım

Bu dersle tipler konusu kapanıyor: tekil nesneden devredilen davranışa, dışarıdan eklenen fonksiyona ve silinen tip parametresine kadar, eşitliğin her zaman değerin kendisinden geldiğini gördük. Sıradaki bölüm "Koleksiyon Türleri": liste, küme ve eşlem de genel tiplerdir ve içlerindeki değerlerin eşit sayılması koleksiyonun türüne değil, içeriğine bağlıdır.
