---
title: 'Uzantı Fonksiyonları'
source: 'https://academia.sh/tr/kurslar/kotlin/uzanti-fonksiyonlari'
course: 'Kotlin Dili'
language: tr
updated: '2026-10-21T05:02:40+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Var olan bir tipe uzantı fonksiyonu ve uzantı özelliği yazmak'
    - 'Boş değer alabilen bir alıcıya uzantı yazıp null değerle çağırmak'
    - 'Çağrıya uyan bir üye varken uzantının hiç seçilmediğini ölçmek'
    - 'Uzantının statik tipe göre seçildiğini, üyenin ise nesneye göre seçildiğini ayırmak'
---

# Uzantı Fonksiyonları

Uzantı fonksiyonu, uzantı özelliği ve boş değer alabilen alıcıya uzantı yazar; uzantının üyeyi gölgeleyemediğini ve statik tipe göre seçildiğini ölçer.

Önceki derste bir sınıf, davranışını `by` ile başka bir nesneye devretti ve devretmenin `equals`'ü iletmediğini gördük. Bu derste davranış sınıfa dışarıdan ekleniyor: kaynağına dokunamadığınız bir tipe yeni bir fonksiyon yazdırmak mümkün. Soru, bu eklemenin tipin kendi üyesinin yerini alıp alamayacağı ve özellikle `equals` gibi bir üyeyi değiştirip değiştiremeyeceği.

**Uzantı fonksiyonu (extension function)**, var olan bir tipin adı önüne yazılan ve o tipin bir örneği üzerinde çağrılan fonksiyondur: `fun String.wrapped(...)`. Tipin tanımına dokunulmaz; uzantı yalnız çağrı sözdizimini ona benzetir. Gövde içinde `this`, fonksiyonun çağrıldığı nesneyi, yani alıcıyı (receiver) gösterir.

Bu `this`, Kapsam Fonksiyonları dersinde gördüğümüz `this` ve `it` ayrımıyla karıştırılmamalı: orada soru hangi nesnenin göründüğüydü, burada `this` yalnız uzantının alıcısıdır ve ölçü o sorudan ayrıdır. Bu dersin ölçüsü bu kursta hep aynı: bu iki değeri eşit yapan ne?

## Ö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 }}")
    }
}
```

**TN30.** Uzantıların hepsi `Main.kt` içinde, `main`'in yanında yazıldı; ayrı bir dosyada tanımlanıp içe alınan uzantı bu derste ölçülmedi.

## Uzantı Fonksiyonu ve Özelliği

Aşağıdaki blok üç uzantı yazar. `wrapped`, bir dizginin iki yanına bir işaret ekler. `lastWord`, bir uzantı **özelliği**dir: değeri saklanmaz, her okunuşta `get()` ile hesaplanır. Üçüncüsü, alıcısı `String?` olan bir uzantıdır; null değer üzerinde de çağrılabilir ve gövdede `this` boş değer alabilen bir tiptir.

```kotlin
// Main.kt — uzanti fonksiyonu, uzanti ozelligi ve bos deger alabilen alici
fun String.wrapped(mark: String): String = mark + this + mark

val String.lastWord: String
    get() = this.substringAfterLast(' ')

fun String?.orPlaceholder(): String = this ?: "yok"

fun main() {
    println("ada".wrapped("*"))
    println("bir iki uc".lastWord)
    val present: String? = "var"
    val missing: String? = null
    println(present.orPlaceholder())
    println(missing.orPlaceholder())
}
```

```
*ada*
uc
var
yok
```

`wrapped` ve `lastWord` bir `String` üzerinde, sanki tipin üyesiymiş gibi yazıldı. `orPlaceholder` ise null değerli `missing` üzerinde patlamadı: normal bir üye çağrısı null üzerinde çalışmazdı, ama uzantı alıcıyı bir parametre gibi aldığı için null da geçerli bir alıcı. Bu yüzden `String?` alıcılı bir uzantı, güvenli çağrı (`?.`) yazmadan null'ı karşılayabilir.

Uzantı özelliği bir alan taşıyamaz: ona saklanan değer verilemez. Ö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 String.cache: Int = 0
```

**TN31.** Reddin nedenini, yani uzantı özelliğinin alan taşımadığını, derleyicinin hata metninden değil dilin tanımından aktarıyoruz; hata metnini derse yazmıyoruz.

## Uzantı Üyeyi Gölgeleyemez

Bir tipin üyesiyle aynı adı taşıyan, ama daha dar bir parametre tipi alan bir uzantı yazarsanız ne olur? Aşağıdaki blok `Plain` sınıfına, `Any`'den gelen `equals(other: Any?)` üyesinin yanına bir `equals(other: Plain)` uzantısı ekler. Uzantı, iki `Plain`'in `id`'sini karşılaştırır. Üç yoldan çağırıyoruz: doğrudan çağrı, `==` ve `Any` tipli bir değişken üzerinden çağrı.

```kotlin
// Main.kt — equals uzantisi uyeyi golgelemez
class Plain(val id: Int)

fun Plain.equals(other: Plain): Boolean = this.id == other.id

fun main() {
    val a = Plain(1)
    val b = Plain(1)
    val asAny: Any = b
    println(a.equals(b))
    println(a == b)
    println(a.equals(asAny))
    Criterion("equals-uzantisi", Plain(1), Plain(1), Plain(2)).report()
}
```

```
false
false
false
equals-uzantisi: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
```

Üç çağrı da aynı sonucu verdi: `id`'ler eşit olduğu hâlde karşılaştırma yanlış çıktı. Uzantı yazıldı, ama hiçbir çağrı ona gitmedi: üye `Any?` aldığı için `Plain` argümanı da ona uyar ve çağrıya uyan bir üye varsa uzantıya bakılmaz, uzantının parametresi daha özgül olsa bile. Uzantının imzası farklıdır; üyeyi gölgelemez, yalnız bir aşırı yükleme olarak durur. `a == b` ise her zaman `equals(Any?)` üyesine çözülür ve uzantıyı hiç çağırmaz. Bu yüzden ölçüt `kimlik`: iki `Plain` aynı `id`'yi taşıyor, ama eşitlik kuralı bunu bilmiyor.

**TN32.** Uzantının üye ile çakıştığında derleyicinin bir uyarı verdiği iddiasını yazmıyoruz; derleyici uyarısı bu derste denetlenmiyor. Bir uzantı üyeden farklı bir parametre tipiyle yazılsa da argüman üyeye uyuyorsa üye seçilir; uzantıya ancak üye uygulanamadığında sıra gelir. Bu durum bir sonraki bölümde `Box.scale` ile ölçülüyor.

## Aynı Ad, Farklı Parametre

Önceki bölümde argümanın hem üyeye hem uzantıya uyduğu bir çağrıya baktık ve üyenin kazandığını gördük. Peki argüman üyeye uymuyorsa? Üye `Int` alır, uzantı `Double` alır ve çağrının hangisine gideceğine argümanın tipi karar verir. Aşağıdaki blok bunu `Box` sınıfı üzerinde ölçer. Ayrıca `infix` işaretli bir uzantıyı ve boş değerli bir alıcı üzerinde `toString` çağrısını ekler.

```kotlin
// Main.kt — farkli parametreli uzanti secilir, infix uzanti, bos alicida toString
class Box(val size: Int) {
    fun scale(factor: Int): Int = size * factor
}

fun Box.scale(factor: Double): Double = size * factor

infix fun Box.bigger(other: Box): Boolean = size > other.size

fun main() {
    val box = Box(3)
    println(box.scale(2))
    println(box.scale(2.5))
    println(box bigger Box(2))
    val nothing: Any? = null
    println(nothing.toString())
    println(nothing?.toString())
    Criterion("kutu", Box(1), Box(1), Box(2)).report()
}
```

```
6
7.5
true
null
null
kutu: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
```

`box.scale(2)` üyeye gitti ve `6` verdi; `box.scale(2.5)` üyenin `Int` parametresine uymadığı için uzantıya gitti ve `7.5` verdi. Uzantı, üye çağrıya uyduğu sürece kaybetti ve üyenin karşılamadığı bir argüman tipini karşıladığında çalıştı. Bu, "uzantı üyeyi gölgeleyemez" cümlesinin sınırını çizer: üye uygulanabiliyorsa uzantıya bakılmaz.

`box bigger Box(2)` ifadesi `true` verdi. `infix` işareti bir uzantıyı nokta ve parantez olmadan, iki işlenenli bir işleç gibi yazmayı sağlar. Bu yalnız yazım kolaylığıdır; çağrı yine alıcı olarak `box`'ı alan sıradan bir uzantıdır.

Boş değerli alıcı satırları ilginçtir. `nothing` değişkeni `Any?` tipli ve değeri null; `nothing.toString()` patlamadan `null` yazdı. Çünkü standart kitaplıkta `Any?` alıcılı bir `toString` uzantısı vardır; üyeymiş gibi çağrılır ama null'ı karşılar. `nothing?.toString()` de `null` yazdı, ama bu kez güvenli çağrı yüzünden: çağrı hiç yapılmadı ve ifadenin değeri null oldu. İki satır aynı çıktıyı verdi, ama farklı yoldan; birinde fonksiyon koştu, diğerinde koşmadı.

`Criterion` satırı yine `kimlik`: `Box` sıradan bir sınıf, uzantılar eşitliğe dokunmadı.

**TN33.** `Box.scale` üyesi `Int`, uzantısı `Double` alır; imzaları farklıdır. `box.scale(2)` çağrısının üyeye gittiği ölçüldü; uzantı `Double` beklediği için `Int` argümanla zaten aday değildir. `Int` argüman üyeye uyduğu için üye seçildi.

## Genel Alıcı ve Sınıf İçindeki Uzantı

Uzantı iki yerde daha tuhaf bir davranış gösterir. Alıcı tipi genel (generic) olabilir: `List<T>` üzerine yazılan bir uzantı her eleman tipinde çalışır. Ve bir uzantı bir sınıfın içinde bildirilebilir; o zaman yalnız o sınıfın içinden çağrılır ve sınıfın üyelerine de ulaşabilir. Aşağıdaki blok ikisini birlikte ölçer.

```kotlin
// Main.kt — genel alicili uzanti ve sinif icinde bildirilen uzanti
fun <T> List<T>.secondOrNull(): T? = if (size >= 2) this[1] else null

class Formatter(private val mark: String) {
    fun String.marked(): String = mark + this + mark

    fun run(text: String): String = text.marked()
}

fun main() {
    println(listOf(1, 2, 3).secondOrNull())
    println(listOf("a").secondOrNull())
    println(emptyList<Int>().secondOrNull())
    println(Formatter("|").run("ada"))
    Criterion("bicimleyici", Formatter("|"), Formatter("|"), Formatter("#")).report()
}
```

```
2
null
null
|ada|
bicimleyici: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
```

`secondOrNull` üç listede çalıştı: üç elemanlı `Int` listesinde `2`, tek elemanlı `String` listesinde `null`, boş listede `null`. Tek uzantı farklı eleman tipleri için yazılmış olduğu hâlde her çağrı kendi tipini çıkardı; dönüş tipi `T?` olduğu için boş durumda null güvenle döndü.

`Formatter` içindeki `marked` uzantısı sınıfın `mark` özelliğini okudu: `Formatter("|").run("ada")` `|ada|` verdi. Üstte yazdığımız uzantılarda alıcı tek bir `this` idi; burada iki alıcı vardır, `Formatter` örneği ve `String` alıcısı. `marked` yalnız `Formatter` gövdesinde görünür; sınıfın dışında `"ada".marked()` yazılamaz.

Ölçüt yine `kimlik`: iki `Formatter` aynı işareti taşıyor olsa da eşit değil. Uzantıların, ister dışarıda ister bir sınıfın içinde yazılsın, tipin eşitliğine dokunmadığı bu dersin üçüncü ölçümünde de yinelendi.

**TN34.** Sınıf dışından `marked` çağrısının derlenmediği ayrıca ölçülmedi; kapsam bilgisi dilin tanımından aktarılıyor.



## Ölçümlerden Çıkan Seçim Kuralı

Uzantı fonksiyonu bir tipe dışarıdan eklenir, ama çağrı yazılışı üye çağrısına benzer. Bu benzerlik, hangisinin seçildiği sorusunu gerekli kılar. Bu dersin ölçümleri üç kural verdi. Birincisi, argüman üyeye uyuyorsa üye kazanır; `equals` üyesi `Any?` aldığı için `Plain` argümanı da ona uydu ve `Plain.equals` uzantısı, parametresi daha özgül olduğu hâlde, üç çağrının hiçbirinde seçilmedi. İkincisi, üye uygulanamıyorsa sıra uzantıya gelir; `Box.scale` üyesi `Int` beklediği için `scale(2.5)` ona uymadı ve uzantıya gitti. Üçüncüsü, seçim değişkenin statik tipine göre yapılır; uzantı çalışma zamanındaki sınıfa bakmaz.

Sık yapılan hata, uzantıyı üyeyi değiştirmenin bir yolu sanmaktır. Bir tipin üyesini "düzeltmek" için aynı adla uzantı yazan kişi, kodun çalışmaya devam ettiğini ve hiçbir şeyin değişmediğini görür; çünkü üye çağrıya uyduğu sürece uzantı hiç çağrılmaz. Üyenin davranışını değiştirmek gerekiyorsa tipin kendisi değişir ya da yeni bir ad seçilir. Bu ölçümde `equals` için de aynı sonuç görüldü ve ölçüt `kimlik` kaldı.

Boş alıcı ayrıntısı ikinci bir tuzağı gösterir. `nothing.toString()` ve `nothing?.toString()` aynı çıktıyı yazdı, ama ilkinde fonksiyon koştu, ikincisinde koşmadı. Çıktıya bakarak hangisinin kullanıldığı anlaşılmaz; fark, fonksiyonun yan etkisi olduğunda görünür. Alıcısı boş değer alabilen bir uzantı yazan kişi, bu fonksiyonun null alıcıda da koşacağını bilmeli ve gövdesini buna göre yazmalıdır.

Genel alıcılı uzantı ile sınıf içindeki uzantı ise kapsam sorusunu getirir. `secondOrNull` bir kez yazıldı ve üç ayrı listede çalıştı; `marked` ise yalnız `Formatter` gövdesinde göründü. İlki tipin dışında, her yerden çağrılabilen bir fonksiyondur; ikincisi sınıfın üyelerine ulaşan ama yalnız o sınıfın içinden çağrılabilen bir fonksiyondur. Hangisini seçeceğiniz, fonksiyonun sınıfın durumuna ihtiyaç duyup duymamasına bağlıdır: durum gerekiyorsa sınıfın içine, gerekmiyorsa dışına yazılır.

## Ölçmeden Önce Tahmin Etmek

Bu dersteki her ölçüm aynı kalıba girer: bir çağrı yazın, hangisinin koşacağını söyleyin, sonra çıktıya bakın. `box.scale(2)` için üyeyi, `box.scale(2.5)` için uzantıyı söyleyebiliyorsanız seçim kuralını anlamışsınız demektir: argüman üyenin parametresine uyuyorsa üye, uymuyorsa uzantı. Aynı mantık `bigger` için de geçerli; `Box` üzerinde `bigger` adında bir üye yoksa uzantı çalışır ve `infix` işareti yalnız yazılışı değiştirir.

Boş alıcıda tahmin iki aşamalıdır. `nothing.toString()` için "patlar" diyen kişi, `toString`'in bir üye olduğunu, null üzerinde çağrılamayacağını varsaymıştır. Ölçüm `null` yazdı, çünkü çağrı üye değil, boş alıcıyı karşılayan bir uzantıya gitti. Bu, bir ifadenin yazılışının (`nothing.toString()`) hangi fonksiyonu çağırdığını söylemediği anlamına gelir; alıcının tipine ve görünür uzantılara bakmak gerekir.

Genel alıcılı uzantıda tahmin dönüş değeri üzerindedir. Üç elemanlı listede `2`, tek elemanlıda ve boşta `null` beklenir; `T?` dönüş tipi bu iki durumu aynı değerle karşılar. Kimlik satırlarında ise `Box` ve `Formatter` sıradan sınıflar olduğu için `==` her zaman `false` verecektir; uzantıların kaç tane yazıldığı sonucu etkilemez.

Tahmin ile ölçümün ayrıldığı yerde durmak değerlidir. Bu derste bir kez, `nothing` için iki farklı yazılışın aynı çıktıyı verdiği görüldü ve bu, iki yazılışın aynı şey olduğu anlamına gelmedi. Çıktının eşit olması ile yolun eşit olması ayrı iki olgudur; bir ölçümde yalnız çıktıya bakan kişi ikincisini görmez. Bu yüzden her satırın yanına, o çıktıya hangi yoldan gelindiğini de yazmak iyi bir alışkanlıktır.

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

Uzantı üyeyi gölgeleyemiyorsa, uzantı hangi kuralla seçilir? Üyeler nesnenin çalışma zamanındaki sınıfına göre seçilir. Uzantılar ise değişkenin bildirilmiş tipine, yani statik tipe göre. **Statik bağlama (static dispatch)**, çağrılacak fonksiyonun derleme sırasında değişkenin bildirilmiş tipine bakılarak seçilmesidir; **dinamik bağlama (dynamic dispatch)** ise nesnenin çalışma zamanındaki sınıfına bakılarak seçilmesi. Uzantılar ilkini kullanır.

Aşağıdaki blok aynı nesneyi iki farklı statik tiple tutar. `Speaker` ve `Loud` için birer `label` uzantısı yazılıdır, `speak` ise sınıfın bir üyesidir ve `Loud` onu yeniden tanımlar. M08/K02'de kararı hangi tipin verdiğine bakmıştık; bu ölçü o soruya dönmüyor, aşağıdaki satırlar yalnız bu iki seçim kuralını yan yana koyuyor.

```kotlin
// Main.kt — uzanti statik tipe gore, uye nesneye gore secilir
open class Speaker {
    open fun speak(): String = "temel"
}

class Loud : Speaker() {
    override fun speak(): String = "yuksek"
}

fun Speaker.label(): String = "etiket-temel"
fun Loud.label(): String = "etiket-yuksek"

fun main() {
    val exact = Loud()
    val asSpeaker: Speaker = exact
    println(exact.label())
    println(asSpeaker.label())
    println(exact.speak())
    println(asSpeaker.speak())
    Criterion("iki-statik-tip", asSpeaker, exact, Speaker()).report()
}
```

```
etiket-yuksek
etiket-temel
yuksek
yuksek
iki-statik-tip: ayni-nesne=true, ikiz=true, farkli=false -> tekil
```

`asSpeaker` ve `exact` aynı nesneyi tutuyor: son satır `tekil` yazdı. Buna karşın `label` iki ayrı sonuç verdi, çünkü uzantı değişkenin tipine bakar: `exact` için `Loud` uzantısı, `asSpeaker` için `Speaker` uzantısı seçildi. `speak` ise iki değişkende de aynı sonucu verdi, çünkü üye nesnenin gerçek sınıfına bakar. Eşitlik de bir üyedir ve bu yüzden bir uzantı onu değiştiremez: `==` çalışma zamanındaki nesnenin `equals`'üne gider, bir uzantı seçilemez.

## Özet

- Uzantı fonksiyonu, bir tipe kaynağına dokunmadan yeni bir fonksiyon ekler; gövdede `this` alıcıdır.
- Uzantı özelliği alan taşıyamaz, her okunuşta hesaplanır; saklanan değerli bir uzantı özelliği derlenmez.
- Alıcısı boş değer alabilen bir tip olan uzantı null üzerinde çağrılabilir ve null'ı kendisi karşılar.
- Çağrıya uyan bir üye varsa üye kazanır, uzantının parametresi daha özgül olsa bile; `==` hep `equals(Any?)` üyesine çözüldüğü için uzantı `equals`'ü değiştiremez.
- Uzantı değişkenin statik tipine göre, üye nesnenin çalışma zamanındaki sınıfına göre seçilir.
- Bu dersin ölçütü: **kimlik**. Uzantı `equals` yazsa bile üye kazandığı için eşitlik, sınıf kendi `equals`'ünü yeniden tanımlamadıkça kimliğe düşer.

## Sonraki Adım

Bu derste bir tipin dışına fonksiyon ekledik ve eklenen şeyin tipi değil, değişkenin tipi tarafından seçildiğini ölçtük. Sıradaki ders tipin kendisini parametreleştiriyor: genellikler, bir sınıfı ya da fonksiyonu birden fazla tiple çalışacak biçimde yazdırır, varyans da bu tiplerin birbirine atanıp atanamayacağını söyler.
