---
title: 'Güvenli Çağrı ve Elvis İşleci'
source: 'https://academia.sh/tr/kurslar/kotlin/guvenli-cagri-ve-elvis-isleci'
course: 'Kotlin Dili'
language: tr
updated: '2026-10-21T05:02:41+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Güvenli çağrı zincirinin ilk null değerde durduğunu ve argümanı değerlendirmediğini ölçer'
    - 'Elvis işlecinin sağ tarafının yalnızca sol taraf null olduğunda değerlendirildiğini gösterir'
    - 'Null olmama iddiasının null değerde NullPointerException fırlattığını yakalayarak gösterir'
    - 'Elvis işlecinin sağ tarafının ölçütü belirlediğini sınırlayıcı ölçümle doğrular'
---

# Güvenli Çağrı ve Elvis İşleci

Güvenli çağrı zincirinin null gördüğünde nerede durduğunun, Elvis işlecinin sağ tarafını ne zaman değerlendirdiğinin ve null olmama iddiasının hangi istisnayı fırlattığının ölçümü.

Önceki ders `null` denetiminin bir `if` ile yapıldığını ve denetimden sonra tipin daraldığını gösterdi. Aynı işi birkaç değerin üst üste bindiği yerde yapmak, her adım için ayrı bir `if` yazmayı gerektirir. Kotlin bu işi iki işleçle kısaltır: `?.` ile güvenli çağrı ve `?:` ile Elvis işleci. Üçüncü bir yol da vardır: denetimi derleyiciden alıp programcının üstüne yüklemek. Bu ders üçünü de ölçer ve her birinin `null` gördüğünde nerede durduğuna bakar.

Ölçü ekseni önceki dersteki gibidir: bir değerin kendisine eşit sayılan başka bir değerle ilişkisi, yani **ölçüt**. `Criterion` sınıfı açılış dersindeki hâliyle aşağıda yeniden verilir. Kural sırayla işler: ilk değer ikiziyle aynı nesneyse tekil; değilse ve ikiz eşit değilse kimlik; ikiz eşit ama üçüncü değer eşit değilse içerik; üçüncü değer de eşitse kısmi içerik. Önceki ders 2/2/3/1 dağılımının tekil kesitini ölçtü; bu ders içerik kesitine bakar.

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

## Güvenli Çağrı

Güvenli çağrı, bir üyeye erişimden önce alıcının `null` olup olmadığına bakar. Alıcı doluysa çağrı yapılır ve sonuç döner; alıcı `null` ise çağrı hiç yapılmaz ve ifadenin değeri `null` olur. Sonuç tipi bu yüzden her zaman boş değer alabilen bir tiptir: `String?` üzerinde `?.length` bir `Int?` verir.

Zincirlenen güvenli çağrılarda ilk `null` değer zinciri durdurur. `user.address?.city?.uppercase()` biçimindeki bir ifadede `address` `null` ise `city`'ye hiç bakılmaz; `city` `null` ise `uppercase` çağrılmaz. Aşağıdaki blok üç kullanıcıyla bunu ölçer: biri tam doldurulmuş, birinin adresi hiç yok, birinin adresi var ama şehri yok. Ardından ikinci bir soru sorar: çağrılmayan bir yöntemin argümanı değerlendirilir mi? `trace` fonksiyonu her çalıştığında bir satır basar, dolayısıyla çalışıp çalışmadığı çıktıdan okunur. Son iki satır `let` ile aynı soruyu sorar: `let` içindeki blok `null` alıcıda hiç koşmaz.

```kotlin
// Main.kt — guvenli cagri zinciri
class Address(val city: String?)
class User(val name: String?, val address: Address?)

fun trace(label: String): String {
    println("degerlendirildi: $label")
    return label
}

fun main() {
    val users = listOf(User("ada", Address("izmir")), User("can", null), User("eda", Address(null)))
    for (user in users) {
        println(user.address?.city?.uppercase())
    }
    val missing: String? = null
    println(missing?.plus(trace("arguman")))
    val present: String? = "ab"
    println(present?.plus(trace("arguman")))
    missing?.let { println("let calisti") }
    present?.let { println("let calisti: $it") }
}
```

```
IZMIR
null
null
null
degerlendirildi: arguman
abarguman
let calisti: ab
```

İlk üç satır zincirin davranışını verir. Tam doldurulmuş kullanıcı için sonuç `IZMIR`'dir. Adresi olmayan kullanıcı için `null` basılır; ikinci halkaya, yani şehre, hiç ulaşılmaz. Adresi olan ama şehri olmayan kullanıcı için de `null` basılır; üçüncü halka, yani `uppercase`, hiç çağrılmaz. Üç sonuç da aynı ifadeden çıkar, yalnızca zincirin nerede koptuğu değişir.

Dördüncü satır, alıcı `null` iken `plus` çağrısının argümanının değerlendirilmediğini gösterir: `degerlendirildi: arguman` satırı bu çağrıdan sonra gelmez, yalnızca alıcı dolu olan çağrıdan önce görünür. Argüman, çağrı yapılmayacaksa hesaplanmaz. Aynı şey `let` için geçerlidir: `null` alıcıda blok koşmaz, dolu alıcıda koşar ve `it` alıcının kendisidir.

**TN6.** Bu ölçüm yalnız `uppercase`, `plus` ve `let` için; başka yöntemlerin ve atama biçimindeki güvenli çağrının (bir özelliğe değer yazmanın) davranışı denenmedi. Argümanın değerlendirilmemesi yalnızca `plus` ile ölçüldü.

## Elvis İşleci

`?:` bir değer üretir: sol taraf `null` değilse sol tarafın kendisi, `null` ise sağ taraf. Sağ taraf yalnızca sol taraf `null` olduğunda değerlendirilir, tıpkı güvenli çağrıdaki argüman gibi. Sol tarafın tipi `String?` ise, işlecin sonucu çoğu zaman boş değer alamayan bir tiptir; çünkü sağ taraf bir `String` verirse ifadenin hiçbir yolu `null` döndürmez.

Sağ taraf bir değer olmak zorunda değildir. `return` ve `throw` da birer ifadedir ve tipi bütün tiplerin alt tipi sayılır; bu yüzden `name ?: return "isim-yok"` ve `name ?: throw ...` geçerlidir. Birincisi fonksiyondan erken çıkar, ikincisi bir istisna fırlatır. Sol taraf doluysa ikisi de çalışmaz ve sonraki satırlar `String` tipli bir değerle devam eder.

Aşağıdaki blok dört şeyi ölçer. İlk iki satırda sol taraf dolu ve boşken sağ tarafın `trace` çağrısı koşar mı? Sonraki iki satırda `describe` fonksiyonu `?: return` ile erken çıkar. Son satırda `needName` bir `IllegalArgumentException` fırlatır ve çağıran yer onu yakalar; basılan şey yalnızca istisnanın sınıf adıdır.

```kotlin
// Main.kt — Elvis
fun trace(label: String): String {
    println("degerlendirildi: $label")
    return label
}

fun describe(name: String?): String {
    val clean = name ?: return "isim-yok"
    return "isim=" + clean
}

fun needName(name: String?): String = name ?: throw IllegalArgumentException("bos")

fun main() {
    val present: String? = "ada"
    val missing: String? = null
    println(present ?: trace("sag-taraf"))
    println(missing ?: trace("sag-taraf"))
    println(describe(present))
    println(describe(missing))
    try {
        needName(missing)
    } catch (e: IllegalArgumentException) {
        println(e::class.simpleName)
    }
}
```

```
ada
degerlendirildi: sag-taraf
sag-taraf
isim=ada
isim-yok
IllegalArgumentException
```

Dolu sol tarafta `trace` hiç çalışmaz: ilk satır yalnızca `ada` basar. Boş sol tarafta `trace` çalışır, `degerlendirildi: sag-taraf` satırı basılır ve ifadenin değeri olan `sag-taraf` yazılır. `describe(present)` `isim=ada`, `describe(missing)` `isim-yok` verir; ikincisinde fonksiyon `return` ile çıktığı için `"isim=" + clean` satırına hiç gelinmez. Son satır, fırlatılan istisnanın sınıf adını verir.

**TN7.** `return` ve `throw` yalnız fonksiyon gövdesinde ve tek ifadeli gövdede ölçüldü; bir lambda içinde `?: return` kullanımı ve etiketli `return` denenmedi. İstisnanın ileti metni basılmadı; yalnızca sınıf adı ölçüldü.

## Null Olmama İddiası

Üçüncü yol, denetimi derleyiciden almak ve sorumluluğu programcıya vermektir. **Null olmama iddiası**, bir `String?` değerini, `null` olmadığını ileri sürerek `String`'e çevirir. Değer gerçekten doluysa sorun yoktur. Değer `null` ise derleyici o anı beklemeden hata vermez; hata çalışma anında bir `NullPointerException` olarak fırlar. İşleç, iki ünlem işaretinden oluşur ve ders metninde işaretin kendisi yazılmaz; iş adıyla anılır, kod bloğunda ise görünür.

Aşağıdaki blok iki çağrı yapar. Dolu değerde iddia geçer ve uzunluk basılır. Boş değerde iddia tutmaz; `try` bloğu istisnayı yakalar ve yalnızca sınıf adını basar. İstisnanın ileti metni ve yığıt izi çalışma ortamına bağlıdır, bu yüzden basılmaz.

```kotlin
// Main.kt — null olmama iddiasi
fun main() {
    val present: String? = "ada"
    val missing: String? = null
    println(present!!.length)
    try {
        println(missing!!.length)
    } catch (e: NullPointerException) {
        println(e::class.simpleName)
    }
}
```

```
3
NullPointerException
```

Dolu değerde `3`, boş değerde `NullPointerException` basılır. Güvenli çağrı ile aradaki fark çıktıdan okunur: güvenli çağrı `null` için `null` döndürüp programı sürdürür, iddia ise aynı durumda programı bir istisnayla durdurur. İddia, hatayı programcının bildirdiği bir noktaya taşır; derleyicinin sunduğu koruma o satırda kalkar.

**TN8.** İstisna yalnız yerel bir değişken üzerinde ve doğrudan `length` çağrısında yakalandı; bir özellik ya da fonksiyon dönüş değeri üzerinde iddianın davranışı denenmedi. `NullPointerException` adının bu çıktıda görünmesi Kotlin 2.4.21'in JVM hedefine bağlı bir ölçümdür.

## Aynı İfade İki Kez

Kursun ölçü ekseni için asıl soru şudur: aynı güvenli çağrı ifadesi iki kez koşturulursa sonuçlar nasıl bir ilişkide olur? Alıcı doluysa `uppercase` her çağrıda ayrı bir dizgi üretir; iki sonuç eşittir ama aynı nesne değildir. Alıcı `null` ise iki ifade de `null` verir ve `null` ile `null` aynı nesnedir. Aşağıdaki blok bu iki durumu ölçer. Dolu durumda alıcı `buildString` ile kurulur; böylece iki çağrı da derleme zamanında bilinen sabitlerle çalışmaz. Üçüncü değer ikizden tek bir harfle ayrılır: `ADI`, `ADA` değildir.

```kotlin
// Main.kt — ayni ifade iki kez
fun main() {
    val name: String? = buildString { append("a"); append("da") }
    val missing: String? = null
    val checks = listOf(
        Criterion("dolu", name?.uppercase(), name?.uppercase(), "ADI"),
        Criterion("bos", missing?.uppercase(), missing?.uppercase(), "ADI"),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

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

Dolu satırda `ayni-nesne=false` ve `ikiz=true` vardır; üçüncü değer ayrıldığı için `farkli=false` çıkar. Ölçüt içeriktir: iki `ADA` ayrı nesnelerdir ama `==` onları eşit bulur. Boş satırda ise aynı ifade `null` verir ve ölçüt tekildir. Güvenli çağrının kendisi bir ölçüt belirlemez; alıcının dolu ya da boş olması belirler.

**TN9.** Bu ölçüm `uppercase` için; aynı alıcıyla iki çağrının daima ayrı nesne üretmesi başka dizgi yöntemleri için denenmedi. Dolu satırdaki ayrı nesne sonucu `===` karşılaştırmasından okundu; çalışma ortamının benzer dizgileri paylaşıp paylaşmadığı ayrıca ölçülmedi.

## Null İçeren Listelerde İşleçler

İşleçler tek değerle sınırlı değildir. `null` öğe taşıyan bir listede `?.` ve `?:` her öğeye ayrı ayrı uygulanır. Blok `List<String?>` üzerinde dönüştürme, süzme ve toplama fonksiyonlarını dener: `map` her öğe için bir sonuç üretir, `mapNotNull` ve `filterNotNull` ise `null` sonuçları atar. Son iki satır `null` değerin kendisini arar.

```kotlin
// Main.kt — null iceren listede islecler
fun main() {
    val names: List<String?> = listOf("ab", null, "cde")
    println(names.map { it?.length })
    println(names.mapNotNull { it?.length })
    println(names.filterNotNull().size)
    println(names.sumOf { it?.length ?: 0 })
    println(names.firstOrNull { it == null })
    println(names.indexOf(null))
}
```

```
[2, null, 3]
[2, 3]
2
5
null
1
```

`map` her öğe için bir sonuç üretir, bu yüzden `null` öğe `?.length` ile `null` sonuca dönüşür ve liste `[2, null, 3]` olarak kalır. `mapNotNull` aynı dönüşümü yapıp `null` sonuçları atar, çıktı `[2, 3]` olur. `filterNotNull` yalnızca süzer, tipi `List<String>` yapar ve iki öğe kalır. `sumOf` bloğundaki `?: 0` ise yok olan uzunluğu sıfırla değiştirir, toplam `5` çıkar.

Son iki satır `null` değerin kendisini arar. `firstOrNull` koşulu sağlayan ilk öğeyi verir; `null` öğe bulunsa bile sonuç `null` olduğundan, bu çıktı aramanın başarısız olmasından ayırt edilemez. `indexOf(null)` ise konumu söyler ve `1` döner. Sık yapılan hata, `firstOrNull` sonucunun `null` olmasını "öğe yok" diye okumaktır. Liste `null` öğe taşıyabiliyorsa varlığı sormak için konum ya da `any` gibi bir fonksiyon seçilir. Genel kural basittir: `null` taşıyabilen bir koleksiyonda işleç öğe başına çalışır, koleksiyonun kendisi hakkında bir şey söylemez.

## İşleç Öncelikleri

Elvis işleci toplama işlecinden daha düşük öncelikli olduğu için parantezsiz yazılmış bir ifade göründüğünden farklı çalışabilir. Blok aynı ifadeyi iki biçimde yazar: sağ tarafta toplama olan ve sol tarafı parantez içine alınmış. Bunu dolu ve boş bir değerle dener. Ardından `?.` ile `?:` birlikte kullanılır ve bir `Int?` üzerinde hem `?:` hem `?.plus` çalıştırılır.

```kotlin
// Main.kt — islec oncelikleri
fun main() {
    val missing: String? = null
    val present: String? = "x"
    println(present ?: "a" + "b")
    println((present ?: "a") + "b")
    println(missing ?: "a" + "b")
    println((missing ?: "a") + "b")
    println(missing?.length ?: -1)
    println(missing?.length.toString())
    val count: Int? = null
    println((count ?: 0) + 1)
    println(count?.plus(1))
}
```

```
x
xb
ab
ab
-1
null
1
null
```

İlk satır işleç önceliğini gösterir. Elvis işleci toplama işlecinden düşük öncelikli olduğundan `present ?: "a" + "b"` ifadesi `present ?: ("a" + "b")` olarak okunur. Sol taraf dolu olduğu için sağ taraf hiç hesaplanmaz ve sonuç `x` çıkar. Parantezle `(present ?: "a") + "b"` yazıldığında ise `xb` elde edilir. Sol taraf `null` olduğunda iki biçim aynı sonucu, `ab`'yi verir, bu yüzden hata yalnızca dolu değerde ortaya çıkar ve testlerde gözden kaçabilir.

Sonraki satırlarda `missing?.length ?: -1` `-1` verir. Aşağıdaki satır daha sinsidir: `missing?.length.toString()` sonucu `null` basar, ama bu `null` bir dizgidir. Zincirin sonundaki `toString` çağrısı `Int?` alıcıda çalışır ve `"null"` dizgisini üretir; kısa devre yapmaz. Son iki satır `Int?` üzerinde aynı ayrımı gösterir: `(count ?: 0) + 1` sayıyı `1` yapar, `count?.plus(1)` ise `null` bırakır. Yok olan değerden sayı üretmek isteniyorsa Elvis, yokluğu korumak isteniyorsa `?.` seçilir.

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

Bu dersin kuralı, güvenli çağrının tek bir ölçüt vermediğidir. Elvis işleci için de aynısı geçerlidir, ama bu kez ölçütü işlecin kendisi değil sağ tarafı belirler. Sol taraf her üç gözlemde de `null`'dur ve ifade her seferinde sağ tarafı değerlendirir. Sağ taraf üç farklı biçimde yazılır: bir nesne bildirimi (ayrıntısı bu kursun ilerleyen bir dersindedir), `equals`'i yazılmamış bir sınıfın her seferinde kurulan örneği ve her seferinde `buildString` ile kurulan bir dizgi.

```kotlin
// Main.kt — sag tarafa gore
object Fallback
class Wrapper(val value: Int)

fun main() {
    val nothing: String? = null
    val checks = listOf(
        Criterion("nesne", nothing ?: Fallback, nothing ?: Fallback, Wrapper(1)),
        Criterion("sinif", nothing ?: Wrapper(0), nothing ?: Wrapper(0), Wrapper(1)),
        Criterion("dizgi", nothing ?: buildString { append("bo"); append("s") },
            nothing ?: buildString { append("bo"); append("s") }, "dolu"),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
nesne: ayni-nesne=true, ikiz=true, farkli=false -> tekil
sinif: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
dizgi: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 1
kimlik: 1
icerik: 1
kismi-icerik: 0
```

Aynı işleç, aynı `null` sol taraf, üç ölçüt çıktı. Nesne bildirimi her değerlendirmede aynı örneği verir, dolayısıyla ölçüt tekildir. Sıradan sınıf her değerlendirmede ayrı bir örnek kurar ve `equals` yazılmadığı için iki örnek eşit sayılmaz: ölçüt kimliktir. Dizgi her seferinde ayrı bir nesnedir ama `String` içeriğe göre eşitlendiği için ölçüt içeriktir. Üç satırın toplamı 1/1/1 dağılımını verir; işleç değişmedi, değişen sağ taraftır.

Bu, Elvis işlecini kullanırken beklenmedik bir sonuca yol açabilir. `nothing ?: Wrapper(0)` yazıldığında ifade her koşuda ayrı bir örnek üretir; iki koşunun sonucunu `==` ile karşılaştıran bir kod, `Wrapper`'ın `equals`'i yazılmadığı sürece hiçbir zaman eşit bulmaz. Eşitlik sağ tarafın tipinden gelir.

**TN10.** Sınırlayıcı ölçüm yalnız sol tarafı `null` olan üç gözlemle yapıldı; sol tarafın dolu olduğu durumda sağ tarafın değerlendirilmediği Elvis bölümündeki ölçümden alındı, burada tekrarlanmadı. Sınıfın `equals`'ini yazmanın sonucu bir sonraki dersin konusudur ve burada ölçülmedi.

## Özet

- Güvenli çağrı alıcı `null` ise çağrıyı yapmaz, argümanı değerlendirmez ve sonuç olarak `null` verir; zincir ilk `null` değerde kopar.
- Elvis işlecinin sağ tarafı yalnızca sol taraf `null` olduğunda değerlendirilir; sağ taraf `return` ya da `throw` olabilir.
- Null olmama iddiası denetimi derleyiciden alır; boş değerde çalışma anında `NullPointerException` fırlar.
- Aynı güvenli çağrı ifadesi dolu alıcıda ayrı ama eşit iki dizgi, boş alıcıda iki `null` verir: ilki içerik, ikincisi tekil.
- Elvis işlecinin ölçütünü sağ taraf belirler: nesne bildirimi tekil, `equals`'siz sınıf kimlik, dizgi içerik.
- Bu dersin ölçütü: **içerik**. Aynı güvenli çağrı iki kez koşunca dolu alıcıda iki ayrı ama eşit dizgi çıkar.

## Sonraki Adım

Elvis işlecinin sağ tarafında `equals`'i yazılmamış bir sınıf ölçütü kimliğe çevirdi. Bir sonraki ders bu sınıfların nasıl yazıldığını ve `equals`'in nereden geldiğini ölçer: birincil yapıcı, `init` bloğu, ikincil yapıcı ve özellikler. İki yoldan kurulan aynı değerli iki örneğin neden eşit sayılmadığı, orada ölçüte bakılarak açıklanır.
