---
title: 'Kotlin Dili'
source: 'https://academia.sh/tr/kurslar/kotlin'
language: tr
updated: '2026-10-21T05:02:39+00:00'
license: 'CC BY-SA 4.0'
lessons: 23
outcomes:
    - "Kotlin'in null güvenliği modelini kullanır"
    - 'Fonksiyonel araçlarla koleksiyon dönüşümü yazar'
    - 'Sınıf türlerini modelleme ihtiyacına göre seçer'
    - 'İki değerin eşitliğini tekil nesnenin mi, nesne kimliğinin mi, içeriğin mi yoksa içeriğin bir kısmının mı verdiğini ayırt eder'
    - 'Eş yordam ve akışla yazılan eşzamanlı kodun temel düzeneğini okur'
---

# Kotlin Dili

Kotlin'de iki değeri eşit yapan şey her tipte aynı değildir: bir numaralandırma girdisi istendiğinde hep aynı nesne gelir, sıradan bir sınıfın aynı değerlerle kurulan iki örneği eşit sayılmaz, bir veri sınıfının iki örneği içerikleri aynıysa eşittir, gövdesinde ek özellik taşıyan veri sınıfında o özelliği farklı olan iki örnek de eşit çıkar. Kurs her düzeneği tek bir soruyla ölçer — bu iki değeri eşit yapan ne? Değişkenler, temel tipler, fonksiyonlar ve lambdalar, null güvenliği, sınıflar, veri sınıfları, mühürlü tipler, nesne bildirimleri, devretme, uzantılar, genellikler, koleksiyonlar, tembel diziler, eş yordamlar, akışlar ve istisnalar aynı çerçeveden geçirilir.

## Dil Temelleri

Kotlin'in JVM üzerindeki konumu ve tasarım hedefleri, değişebilir ve değişmez bildirim, temel tipler, dizgi şablonları, when ifadesi ve döngülerle denetim akışı, varsayılan ve adlandırılmış parametreli fonksiyonlar, lambdalar ve yüksek mertebeden fonksiyonlar ile kapsam fonksiyonları.

### Kotlin'in Konumu

Source: https://academia.sh/tr/kurslar/kotlin/kotlinin-konumu

Kotlin'in JVM üzerindeki yerini ve tasarım hedeflerini tanıtır; aynı `==` işlecinin sekiz değerde dört ayrı ölçütle eşitlik verdiğini ölçer.

Bu kurs Kotlin'i bir dilin kendi sorusuyla öğretir: iki değer ne zaman aynıdır? Kotlin, statik tipli, derlenen bir programlama dilidir; JVM (Java Virtual Machine), Android, JavaScript, Wasm ve Native hedeflerine derlenir. Bu kurs ölçümlerini JVM hedefinde yapar: kaynak kodu bayt koduna çevrilir ve JVM'in kendi sınıf kitaplığıyla yan yana yaşar. Tasarım hedefleri iki cümlede toplanır: az törenle okunabilir kod yazmak ve tip denetimini derleme zamanına çekmek. Bu iki hedefin ortak noktası, dilin değerler hakkında kesin kurallar koymasıdır. İlk kurala hemen bakıyoruz: Kotlin'de `==` işleci eşitlik sorar, `===` işleci ise iki ifadenin aynı nesneye işaret edip etmediğini sorar.

Bu konunun sonraki yedi dersi aynı soruyu farklı yapılar üzerinde sorar. Her derste bir ölçüt bulunur ve bir sınırlayıcı ölçüm o ölçütün sınırını gösterir. Ölçüt dört addan biridir. **Tekil** (singleton) ölçüt, değerin bellekte tek bir örneği olduğu ve her yerden aynı nesneye ulaşıldığı durumdur. **Kimlik** ölçüt, eşitliğin nesnenin kendisine bağlı olduğu durumdur: aynı biçimde kurulmuş iki nesne eşit sayılmaz. **İçerik** ölçüt, eşitliğin taşınan değerlere bağlı olduğu durumdur. **Kısmi içerik** ölçüt, eşitliğin değerlerin yalnız bir bölümüne baktığı, geri kalanını yok saydığı durumdur.

#### Sekiz Değer, Dört Ölçüt

Ölçüm her derste aynı yardımcı dosyayla yapılır. `Criterion` sınıfı bir değer ile onun aynı biçimde kurulmuş ikizini, bir de farklı bir değeri alır; üç soru sorar. İkiz aynı nesne mi? İkiz `==` ile eşit mi? Farklı değer `==` ile eşit mi? Bu üç yanıt dört ölçütten birini belirler.

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

Aşağıdaki program sekiz değeri bu sınıftan geçirir. Numaralandırma girdisi, nesne bildirimi, sıradan sınıf, dizi, veri sınıfı, liste, dizgi ve gövdesinde fazladan bir özellik taşıyan bir veri sınıfı. Veri sınıfı (data class), eşitliği ve metin gösterimini kurucu özelliklerinden türeten sınıftır; ayrıntısı bu dersin konusu değil, Veri Sınıfları dersinde işlenir; burada yalnız eşitliğine bakıyoruz.

```kotlin
// Main.kt — sekiz deger, dort olcut
enum class Color { RED, BLUE }

object Registry
object Archive

class Box(val size: Int)

data class Point(val x: Int, val y: Int)

data class Tag(val name: String) {
    var note: String = ""
}

fun main() {
    val word = listOf("kot", "lin").joinToString("")
    val checks = listOf(
        Criterion("numaralandirma-girdisi", Color.RED, Color.valueOf("RED"), Color.BLUE),
        Criterion("nesne-bildirimi", Registry, Registry, Archive),
        Criterion("siradan-sinif", Box(1), Box(1), Box(2)),
        Criterion("dizi", intArrayOf(1, 2), intArrayOf(1, 2), intArrayOf(1, 3)),
        Criterion("veri-sinifi", Point(1, 2), Point(1, 2), Point(1, 3)),
        Criterion("liste", listOf(1, 2), listOf(1, 2), listOf(1, 3)),
        Criterion("dizgi", "kotlin", word, "kotlim"),
        Criterion(
            "govdedeki-ozellik",
            Tag("a").apply { note = "ilk" },
            Tag("a").apply { note = "ilk" },
            Tag("a").apply { note = "son" },
        ),
    )
    for (check in checks) {
        check.report()
    }
    summarize(checks)
}
```

```
numaralandirma-girdisi: ayni-nesne=true, ikiz=true, farkli=false -> tekil
nesne-bildirimi: ayni-nesne=true, ikiz=true, farkli=false -> tekil
siradan-sinif: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
dizi: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
veri-sinifi: ayni-nesne=false, ikiz=true, farkli=false -> icerik
liste: ayni-nesne=false, ikiz=true, farkli=false -> icerik
dizgi: ayni-nesne=false, ikiz=true, farkli=false -> icerik
govdedeki-ozellik: ayni-nesne=false, ikiz=true, farkli=true -> kismi-icerik
tekil: 2
kimlik: 2
icerik: 3
kismi-icerik: 1
```

Çıktının okunuşu şöyledir. `numaralandirma-girdisi` ve `nesne-bildirimi` tekildir: numaralandırma girdisi (enum entry) ile `object` bildirimi bellekte tek örnek olarak yaşar, `Color.valueOf("RED")` çağrısı da aynı örneği döndürür. `siradan-sinif` ile `dizi` kimliktir: iki `Box(1)` aynı kurucu argümanını taşısa da eşit sayılmaz, iki `intArrayOf(1, 2)` de öyle. Dizide `==` elemanlara bakmaz, nesneyi karşılaştırır. `veri-sinifi`, `liste` ve `dizgi` içeriktir: ikizi eşittir, farklı değer eşit değildir ve ikizi aynı nesne değildir. Son satır, `govdedeki-ozellik`, kısmi içeriktir. `note` özelliği veri sınıfının kurucusunda değil gövdesinde tanımlı olduğu için eşitliğe katılmaz; `note` değeri `ilk` ile `son` olan iki nesne `==` ile eşit çıkar.

Özet satırları bu dağılımı doğrular: iki tekil, iki kimlik, üç içerik, bir kısmi içerik. Sekiz değerin tamamı sınıflandı ve hiçbiri beşinci bir ölçüte düşmedi.

**DT1.** Sekiz değerin her biri tek bir kurucu argümanı kümesiyle ölçüldü; aynı sınıfın başka argümanlarla kurulmuş örneklerinin sınıflandırması bu dersin dışındadır.

**DT2.** Dizi için yalnız `intArrayOf` ölçüldü; diğer dizi tiplerinin aynı davranışta olduğu bu derste ölçülmüş bir iddia değildir.

#### JVM Üzerinde Kotlin

Kotlin JVM üzerinde çalıştığı için JVM'in kendi tiplerini doğrudan kullanır. `java.util.ArrayList` böyle bir tiptir. Kotlin'in `listOf` ile kurduğu liste de çalışma anında bir JVM listesidir; ikisi aynı eşitlik kuralına bağlıdır. Aşağıdaki blok üç şeyi ölçer: iki JVM listesini, bir Kotlin listesi ile bir JVM listesini ve kurucuya geçirilen değeri değiştirince veri sınıfının eşitliğini.

```kotlin
// Main.kt — JVM listesi ve ayni veri sinifi
import java.util.ArrayList

data class Tag(val name: String) {
    var note: String = ""
}

fun main() {
    val jvmList = ArrayList<Int>()
    jvmList.add(1)
    jvmList.add(2)
    val jvmTwin = ArrayList<Int>()
    jvmTwin.add(1)
    jvmTwin.add(2)
    println(jvmList.javaClass.name)
    val checks = listOf(
        Criterion("jvm-listesi", jvmList, jvmTwin, listOf(1, 3)),
        Criterion("kotlin-ve-jvm-listesi", listOf(1, 2), jvmList, listOf(1, 3)),
        Criterion("veri-sinifi-yapici", Tag("a"), Tag("a"), Tag("b")),
    )
    for (check in checks) {
        check.report()
    }
    summarize(checks)
}
```

```
java.util.ArrayList
jvm-listesi: ayni-nesne=false, ikiz=true, farkli=false -> icerik
kotlin-ve-jvm-listesi: ayni-nesne=false, ikiz=true, farkli=false -> icerik
veri-sinifi-yapici: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 0
kimlik: 0
icerik: 3
kismi-icerik: 0
```

İlk satır JVM'in sınıf adını basar: `java.util.ArrayList`. `jvm-listesi` içeriktir, çünkü aynı elemanları taşıyan iki liste eşittir ve ayrı nesnelerdir. `kotlin-ve-jvm-listesi` de içeriktir: Kotlin'in `listOf(1, 2)` ifadesiyle kurduğu liste, bir JVM listesiyle elemanlar aynıysa `==` ile eşit çıkar. Kotlin tipi ile JVM tipi eşitlik açısından ayrı bir dünya oluşturmaz. `veri-sinifi-yapici` ise bu derste daha önce gördüğümüz `Tag` sınıfının kurucuya bağlı hâlidir; kurucudaki `name` farklı olunca eşitlik bozulur, yani eşitlik kurucu özelliklerine bakar.

**DT3.** Bu bölümde `listOf` ile `java.util.ArrayList` yalnız eşitlik açısından karşılaştırıldı; `listOf` ifadesinin çalışma anı sınıfı ilerideki bir bölümde ayrıca basılır.

**DT4.** JVM listesi `java.util.ArrayList` ile ölçüldü; JVM'in diğer koleksiyon sınıfları bu dersin dışındadır.

#### Veri Sınıfının Türettikleri

Veri sınıfı, birincil yapıcıdaki özelliklerden `equals`, `hashCode`, `toString`, `componentN` ve `copy` üyelerini türetir; bu bölüm ilk üçünü ölçer. Önceki ölçüm yalnız `==` kolunu gösterdi. Aşağıdaki blok, gövdesinde `note` özelliği taşıyan bir `Tag` sınıfıyla üçünü birden sorar. İkinci nesnenin `note` değeri sonradan değiştirilir; kurucu argümanı iki nesnede de aynıdır.

```kotlin
// Main.kt — veri sinifi: equals, hashCode ve toString
data class Tag(val name: String) {
    var note: String = ""
}

fun main() {
    val first = Tag("a")
    val second = Tag("a")
    second.note = "ikinci"
    println(first == second)
    println(first.hashCode() == second.hashCode())
    println(first)
    println(second)
    println(first.note.isEmpty())
}
```

```
true
true
Tag(name=a)
Tag(name=a)
true
```

İlk iki satır `true` verir: `==` ve `hashCode` farkı görmez. Sonraki iki satır, `println` ile basılan gösterimin `note` değerini taşımadığını ortaya koyar. `second.note` artık `"ikinci"` olduğu hâlde ekrana `Tag(name=a)` çıkar. Son satır, `first.note` değerinin hâlâ boş olduğunu doğrular; yani iki nesne gerçekten farklı durumda ve türetilen üç davranış bu farkı birlikte yok sayar.

Sık yapılan hata tam burada çıkar. Bir hata ayıklama oturumunda iki nesneyi `println` ile basıp aynı görünce "bunlar aynı" sonucuna varılır; oysa gövdedeki özellik ayrı durur. Bu yüzden eşitliğe katılması gereken her değer kurucu özelliği olmalıdır. Java'da `equals` ve `hashCode` elle yazılır ve ikisinin birbiriyle tutarlı kalması geliştiricinin işidir; Kotlin'de ikisi aynı kurucu özelliklerinden birlikte türediği için, biri elle yeniden tanımlanmadıkça tutarsız kalmaz.

**DT5.** Java karşılaştırması bu bölümde ölçülmedi; Java tarafındaki `equals` ve `hashCode` yazımı bilinen davranış olarak anıldı, bu derste çalıştırılmış bir iddia değildir.

#### Çalışma Anı Sınıfları

Kotlin kaynağında tek bir `Int` adı vardır; JVM'de ise `int` ilkeli ile `java.lang.Integer` kutusu ayrı iki şeydir. Aynı değerin çalışma anında hangi sınıfla yaşadığını `javaClass.name` ile okuyabiliriz. Blok beş değerin ve üç listenin sınıf adını basar.

```kotlin
// Main.kt — Kotlin degerlerinin JVM siniflari
fun main() {
    val number: Any = 1
    val text: Any = "kotlin"
    println(number.javaClass.name)
    println(text.javaClass.name)
    println(listOf(1, 2).javaClass.name)
    println(listOf(1).javaClass.name)
    println(emptyList<Int>().javaClass.name)
    println(mutableListOf(1).javaClass.name)
    println(intArrayOf(1).javaClass.name)
}
```

```
java.lang.Integer
java.lang.String
java.util.Arrays$ArrayList
java.util.Collections$SingletonList
kotlin.collections.EmptyList
java.util.ArrayList
[I
```

`Any` tipli tutulan `1`, `java.lang.Integer` olarak çıkar: değer bir `Any` değişkenine girince kutulanır. Dizgi `java.lang.String` olarak görünür; Kotlin'in ayrı bir dizgi sınıfı yoktur. `intArrayOf(1)` için sınıf adı `[I` çıkar, bu JVM'in `int[]` gösterimidir.

Listeler daha öğreticidir. `listOf(1, 2)` ifadesi `java.util.Arrays$ArrayList` verir, `listOf(1)` ise `java.util.Collections$SingletonList`. Boş liste için `kotlin.collections.EmptyList` çıkar. `mutableListOf(1)` ise `java.util.ArrayList` döndürür. Mekanizmanın nedeni şudur: `listOf` tek bir sınıf kurmaz, eleman sayısına göre en ucuz gerçekleştirmeyi seçer. Dolayısıyla bir `List` değerinin çalışma anı sınıfına güvenen kod kırılgandır: dört ayrı çağrı dört ayrı sınıf verdi. Eşitlik ise sınıf adına değil elemanlara dayanır; önceki bölümde `listOf` ile `java.util.ArrayList` içerik ölçütüyle eşit çıkmıştı.

Java karşılaştırması: Java'da `int` ile `Integer` kaynak kodda ayrı yazılır ve geliştirici kutulamayı kendisi seçer. Kotlin'de seçimi derleyici yapar. Bu gözlem `Any` tipli değişkenle ölçüldü; `Int?` tipinde de kutulama olacağı bu derste ölçülmedi.

**DT6.** Sınıf adları Kotlin 2.4.21 ve sandbox imajındaki JVM ile ölçüldü; başka bir JVM sürümünde `listOf` aynı iç sınıfları seçmeyebilir.

#### Null ile Karşılaştırma

`==` işleci boş değer alabilen tiplerde de çalışır. Aşağıdaki blok boş bir `Tag?` ile dolu bir `Tag?` arasındaki beş karşılaştırmayı yapar.

```kotlin
// Main.kt — null ile karsilastirma
data class Tag(val name: String)

fun main() {
    val missing: Tag? = null
    val present: Tag? = Tag("a")
    println(missing == null)
    println(missing == present)
    println(present == Tag("a"))
    println(missing === missing)
    val none: String? = null
    println(none == none)
}
```

```
true
false
true
true
true
```

Beş satır sırayla şunları söyler. Boş değer `null` ile eşittir. Boş değer dolu bir nesneyle eşit değildir ve bu karşılaştırma bir hata fırlatmaz. Dolu nesne, aynı kurucu argümanıyla kurulmuş başka bir `Tag` ile eşittir. `===` null için de çalışır. İki boş dizgi birbirine eşittir.

Mekanizma şudur: `a == b` ifadesi, `a` boşsa `b`nin de boş olup olmadığını sorar; `a` doluysa `a.equals(b)` çağrılır. Derleyici bu null denetimini kendisi ekler. Sık yapılan hata, bu denetimi elle yazmaktır: `a != null && a.equals(b)` biçimindeki ifade fazladan söz taşır. Java'da `a.equals(b)` çağrısı `a` boş olduğunda `NullPointerException` fırlatır; Java'da null güvenli karşılaştırma için `Objects.equals` kullanılır. Kotlin'de aynı güvence `==` işlecinin kendisindedir.

**DT7.** Bu beş karşılaştırma `Tag` ve `String` üzerinde ölçüldü; Java tarafındaki `NullPointerException` davranışı ve `Objects.equals` anımsatma olarak yazıldı, bu derste çalıştırılmadı.

#### Ölçümlerden Çıkan Alışkanlıklar

Bu dersin ölçümleri üç alışkanlığa bağlanabilir. Birincisi, bir sınıfın eşitlik davranışını tahmin etmek yerine ölçmektir. `Tag` veri sınıfı olduğu için iki ayrı örneği `==` ile eşit çıktı, ama `===` ile eşit çıkmadı; sıradan bir sınıfta ikisi de `false` verirdi. Bir sınıfı koleksiyonda anahtar olarak kullanacak ya da testte `assertEquals` ile karşılaştıracak kişi, önce bu iki işleç arasındaki farkı bilmelidir. Java'da `==` kimliğe, `equals` içeriğe bakar; Kotlin'de `==` içeriğe, `===` kimliğe bakar. Java'dan gelen biri `==` yazarken kimlik karşılaştırdığını sanabilir ve bu, dilleri ilk geçişte kod okumayı yanıltan bir noktadır.

İkincisi, çalışma anı sınıfına güvenmemektir. `listOf` çağrıları bu derste farklı JVM sınıfları üretti: tek öğeli liste, boş liste ve çok öğeli liste ayrı gerçekleştirimlerle geldi. Bu sınıf adları bir dil sözleşmesi değildir; Kotlin sürümü ya da hedef değiştiğinde değişebilir. Koda `list is ArrayList` gibi bir denetim yazmak, bu yüzden kırılgandır. Sözleşme, `List` arayüzünün kendisidir.

Üçüncüsü, `null` karşılaştırmasında okunur yazımı seçmektir. `==` ile `null` denetimi, nesnenin `equals` yöntemi çağrılmadan önce `null` durumunu ayrı ele alır; bu yüzden `null == null` doğrudur ve `x == null` yazımı `NullPointerException` fırlatmaz. Java'da `x.equals(y)` yazıldığında `x` boşsa istisna oluşur. Kotlin bunu işlecin tanımıyla kapatır ve ölçüm de beş karşılaştırmanın hiçbirinde istisna olmadığını gösterdi.

Bu üç alışkanlığın ortak yanı, ölçülmeden iddia edilmemesidir. Dersin geri kalanı bu ilkeyle ilerler: her iddia ya bir çıktıya dayanır ya da `DT` satırında "ölçülmedi" diye işaretlenir. Okuyucu için pratik sonuç şudur: bir davranıştan emin değilseniz, tek dosyalık küçük bir program yazıp çalıştırmak, belgeyi aramaktan daha kısa sürer. Bu kursta her dersin bir ölçütü olmasının nedeni de budur: ölçüt, hangi soruyu hangi işleçle sorduğunuzu baştan sabitler ve çıktıyı yorumlamayı kolaylaştırır. Veri sınıfı için içerik, sıradan sınıf için kimlik, tekil nesne için tekillik ölçütü geçerlidir. Bir tipin ölçütünü bilmek, o tiple yazılan her karşılaştırmanın ne anlama geldiğini bilmek demektir. Ölçütü yanlış varsayan kod çoğu zaman derlenir ve çalışır; hata ancak iki nesne beklenmedik biçimde eşit ya da farklı çıktığında görünür.

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

Ölçütün sınırı veri sınıfının içinde geçer. Aynı `Tag` sınıfı iki ölçütü birlikte taşır: kurucudaki `name` eşitliğe katılır, gövdedeki `note` katılmaz. `Tag("a")` ile `Tag("a")` içeriktir, `Tag("a")` ile `Tag("b")` eşit değildir; ama `name` aynı kalıp yalnız `note` değişince `==` yine `true` verir, yani kısmi içeriğe düşeriz. Bir sınıfın ölçütü bu yüzden sınıfın adından değil, `equals` davranışını hangi özelliklerden kurduğundan çıkar.

Bu iddiayı iki ölçümde gördük: `veri-sinifi-yapici` içeriktir, `govdedeki-ozellik` kısmi içeriktir.

#### Özet

- Kotlin statik tipli, derlenen bir dildir; birden çok hedefe derlenir ve bu kursun ölçtüğü JVM hedefinde JVM tipleriyle doğrudan çalışır.
- `==` eşitliği, `===` ise nesne kimliğini sorar.
- Eşitlik dört ölçütten birine dayanır: tekil, kimlik, içerik, kısmi içerik.
- Veri sınıfının eşitliği kurucu özelliklerine bakar; gövdedeki özellik eşitliğe katılmaz.
- Bu dersin ölçütü: **içerik**; sınırlayıcı ölçüm: kurucudaki `name` farklı olunca aynı veri sınıfı eşit değildir.

#### Sonraki Adım

Eşitliğin dört ölçütünü gördük. Sıradaki derste en küçük yapıdan başlıyoruz: bir değişkenin adı, tipi ve yeniden atanabilirliği. `val` ile `var` arasındaki fark, aynı değerin iki değişkende nasıl davrandığını da değiştirir mi, onu ölçeriz.

### Değişkenler

Source: https://academia.sh/tr/kurslar/kotlin/degiskenler

`val` ve `var` ile bildirimi, tip çıkarımını ve `const val` sabitini tanıtır; aynı dizgi sabitinin iki değişkende tek nesne olduğunu ölçer.

Önceki derste eşitliğin dört ölçütünü sekiz değer üzerinde gördük. Bu derste değerin bir ada bağlanışına bakıyoruz: `val` ve `var` ile bildirilen değişkenler. Soru şudur: aynı sabiti iki ayrı değişkene bağlarsam iki ayrı nesne mi oluşur, yoksa tek nesneye iki ad mı verilmiş olur?

#### val, var ve Tip Çıkarımı

Kotlin'de bir değişkeni `val` ya da `var` ile bildirirsiniz. `val` yeniden atanamayan değişkendir (read-only): ilk değer verildikten sonra aynı ada başka bir değer atanamaz. `var` ise yeniden atanabilir. İki biçim de değer verirken tipi yazmak zorunda bırakmaz; derleyici sağdaki ifadeden tipi bulur. Bu çıkarım (type inference) derleme zamanında olur ve değişkenin tipi sonradan sabit kalır. Tipi açıkça yazmak isterseniz ad ile değer arasına iki nokta ve tip koyarsınız.

`val` ile bildirilen bir değişkenin bağlı olduğu nesnenin hiç değişmediğini düşünmek yanlış olur: `val` yalnız adın bir daha başka değere bağlanamayacağını söyler. Bağlandığı nesne içinden değişebiliyorsa o değişebilir. Bu ayrım ilerleyen derslerde işe yarayacak; burada yalnız adın yeniden atanıp atanamadığına bakıyoruz.

Bu derste de ölçümler ilk derste tanıtılan yardımcı dosyayla yapılır. `Criterion` sınıfı, bir değer ile ikizini ve farklı bir değeri alıp üç soruyu sorar; dosya aşağıda aynen verilmiştir.

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

Aşağıdaki program bildirimin dört yüzünü bir arada gösterir. `city` yeniden atanamaz, `visits` yeniden atanır, `ratio` tipini açıkça yazar, `inferred` ise tipini çıkarımdan alır. Dosyanın tepesindeki `const val` ise derleme zamanı sabitidir (compile-time constant): değeri derleme sırasında bilinen ve yalnız temel tip ya da dizgi olabilen ve üst düzeyde ya da bir nesne bildirimi içinde duran bir sabit.

```kotlin
// Main.kt — val, var, tip cikarimi ve const val
const val MAX_VISITS = 3

fun main() {
    val city = "Ankara"
    var visits = 1
    visits = visits + 1
    val ratio: Double = 2.0
    val inferred = 7
    println("$city, ziyaret: $visits, oran: $ratio")
    println("cikarilan tip: ${inferred::class.simpleName}")
    println("ust sinir: $MAX_VISITS")
}
```

```
Ankara, ziyaret: 2, oran: 2.0
cikarilan tip: Int
ust sinir: 3
```

Çıktıda `Ankara, ziyaret: 2, oran: 2.0` satırı `visits` değişkeninin bir kez yeniden atandığını gösterir. `cikarilan tip: Int` satırı, `7` sabitinden çıkarılan tipin `Int` olduğunu doğrular; bunu `inferred::class.simpleName` ile çalışma anında sorduk. Son satır `const val` sabitinin değerini basar. Aynı çıktı sandbox'ta Kotlin 2.4.21 ile alındı.

**DT8.** Çıkarılan tip yalnız `7` sabitiyle ölçüldü; `Long` ya da `Double` gerektiren bağlamlarda çıkarımın ne yaptığı bir sonraki dersin konusudur.

**DT9.** `const val` yalnız dosyanın tepesinde ölçüldü; bir `object` ya da `companion object` içindeki hâlinin kuralları bu dersin dışındadır.

Bu çıktıdan çıkan pratik sonuç şudur. `visits` gibi bir sayaç `var` ister, çünkü değeri programın akışında değişir. `city` gibi bir ad ise `val` ister, çünkü ilk atamadan sonra başka değere bağlanması bir hata olurdu. Karar değişkenin rolüne göre verilir; tipin çıkarımla mı yazıldığı bu kararı etkilemez.

#### Yeniden Atama Reddedilir

`val` ile bildirilen bir değişkene ikinci kez değer atamak derleyicide reddedilir. Bunu sandbox'ta derleyerek gördük; aşağıdaki parça bu yüzden çıktısızdır ve bir `main` taşımaz.

```kotlin
val limit = 10
limit = 20
```

Derleyici ikinci satırı `val` yeniden atanamaz diyerek reddeder, program hiç çalışmaz. Bu, yeniden atamayı çalışma anının değil derleme zamanının sorunu yapar. Kotlin'de varsayılan tercih `val` olur; `var` yalnız değerin gerçekten yeniden atanması gerektiğinde yazılır. Böylece bir ada bakan okuyucu, o adın bir yerde başka değere bağlanıp bağlanmadığını aramak zorunda kalmaz.

**DT10.** Reddedilme tek bir yerel `val` ile ölçüldü; üst düzey `val` ve sınıf özelliği olan `val` için hata mesajının biçimi bu derste doğrulanmadı.

#### Aynı Sabit, İki Değişken

Şimdi dersin ölçümü. Dört gözlem var. `val-literal`: iki ayrı `val`, aynı `"kotlin"` sabitine bağlanır. `var-literal`: bir `var` ile aynı sabit karşılaştırılır. `val-kurulan` ve `var-kurulan` ise aynı metni çalışma anında kurar; biri `joinToString` ile, öbürü `buildString` ile. Bildirim biçimi `val` ya da `var` olsa da fark etmeyip etmediğini böylece ölçeriz.

Her gözlemde `Criterion` üç şey alır: ölçülen değer, aynı biçimde kurulmuş bir ikiz ve farklı bir değer. Sabit gözlemlerinde ikiz ikinci bir değişkendir ya da doğrudan sabitin kendisidir. Kurulan gözlemlerde ikiz, aynı yöntem bir kez daha çağrılarak üretilir; böylece iki taraf da çalışma anında ayrı ayrı oluşur. Farklı değer her seferinde bir harfi değiştirilmiş `kotlim` dizgisidir.

```kotlin
// Main.kt — ayni literal iki degiskende
fun main() {
    val first = "kotlin"
    val twin = "kotlin"
    var changeable = "kotlin"
    val built = listOf("kot", "lin").joinToString("")
    var builtVar = buildString {
        append("kot")
        append("lin")
    }
    val checks = listOf(
        Criterion("val-literal", first, twin, "kotlim"),
        Criterion("var-literal", changeable, "kotlin", "kotlim"),
        Criterion("val-kurulan", built, listOf("kot", "lin").joinToString(""), "kotlim"),
        Criterion("var-kurulan", builtVar, buildString {
            append("kot")
            append("lin")
        }, "kotlim"),
    )
    for (check in checks) {
        check.report()
    }
    summarize(checks)
}
```

```
val-literal: ayni-nesne=true, ikiz=true, farkli=false -> tekil
var-literal: ayni-nesne=true, ikiz=true, farkli=false -> tekil
val-kurulan: ayni-nesne=false, ikiz=true, farkli=false -> icerik
var-kurulan: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 2
kimlik: 0
icerik: 2
kismi-icerik: 0
```

`val-literal` tekildir: `first` ile `twin` aynı nesnedir, `ayni-nesne=true`. Aynı dizgi sabiti, derleme sırasında tek bir nesne olarak tutulur ve onu kullanan her ifade aynı nesneye ulaşır. `var-literal` de tekildir; `var` kullanmak sabitin aynı nesne olmasını bozmaz, çünkü `var` yalnız adın yeniden atanabilirliğini etkiler. `val-kurulan` ile `var-kurulan` ise içeriktir: `joinToString` ve `buildString` çalışma anında yeni bir dizgi üretir, bu yüzden `ayni-nesne=false`; ikizle `==` yine `true` verir, farklı değerle `false`.

Özet satırlarında iki tekil, iki içerik görünür. Yani bir değeri `val` ile ya da `var` ile bağlamak ölçütü belirlemez; ölçütü belirleyen, değerin nereden geldiğidir: sabit mi, çalışma anında kurulmuş bir değer mi.

**DT11.** Dizgi sabitinin tek nesne olması Kotlin 2.4.21'in JVM hedefinde ölçüldü; bu davranış JVM'in dizgi havuzuna dayanır ve başka hedeflerde aynı olduğu iddia edilmiyor.

**DT12.** Kurulan dizgiler `joinToString` ve `buildString` ile ölçüldü; ikisi dışındaki kurma yollarının aynı sonucu verdiği bu derste ölçülmedi.

#### val Bağlanan Nesneyi Dondurmaz

Yukarıda `val` için "ad bir daha başka değere bağlanamaz" demiştik. Bu cümlenin sınırı, bağlanan nesnenin kendisinin değişebilmesidir. Aşağıdaki blok `val` ile bildirilmiş bir değişebilir liste kurar ve ona eleman ekler. Ardından ilk değeri bildirim satırında verilmeyen, sonradan atanan bir `val` gösterir.

```kotlin
// Main.kt — val bagli nesneyi degistirebilir, ilk deger sonra verilebilir
fun main() {
    val names = mutableListOf("ada")
    names.add("can")
    println(names)
    val assigned: Int
    val flag = names.size > 1
    if (flag) {
        assigned = 10
    } else {
        assigned = 20
    }
    println(assigned)
}
```

```
[ada, can]
10
```

`names` bir `val`dir; yine de `add` çağrısı listeyi `[ada, can]` hâline getirir. Hata veren bir şey yoktur, çünkü `names` adı hâlâ aynı listeye bağlıdır; değişen listenin içidir. İkinci değişken `assigned` bildirim satırında değer almaz, tipi yazılıdır ve `if` ile `else` dallarının her birinde tam bir kez atanır. Derleyici bu deseni kabul eder ve ekrana `10` basar. Kural şudur: `val` değişken, kapsamının her yolunda en çok bir kez değer alabilir.

Sık yapılan hata, `val` sözcüğünü "nesne değiştirilemez" diye okumaktır. Bir listenin içeriğini dondurmak istiyorsanız salt okunur bir koleksiyon tipi seçilir (`listOf`); `val` tek başına bunu sağlamaz. Java karşılaştırması: Java'daki `final` değişken aynı sınırı taşır, `final` bir listeye eleman eklenebilir ve ilk değerin sonradan verildiği `final` değişken de Java'da bilinen bir desendir. Bu iki Java davranışı burada ölçülmedi, bilinen davranış olarak anıldı.

**DT13.** Bu bölümde `MutableList` ve `Int` ile ölçüldü; `val` bir `data class` örneğine bağlıysa özelliklerinin değişip değişmediği bu derste ölçülmedi.

#### Lambda İçinden var Değiştirmek

Kotlin, bir lambdanın kendi dışındaki `var` değişkenini okumasına ve değiştirmesine izin verir. Aşağıdaki blok bir toplamı ve bir çağrı sayacını `forEach` içinden günceller; ardından toplamın o andaki değerini bir `val` içine kopyalayıp asıl `var` değişkenini değiştirir.

```kotlin
// Main.kt — lambda icinden var degistirmek
fun main() {
    var total = 0
    var calls = 0
    listOf(1, 2, 3).forEach {
        total += it
        calls++
    }
    println("toplam: $total, cagri: $calls")
    val snapshot = total
    total = 100
    println("kopya: $snapshot, var: $total")
}
```

```
toplam: 6, cagri: 3
kopya: 6, var: 100
```

Birinci satır `toplam: 6, cagri: 3` verir: lambda üç kez çalıştı ve dışarıdaki iki değişken de güncellendi. İkinci satır kopyanın ayrı kaldığını gösterir. `snapshot` değeri `6` olarak kalır, `total` ise `100` olur. `Int` değeri atandığı anda kopyalandığı için sonradan değişen `var` kopyaya yansımaz.

Mekanizmanın nedeni lambdanın dışarıdaki değişkeni değer olarak değil, o değişkenin kendisini taşıyarak yakalamasıdır; lambda ve dış kod aynı `var` üzerinde çalışır. Java'da aynı iş için lambdanın yakaladığı yerel değişkenin etkin olarak `final` olması gerekir, bu yüzden sayaç tutmak için dizi ya da `AtomicInteger` gibi bir kap kullanılır. Kotlin'de böyle bir geçici çözüme gerek yoktur. Bu Java kuralı ve Kotlin'in arkada yakalanan değişkeni nasıl taşıdığı bu derste ölçülmedi; ölçülen yalnız dışarıdaki `var` değerlerinin lambda sonrasında güncel olduğudur.

**DT14.** Yakalanan değişken `Int` tipindeydi ve `forEach` ile ölçüldü; çok iş parçacıklı bir bağlamda aynı güncellemenin davranışı bu dersin dışındadır.

#### const val ve Birleşim

Dersin başında `const val` için "değeri derleme sırasında bilinen sabit" dedik. Bu tanımın bir sonucu var: iki `const val` dizgisinin birleşimi de derleme sırasında hesaplanır. Aşağıdaki blok iki `const val` birleşimini, aynı metni çalışma anında `joinToString` ile kuran bir üst düzey `val` ile karşılaştırır. İkizi her iki ölçümde aynı ifadenin bir kez daha yazılmasıyla gelir.

```kotlin
// Main.kt — const val birlesimi ve calisma ani degeri
const val PREFIX = "kot"
const val FULL = PREFIX + "lin"

val runtimeFull = listOf("kot", "lin").joinToString("")

fun main() {
    val checks = listOf(
        Criterion("const-birlesim", FULL, "kotlin", "kotlim"),
        Criterion("calisma-ani-top-level", runtimeFull, "kotlin", "kotlim"),
    )
    for (check in checks) {
        check.report()
    }
    summarize(checks)
}
```

```
const-birlesim: ayni-nesne=true, ikiz=true, farkli=false -> tekil
calisma-ani-top-level: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 1
kimlik: 0
icerik: 1
kismi-icerik: 0
```

`const-birlesim` tekildir: `PREFIX + "lin"` ifadesi derleme zamanında `"kotlin"` sabitine dönüşür ve dizgi havuzundaki tek nesneye bağlanır. `calisma-ani-top-level` içeriktir: aynı metin çalışma anında kurulduğunda ayrı bir dizgi olur, `ayni-nesne=false`. Özet satırında bir tekil ve bir içerik görünür.

Burada sık yapılan hata, `const val` ile üst düzey `val` değişkenini birbirinin yerine kullanılabilir saymaktır. İkisi de dosyanın tepesinde durur ve ikisi de yeniden atanamaz; ama biri değerini derleme sırasında, öbürü program çalışırken alır. Sınır durumu da buradan çıkar: `const val` yalnız derleyicinin hesaplayabildiği değerlerle kurulur. Java karşılaştırması: Java'daki `static final` alan benzer bir ayrımı taşır, derleme zamanı sabiti olan ve olmayan `static final` alanlar vardır. Bu Java ayrımı ölçülmedi.

**DT15.** Bu karşılaştırma iki `const val` dizgisi ve `joinToString` ile ölçüldü; sayısal `const val` birleşimlerinin davranışı bu bölümde ölçülmedi.

#### Ölçümlerden Çıkan Alışkanlıklar

`val` ile `var` arasındaki fark, bu dersin ölçümlerinde bir değişkenin bağlanmasıyla nesnenin içeriği arasındaki fark olarak ortaya çıktı. `val` bir kez bağlanan bir ad demektir; bağlanan nesne değiştirilebilir bir liste ise listeye eleman eklenebilir ve `[ada, can]` çıktısı bunu gösterdi. Dolayısıyla bir `val` için "değiştirilemez" demek dikkat ister: yeniden atanamayan bağlanmadır, nesne değildir. İçeriği korumak için salt okunur bir koleksiyon tipi ya da `data class` içinde `val` alanlar kullanılır.

Lambda içinden bir `var` değiştirildiğinde, sayaç dışarıda da göründü: `toplam: 6, cagri: 3`. Ölçüm, lambdanın ve dış kodun aynı değişkene baktığını gösterdi; bunun altında değişkenin bir kutuya sarılması olduğu bilinir, ama bu derste ölçülmedi. Kopya alınan değer ise ayrı kaldı: `kopya: 6, var: 100` satırında `var` yeniden atandığında kopya etkilenmedi. Sık yapılan hata, bir değeri yakalayıp sonra değişkeni değiştirdiğinde yakalanan değerin de değişeceğini sanmaktır; kopya ile referans arasındaki farkı atamanın nerede yapıldığına bakarak ayırmak gerekir.

`const val` ise derleme anı sabitidir ve yalnız temel tip ya da dizgi değeri alabilir; üst düzeyde, bir nesne bildiriminde ya da yardımcı nesnede tanımlanır. Ölçümde iki `const val` birleşimi tek bir nesne olarak ortaya çıktı, çalışma anında hesaplanan birleşim ise içerik olarak eşit ama ayrı bir değer verdi. Pratik kural şudur: bir değer derleme anında bilinebiliyorsa `const val`, bilinemiyorsa `val` yazılır. Java'da aynı ayrım `static final` ile ifade edilir, ama derleme anı sabiti olup olmadığı alanın başlatma ifadesine bağlıdır ve yazımdan okunmaz.

Bu üç gözlemi tek bir cümleye indirgemek gerekirse: bir adın değişip değişmediğini, adın bağlandığı nesnenin değişip değişmediğinden ayrı sorun. Kodu okurken her `val` gördüğünüzde bu iki soruyu birbirinden ayırmak, hataların büyük kısmını önceden yakalar.

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

Tekil ölçüt sabitle sınırlıdır. Aynı metin çalışma anında kurulduğunda ölçüt içeriğe döner: `val-kurulan` ve `var-kurulan` bunun iki örneğidir. Aynı `"kotlin"` metni, bir yerde sabit olarak yazıldığında tekil, başka bir yerde `joinToString` ya da `buildString` ile kurulduğunda içerik ölçütüne düşer. Bu yüzden kodda metin karşılaştırırken `===` yerine `==` kullanılır; ikincisi her iki durumda da doğru yanıtı verir.

#### Özet

- `val` yeniden atanamayan, `var` yeniden atanabilir değişkendir; ikisinin de tipi çıkarımla bulunabilir.
- `const val` derleme zamanı sabitidir; değeri derleme sırasında bilinmelidir.
- `val` değişkene ikinci kez atama derleyicide reddedilir.
- `val` ya da `var` seçimi eşitlik ölçütünü belirlemez; değerin sabitten mi çalışma anından mı geldiği belirler.
- Bu dersin ölçütü: **tekil**; sınırlayıcı ölçüm: çalışma anında kurulan aynı metin içerik ölçütüne düşer.

#### Sonraki Adım

Bir değişkenin tipi çıkarımla bulunuyordu. Sıradaki derste temel tiplere bakıyoruz: sayılar, karakter ve mantıksal değer. Aynı `1` sayısı bir bağlamda `Int`, başka bir bağlamda `Long` olur; bu iki tipi karıştıran bir `==` neden reddedilir, onu ölçeriz.

### Temel Tipler

Source: https://academia.sh/tr/kurslar/kotlin/temel-tipler

Sayı tiplerini, karakteri ve mantıksal değeri tanıtır; boş değer alabilen `Int` kutulandığında eşitliğin ölçütünün değiştiğini ölçer.

Önceki derste `7` sabitinden çıkarılan tipin `Int` olduğunu gördük. Şimdi temel tiplere bakıyoruz: tam sayı için `Int` ve `Long`, kayan noktalı sayı için `Double`, tek karakter için `Char`, mantıksal değer için `Boolean`. Bu tiplerde eşitlik sorusunun iki yüzü var. Birincisi, farklı tipteki sayılar `==` ile karşılaştırılabilir mi? İkincisi, bir sayı kutulandığında (boxing) aynı değer iki nesneye bölünebilir mi?

#### Tipler ve Açık Dönüşüm

Kotlin'de tam sayı sabiti varsayılan olarak `Int` olur. `Int` sınırını aşan sabit kendiliğinden `Long` olur; `L` soneki küçük bir değeri de `Long` yapmak için yazılır. Ondalıklı sabit `Double` olur. `Char` tek tırnakla yazılır ve sayısal kodunu `.code` ile verir. Dönüşümler açıktır: `Int` bir `Long` değişkene kendiliğinden atanmaz; `count.toLong()` yazılır. Aritmetik ise tipleri karıştırabilir: `Int` ile `Double` toplanınca sonuç `Double` olur.

Bu derste de ölçümler ilk derste tanıtılan yardımcı dosyayla yapılır. `Criterion` sınıfı, bir değer ile ikizini ve farklı bir değeri alıp üç soruyu sorar; dosya aşağıda aynen verilmiştir.

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

Aşağıdaki program bu kuralların hepsini bir arada çalıştırır. `total` değişkeni `Int` değerini `Long`'a çevirip milyarla çarpar. `7 / 2` ile `7 / 2.0` iki bölmeyi karşılaştırır: ilkinde iki taraf tam sayıdır, ikincisinde bir taraf ondalıklıdır. `letter + 1` bir `Char` ile bir sayıyı toplar.

```kotlin
// Main.kt — sayilar, karakter, mantiksal deger ve acik donusum
fun main() {
    val count: Int = 3
    val total: Long = count.toLong() * 1_000_000_000L
    val size = 2.5
    val letter = 'K'
    val ready = count > 2
    println(count + size)
    println(total)
    println(7 / 2)
    println(7 / 2.0)
    println(letter.code)
    println(letter + 1)
    println(ready)
}
```

```
5.5
3000000000
3
3.5
75
L
true
```

`count + size` 5.5 verir: `Int` ile `Double` toplandı ve sonuç `Double` oldu. `total` 3000000000 basar; bu değer `Int` sınırını aştığı için `Long` gerekti. `7 / 2` üç verir, çünkü iki taraf tam sayıdır ve bölme sayı bölmesi olarak yapılır, kesirli kısım atılır. `7 / 2.0` ise 3.5 verir. `letter.code` büyük `K` harfinin kodu olan 75'i basar. `letter + 1` bir sonraki karakter olan `L` harfini verir. Son satır `ready` değişkeninin `true` olduğunu gösterir.

**DT16.** Bu bölümde bölme `7` ve `2` sabitleriyle ölçüldü; negatif sayıda bölmenin davranışı ayrı bir bölümde ölçülür.

**DT17.** `Char` aritmetiği yalnız `letter + 1` ile ölçüldü; iki `Char` arasındaki farkın tipi bu derste ölçülmedi.

#### Dönüşüm Açıktır, Karşılaştırma da

Aritmetiğin tipleri karıştırabilmesi atama ve karşılaştırma için geçerli değildir. `Int` değerini `Long` tipli bir değişkene atamak derleyicide reddedilir; bunu sandbox'ta derleyerek gördük, parça bu yüzden çıktısızdır.

```kotlin
val count: Int = 3
val wide: Long = count
```

Derleyici `Long` beklenen yerde `Int` bulduğunu bildirir. Karşılaştırma da aynı kuralı izler: bir `Int` ile bir `Long` sabitini `==` ile karşılaştırmak yine derlenmez.

```kotlin
val count: Int = 1
println(count == 1L)
```

Derleyici `==` işlecinin `Int` ile `Long` arasında uygulanamayacağını bildirir. Çözüm dönüşümü açık yazmaktır: `count.toLong() == 1L`. Ama tipler `Any` olarak tutulursa durum değişir: derleyici statik tipi bilemez, eşitliği çalışma anındaki nesnelere sorar.

```kotlin
// Main.kt — Any olarak tutulan sayilar
fun main() {
    val a: Any = 1
    val b: Any = 1L
    println(a == b)
}
```

```
false
```

`1` ile `1L`, `Any` olarak tutulduğunda eşit çıkmadı: çıktı `false`. Bir `Int` nesnesi ile bir `Long` nesnesi, sayısal değerleri aynı olsa da farklı sınıfa aittir ve `equals` bu farkı gözetir. Yani derleyicinin reddettiği karşılaştırma, tipi gizlendiğinde reddedilmez ama yine de yanıtı `false` olur.

**DT18.** Derleyici hataları Kotlin 2.4.21 ile alındı; ileri sürümlerde mesaj sözcükleri değişebilir, ama iki karşılaştırmanın reddedileceği kuralı bu derste ölçülen budur.

#### Kutulanan Sayı ve NaN

Dersin ölçümü iki şeye bakar. Birincisi, boş değer alabilen bir tip olan `Int?` kutulanmış bir sayı tutar; JVM'de bu bir `Integer` nesnesidir. İki kutulanmış sayı aynı değerde olsa bile aynı nesne mi? İkincisi, `Double.NaN` (sayı olmayan değer) statik tipe göre farklı eşitlik verir. Önce iki tarafı da `Double` olan karşılaştırma, sonra aynı değerin `Any` olarak tutulduğu karşılaştırma yazdırılır.

```kotlin
// Main.kt — kutulanan sayilar ve NaN
fun main() {
    val boxedBig: Int? = 1000
    val boxedBigTwin: Int? = 1000
    val boxedSmall: Int? = 127
    val boxedSmallTwin: Int? = 127
    val nan = Double.NaN
    val anyNan: Any = nan
    val anyNanTwin: Any = Double.NaN
    println(nan == nan)
    println(anyNan == anyNan)
    val checks = listOf(
        Criterion("kutulanan-1000", boxedBig, boxedBigTwin, 1001),
        Criterion("kutulanan-127", boxedSmall, boxedSmallTwin, 128),
        Criterion("nan-any-tipinde", anyNan, anyNanTwin, 1.0),
    )
    for (check in checks) {
        check.report()
    }
    summarize(checks)
}
```

```
false
true
kutulanan-1000: ayni-nesne=false, ikiz=true, farkli=false -> icerik
kutulanan-127: ayni-nesne=true, ikiz=true, farkli=false -> tekil
nan-any-tipinde: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 1
kimlik: 0
icerik: 2
kismi-icerik: 0
```

İlk iki satır NaN eşitliğidir. `nan == nan`, statik tip `Double` olduğu için IEEE kuralını izler ve `false` verir; NaN kendisine eşit değildir. `anyNan == anyNan` ise `true` verir, çünkü statik tip `Any` olduğunda `==` nesnenin `equals` yöntemini çağırır ve `equals` NaN'ı kendisine eşit sayar. Aynı sayı, hangi tipte tutulduğuna göre iki ayrı yanıt veriyor.

Üç ölçüm satırında `kutulanan-1000` içeriktir: iki `Int?` değişkeni 1000 değerinde ama ayrı nesnelerdir, `ayni-nesne=false`. `kutulanan-127` ise tekildir: aynı değer JVM'de aynı nesneye işaret eder, `ayni-nesne=true`. Tipi aynı, değer biçimi aynı iki sayı, değere göre farklı ölçüte düştü. `nan-any-tipinde` içeriktir: ikizle `==` `true` verir, ayrı nesnelerdir.

**DT19.** 127 ve 1000 JVM'de, Kotlin 2.4.21'de ölçüldü; hangi değerlerin paylaşıldığına dair genel bir sınır bu derste çıkarılmıyor ve 127 ile 1000 dışındaki değerler için bir iddia kurulmuyor.

**DT20.** NaN gözlemi tek bir `Double.NaN` değeriyle ve `Any` tipli tutarak ölçüldü; `Float.NaN` için aynı sonucun çıkması bu derste ölçülmedi.

#### Taşma, Sıfıra Bölme ve Ondalık Hata

Tipin bir sınırı olması, sınırın aşıldığı durumda ne olacağını da bilmeyi gerektirir. Aşağıdaki blok üç sınır durumunu bir arada gösterir: `Int` sınırını aşan toplam, sıfıra tam sayı bölmesi ve sıfıra ondalıklı bölme. Ardından ondalıklı sayıların ikilik gösterimden gelen hatasını `0.1 + 0.2` ile sınar. Taşmayı hata olarak yakalayan `Math.addExact` çağrısı JVM'in kendi yöntemidir; Kotlin onu doğrudan çağırabilir.

```kotlin
// Main.kt — tasma, sifira bolme ve ondalik hata
fun main() {
    val zero = 0
    val big = 3_000_000_000
    println(Int.MAX_VALUE + 1)
    println(Long.MAX_VALUE)
    println(big::class.simpleName)
    try {
        Math.addExact(Int.MAX_VALUE, 1)
    } catch (e: ArithmeticException) {
        println(e.message)
    }
    try {
        println(1 / zero)
    } catch (e: ArithmeticException) {
        println(e.message)
    }
    println(1.0 / 0.0)
    println(0.1 + 0.2)
    println(0.1 + 0.2 == 0.3)
}
```

```
-2147483648
9223372036854775807
Long
integer overflow
/ by zero
Infinity
0.30000000000000004
false
```

`Int.MAX_VALUE + 1` hata vermez, sessizce `-2147483648` olur: sayı en büyük değerden en küçüğe sarar. `Long.MAX_VALUE` için `9223372036854775807` basılır. `3_000_000_000` sabiti `Int` sınırını aştığı için tip çıkarımı onu kendiliğinden `Long` yapar; çıktıdaki `Long` bunu doğrular. `Math.addExact` ise aynı toplamda `integer overflow` iletisiyle hata fırlatır; yani taşmanın fark edilmesini istiyorsanız bu yolu seçersiniz.

Tam sayı sıfıra bölününce `/ by zero` iletisi gelir. Ondalıklı bölmede aynı işlem hata vermez ve `Infinity` döner. Son iki satır ondalıklı sayının sınırını gösterir: `0.1 + 0.2` sonucu `0.30000000000000004` olur ve `0.3` ile `==` karşılaştırması `false` verir.

Sık yapılan hata iki yerde çıkar. Birincisi, taşmanın hata vermeyeceğini unutup toplamı `Int` içinde biriktirmektir. İkincisi, ondalıklı iki sayıyı `==` ile karşılaştırmaktır; para gibi tam çıkması gereken değerlerde ondalıklı tip yanlış seçimdir. Java karşılaştırması: bu çıktılar Java ile aynıdır, çünkü Kotlin burada JVM'in kendi aritmetiğini kullanır; Kotlin bu davranış için ayrı bir tip yazmayı gerektirmez. Bu aynılık Java kodu çalıştırılarak değil, çalışma ortamının aynı olmasına dayanarak söyleniyor.

**DT21.** Taşma `Int` toplamıyla, sıfıra bölme `Int` ve `Double` ile ölçüldü; `Long` taşması ve `Float` için sonucun aynı biçimde çıktığı bu derste ölçülmedi.

#### Daraltan Dönüşümler

Genişleten dönüşüm sorun çıkarmaz; sorun değerin dar bir tipe sığdırıldığı yerde çıkar. Aşağıdaki blok beş dönüşümü ve metinden sayıya çevirmeyi ölçer: `300` değerini `Byte`a, ondalıklı sayıları `Int`e, `65` değerini `Char`a çevirmek ve geçerli ya da geçersiz bir dizgiyi `Int`e çevirmek.

```kotlin
// Main.kt — daraltan donusumler ve metinden sayi
fun main() {
    println(300.toByte())
    println(3.99.toInt())
    println((-3.99).toInt())
    println(65.toChar())
    println("42".toInt() + 1)
    println("x".toIntOrNull())
    try {
        "x".toInt()
    } catch (e: NumberFormatException) {
        println(e.message)
    }
}
```

```
44
3
-3
A
43
null
For input string: "x"
```

`300.toByte()` sekiz bitten fazlasını taşıyamaz ve `44` olur: yalnız alt sekiz bit kalır, üst bitler atılır ve hata verilmez. `3.99.toInt()` yuvarlamaz, kesirli kısmı keser; `3` çıkar. Negatif değerde `(-3.99).toInt()` de `-3` olur, yani sıfıra doğru kesme vardır, aşağı yuvarlama değil. `65.toChar()` büyük `A` harfini verir; sayı ile karakter kodu arasındaki köprü açıktır.

Metinden sayı çevirmenin iki yolu vardır. `"42".toInt() + 1` geçerli metinde `43` verir. Geçersiz metinde ise iki seçenek ayrılır: `toIntOrNull()` hata fırlatmak yerine `null` döndürür, `toInt()` ise `For input string: "x"` iletisiyle bir `NumberFormatException` fırlatır. Kullanıcıdan gelen metin için ilk yol bir `null` denetimi ister ama programı düşürmez, ikinci yol ise düşürür.

Mekanizmanın nedeni dönüşümlerin açık yazılmasıdır. Derleyici dar bir tipe sessizce atama yapmaz; `toByte()` yazmak, bilgi kaybını bilerek kabul ettiğini söyler. Java'da aynı daralma `(byte) 300` biçimindeki dönüştürme (cast) ile yazılır ve çoğu zaman gözden kaçar.

**DT22.** Dönüşümler `Byte`, `Int` ve `Char` ile ölçüldü; `Short`, `Float` ve `Long` için aynı kesme kurallarının geçerli olduğu bu derste ölçülmedi.

#### Negatif Sayıda Bölme ve Mod

İlk bölümde `7 / 2` ifadesinin kesirli kısmı attığını gördük. Peki bölünen negatifse? Aşağıdaki blok negatif bir bölme ve kalan işleminin dört yazılışını, ardından üç başka sınır durumunu bir arada gösterir: en küçük `Int`in mutlak değeri, onaltılık ve ikilik sabit yazımı, iki `Char` arasındaki sıralama.

```kotlin
// Main.kt — negatif sayilarda bolme ve mod
import kotlin.math.abs

fun main() {
    println(-7 / 2)
    println(-7 % 2)
    println((-7).mod(2))
    println(Math.floorMod(-7, 2))
    println(abs(Int.MIN_VALUE))
    println(0xFF)
    println(0b1010)
    println('a' < 'b')
}
```

```
-3
-1
1
1
-2147483648
255
10
true
```

`-7 / 2` sonucu `-3` verir; kesirli kısım sıfıra doğru atılır, aşağıya doğru değil. `-7 % 2` ise `-1` verir: kalanın işareti bölünenin işaretini izler. Kalanın her zaman sıfır ya da pozitif çıkmasını istiyorsanız `(-7).mod(2)` ya da JVM'in `Math.floorMod(-7, 2)` çağrısı gerekir; ikisi de `1` verdi. Sık yapılan hata, bir dizinin indeksini `%` ile dönüştürürken negatif kalanın çıkabileceğini unutmaktır.

`abs(Int.MIN_VALUE)` sonucu `-2147483648` olur; mutlak değer bile pozitif çıkmaz, çünkü `Int` içinde `+2147483648` karşılığı yoktur. `0xFF` sabiti `255`, `0b1010` sabiti `10` basılır; onaltılık ve ikilik sabit yazımı tipin kendisini değiştirmez, yalnız yazılışı değiştirir. `'a' < 'b'` ifadesi `true` verir, çünkü `Char` değerleri karakter koduna göre sıralanır.

Java karşılaştırması: `%` işleci Java'da da bölünenin işaretini izler. Kotlin'in eklediği `mod`, Java'da `Math.floorMod` ile elde edilen sonucu işlecin yanında bir yöntem olarak sunar. Bu iki sonuç da yukarıdaki çıktıda gözlendi.

**DT23.** Negatif bölme ve kalan tek bir çiftle (`-7`, `2`) ölçüldü; negatif bölen ya da `Long` tipi için aynı işaret kuralının geçerli olduğu bu derste ölçülmedi.

#### Ölçümlerden Çıkan Alışkanlıklar

Sayısal tiplerde Kotlin'in güvenceleri sınırlıdır ve bu dersin ölçümleri sınırın nerede başladığını gösterdi. Taşma sessizdir: `Int.MAX_VALUE + 1` çıktısı `-2147483648` oldu ve hiçbir istisna fırlatılmadı. Bu davranış Java ile aynıdır. Taşmanın zararlı olabileceği yerlerde, örneğin para hesabında ya da süre toplamında, `Long` kullanmak ya da `Math.addExact` gibi denetimli işlemler yazmak gerekir. `Long` için aynı taşma çok daha uzak bir sınırda olduğundan çoğu durumda yeterlidir, ama sınırı ortadan kaldırmaz.

Sıfıra bölme tam sayıda istisna fırlatır (`/ by zero`), ondalık sayıda ise `Infinity` verir. Bu fark, bir hesabın tipi değiştirildiğinde hata davranışının da değiştiği anlamına gelir. `Double` kullanan bir kod parçası sıfıra bölündüğünde sessizce çalışmaya devam eder ve sonucu yayar; hatayı fark etmek için `isInfinite` ya da `isNaN` denetimi gerekir. Ondalık sayılar ikili tabanda tutulduğu için `0.1 + 0.2` çıktısı `0.30000000000000004` oldu ve `== 0.3` karşılaştırması `false` verdi. Para gibi tam değer isteyen yerlerde `BigDecimal` ya da kuruşa çevrilmiş tam sayı kullanılır.

Daraltan dönüşümler de dikkat ister. Büyük bir tipten küçüğe çevirmek sayıyı kırpar ya da çevirir ve ölçümde `44`, `3`, `-3`, `A` gibi sonuçlar bunun her zaman "yuvarlama" olmadığını gösterdi: `toInt` ondalık kısmı atar, yani `-3.99` değeri `-3` olur. `toIntOrNull` ise çevrilemeyen dizgi için `null` döndürür, `toInt` aynı durumda istisna fırlatır ve `For input string: "x"` iletisi bu davranışı doğruladı. Kullanıcıdan gelen metni sayıya çevirirken `OrNull` biçimini seçmek, hatayı akışın bir parçası yapar.

Son olarak negatif bölme ve mod ölçümü, sonucun işaretinin Java ile aynı kuralı izlediğini gösterdi: bölme sıfıra doğru yuvarlanır, kalan ise bölünenin işaretini taşır. Matematikteki mod ile program mod işlemi aynı şey değildir; negatif sayılarla çalışan bir döngüsel indeks hesabında `mod` yerine `%` kullanan kod, indeksi eksiye çıkarabilir.

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

Kutulanan sayı için ölçüt değere bağlıdır: 1000 içerik, 127 tekil. Bu yüzden sayıların kimliğini `===` ile karşılaştırmak güvenilmez; sayı karşılaştırması `==` ile yapılır. Aynı değer 127 iken tekil, 1000 iken içerik çıktı. Tek bir ölçüt yerine iki ölçüt gördük.

NaN ise ayrı bir sınır koyar: statik tipi `Double` olan karşılaştırmada NaN eşit değildir, `Any` tipinde eşittir. `==` işlecinin anlamı, işlecin soldaki ifadenin statik tipine bağlı olduğu bir yerde değişir.

#### Özet

- Tam sayı sabiti `Int` olur; sınırı aşan değer `Long` ister. Ondalıklı sabit `Double` olur.
- `Int` değeri `Long` değişkene kendiliğinden atanmaz ve bir `Int` ile bir `Long` sabiti `==` ile karşılaştırılamaz.
- Aritmetik tipleri karıştırabilir: `Int` ile `Double` toplamı `Double` olur.
- Boş değer alabilen `Int` kutulanır; 127 tekil, 1000 ise içerik çıktı.
- Bu dersin ölçütü: **içerik** (1000); sınırlayıcı ölçüm: 127 değeri JVM'de tekildir ve NaN statik tipe göre farklı eşitlik verir.

#### Sonraki Adım

Sayıları ve karakteri gördük. Sıradaki derste dizgilere dönüyoruz: şablonlar, ham dizgiler ve `trimIndent`. Bir dizgiyi şablonla kurmak, aynı metni elle yazmakla aynı nesneyi mi verir, onu ölçeriz.

### Dizgi Şablonları

Source: https://academia.sh/tr/kurslar/kotlin/dizgi-sablonlari

Dizgi şablonlarını, ham dizgileri ve `trimIndent` fonksiyonunu tanıtır; şablonla kurulan dizginin içerik ölçütüne düştüğünü, tek değişkenli şablonun ise tekil kaldığını ölçer.

Sayı tiplerinde otomatik kutulama, aynı değerin iki nesneye bölünebileceğini gösterdi. Dizgiler bu soruyu başka bir yerden sorar: bir metni şablonla kurduğumda, yazdığım metin ile aynı nesneyi mi elde ederim? Önce şablonun kendisine bakıyoruz: nasıl yazılır, ham dizgiyle nasıl birleşir ve çıktıda neyi değiştirir. Ardından aynı şablonları eşitlik ölçüsünden geçiriyoruz ve tek bir yazım biçiminin iki ölçüte düştüğü yeri arıyoruz.

#### Şablon, Ham Dizgi ve trimIndent

Kotlin'de çift tırnaklı bir dizgi içine değer yerleştirmek için `$` işareti kullanılır. `$ad` bir değişkenin değerini, `${ifade}` ise bir ifadenin sonucunu dizgiye koyar. İfade biçimi bir fonksiyon çağrısı ya da bir özellik erişimi de olabilir: `${items.size}`, `${items.first().uppercase()}`. Dizgi içinde `$` karakterini kendi anlamıyla yazmak isterseniz önüne ters bölü koyarsınız: `\$5`.

Üç çift tırnakla başlayan dizgi ham dizgidir (raw string). Satır sonları ve girintiler olduğu gibi korunur, kaçış dizileri çalışmaz, ama şablonlar çalışır. Ham dizgi çok satırlı olduğunda her satırın başındaki girinti metnin parçası olur; `trimIndent` fonksiyonu ortak girintiyi siler ve baştaki ile sondaki boş satırı atar.

Bu derste de ölçümler ilk derste tanıtılan yardımcı dosyayla yapılır. `Criterion` sınıfı, bir değer ile ikizini ve farklı bir değeri alıp üç soruyu sorar; dosya aşağıda aynen verilmiştir.

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

Aşağıdaki program bu üç şeyi birlikte çalıştırır.

```kotlin
// Main.kt — sablonlar, ham dizgi ve trimIndent
fun main() {
    val name = "Zeynep"
    val items = listOf("elma", "armut")
    println("Merhaba, $name")
    println("Oge sayisi: ${items.size}, ilki: ${items.first().uppercase()}")
    println("Fiyat: \$5")
    val text = """
        ad: $name
        ogeler: ${items.joinToString()}
    """.trimIndent()
    println(text)
}
```

```
Merhaba, Zeynep
Oge sayisi: 2, ilki: ELMA
Fiyat: $5
ad: Zeynep
ogeler: elma, armut
```

İlk satır tek bir değişkeni yerleştirir: `Merhaba, Zeynep`. İkincisi iki ifade yerleştirir: `items.size` listenin uzunluğunu verir, `items.first().uppercase()` ilk elemanı büyük harfe çevirir; çıktıda `Oge sayisi: 2, ilki: ELMA` görünür. Üçüncü satırda ters bölü, `$5` metnini şablon olarak yorumlanmaktan kurtarır ve `Fiyat: $5` basılır. Ham dizgi dört satırlık kaynak koddan iki satırlık bir metin üretir: girinti `trimIndent` ile silinir ve çıktıda `ad: Zeynep` ile `ogeler: elma, armut` satırları hizalı görünür. `items.joinToString()` eleman listesini virgülle ayırarak tek metne çevirir.

Programdaki üç yazım biçimi üç ayrı iş görür. Düz `$ad` en kısa yazımdır ve yalnız bir adın değerini koyar. Süslü parantezli biçim, ad tek başına yetmediğinde ya da şablonun hemen ardından harf geldiğinde gerekir; çünkü parantez, adın nerede bittiğini açıkça gösterir. Ham dizgi ise kaçış dizilerinden kaçınılması gereken çok satırlı metinlerde, örneğin bir yapılandırma örneğinde ya da biçimli bir ileti taslağında işe yarar. `trimIndent` kaynak kodu girintili tutmanıza izin verir; çıktıdaki metin ise sol kenara yaslı kalır.

**DT24.** Şablon içinde yalnız basit ifadeler ölçüldü; iç içe şablon ya da dizgi içinde dizgi içeren bir ifadenin davranışı bu dersin dışındadır.

**DT25.** `trimIndent` ortak girintiyi siler; girintisi düzensiz satırlarda ne yaptığı bu derste ölçülmedi.

#### Şablonla Kurulan Dizginin Ölçütü

Dersin sorusuna dönüyoruz. Önceki derste aynı dizgi sabiti iki değişkende tek nesne oldu ve tekil çıktı. Şablonla kurulan dizgi için üç gözlem yazıyoruz. `sablon-ile-kurulan`: `"dil: $name"` ile `"dil: $name"`, yani iki ayrı şablon ifadesi. `tek-degiskenli-sablon`: yalnız `"$name"` biçiminde, başka metin içermeyen şablon, `name` değişkenine karşı. `sablon-ve-ek`: `"$name."`, yani değişken artı sabit bir ek.

İkinci derste `joinToString` ve `buildString` ile kurulan dizgilerin içerik çıktığını görmüştük. Şablon da çalışma anında bir dizgi kuran bir yazım biçimidir; bu yüzden aynı sonucu bekleriz. Beklentiyi ölçüyoruz, çünkü şablonun derleyici tarafından nasıl işlendiği bir dil kuralı değil, bir uygulama ayrıntısıdır ve ayrıntı yazıdan çıkarılamaz. Her gözlemde ikiz, aynı şablonun ikinci kez yazılmasıyla ya da sabitle elde edilir; farklı değer ise `kotlim` harf değişikliğini taşır.

```kotlin
// Main.kt — sablonla kurulan dizgi
fun main() {
    val name = "kotlin"
    val checks = listOf(
        Criterion("sablon-ile-kurulan", "dil: $name", "dil: $name", "dil: kotlim"),
        Criterion("tek-degiskenli-sablon", name, "$name", "kotlim"),
        Criterion("sablon-ve-ek", "$name.", "$name.", "kotlim."),
    )
    for (check in checks) {
        check.report()
    }
    summarize(checks)
}
```

```
sablon-ile-kurulan: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tek-degiskenli-sablon: ayni-nesne=true, ikiz=true, farkli=false -> tekil
sablon-ve-ek: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 1
kimlik: 0
icerik: 2
kismi-icerik: 0
```

`sablon-ile-kurulan` içeriktir. İki şablon ifadesi iki ayrı dizgi üretir; `ayni-nesne=false`, ama `==` ile eşittirler ve farklı metinle eşit değildirler. `sablon-ve-ek` de içeriktir; bir değişkeni sabit bir ekle birleştiren şablon her çalıştığında yeni bir nesne kurar.

`tek-degiskenli-sablon` ise tekildir: `"$name"` yazıldığında elde edilen nesne, `name` değişkeninin işaret ettiği nesnenin kendisidir, `ayni-nesne=true`. Başka bir metin eklenmeyen şablon, yeni bir dizgi kurmaz.

Bu üç gözlemin ortak noktası, hepsinin aynı `name` değişkenini kullanmasıdır. Değişken tek, şablon biçimi değişiyor. Bu seçim bilinçlidir: ölçütü etkileyen şeyin değişkenin değeri değil, şablonun biçimi olduğunu görmek istiyoruz. `sablon-ve-ek` içindeki nokta, adın hemen ardından gelen sabit bir metindir; şablon bu noktayı adın parçası saymaz, yalnız `name` değişkenini yerleştirir ve noktayı olduğu gibi bırakır.

**DT26.** Bu üç gözlem Kotlin 2.4.21 ile ölçüldü; `"$s"` ifadesinin yeni bir nesne kurmadığı derleyicinin bu sürümdeki davranışıdır ve başka sürümlerde aynı olacağı iddia edilmiyor.

**DT27.** `name` değişkeni burada bir dizgi sabitine bağlı bir `val`; değişkenin çalışma anında kurulmuş bir dizgiye bağlı olduğu hâlde aynı sonucun çıkması ölçülmedi.

Bu sonuç dizgilerle çalışırken bir alışkanlık önerir. Bir metni başka bir metne eşitlik için karşılaştırırken, metnin sabitten mi şablondan mı geldiğini düşünmek gerekmez; `==` her ikisinde de metne bakar. Metnin hangi nesne olduğu ancak `===` sorulduğunda ortaya çıkar ve o zaman da yanıt şablonun içinde başka bir şey olup olmadığına göre değişir.

#### Dolar İşareti Ne Zaman Şablon Başlatır

Yukarıda `\$5` ile dolar işaretinin kaçırılabildiğini gördük. Peki her `$` kaçış ister mi? Aşağıdaki blok yedi yazımı arka arkaya basar: rakamla biten bir `$`, boşlukla çevrili bir `$`, dizginin sonundaki bir `$`, iki ayrı kaçış yolu ve değişken adının hemen ardına yazılan nokta.

```kotlin
// Main.kt — dolar isareti ne zaman sablon baslatir
fun main() {
    val name = "kotlin"
    println("fiyat: $5")
    println("a $ b")
    println("son: $")
    println("${'$'}name")
    println("\$name")
    println("$name.length")
    println("${name.length}")
}
```

```
fiyat: $5
a $ b
son: $
$name
$name
kotlin.length
6
```

İlk üç satırda `$` olduğu gibi basıldı: `$5`, `a $ b` ve sonda duran `$`. Kural şudur: `$` işaretinden sonra bir tanımlayıcının ilk harfi gelirse şablon başlar; rakam, boşluk ya da dizgi sonu gelirse `$` sıradan bir karakterdir. Bu yüzden `"fiyat: $5"` kaçış istemeden çalıştı. Dördüncü ve beşinci satır, adın başında `$` isteyen metni yazmanın iki yolunu verir: `"${'$'}name"` ve `"\$name"` aynı `$name` metnini basar.

Altıncı satır sık yapılan hatayı gösterir. `"$name.length"` yazan kişi 6 bekler, ekrana `kotlin.length` çıkar: düz `$ad` biçimi yalnız adı alır, noktadan sonrası metin olarak kalır. Özelliğe erişmek için `"${name.length}"` yazılır ve son satır `6` verir. Mekanizmanın nedeni düz biçimin ifade değil yalnız ad okumasıdır; süslü parantez ifadenin sınırını açıkça belirler.

Java karşılaştırması: Java'da dizgi içine değer koymak için `+` ile birleştirme, `String.format` ya da `formatted` kullanılır; `$` bir özel karakter değildir. Java'dan Kotlin'e geçen biri `$` işaretini her zaman kaçıracağını sanabilir. Bu gözlem yalnız Kotlin tarafını ölçer.

**DT28.** Bu yedi yazım tek bir `name` değişkeniyle ölçüldü; `$` ardından Unicode harf gelen adların nasıl ayrıştığı bu derste ölçülmedi.

#### Dizgi Değiştirilemez

Kotlin'de `String` değiştirilemez: hiçbir yöntem var olan nesnenin içeriğini değiştirmez. Değişikliği yapan yöntemler yeni bir dizgi döndürür. Aşağıdaki blok önce `uppercase` ile bunu gösterir, sonra dört yöntemi eşitlik ölçüsünden geçirir. Soru şudur: bir yöntem bir şey değiştirmediğinde yeni bir nesne mi kurar, yoksa eldekini mi döndürür?

```kotlin
// Main.kt — dizgi degistirilemez, yontemler yeni nesne dondurebilir
fun main() {
    val text = "kotlin"
    val upper = text.uppercase()
    println(text)
    println(upper)
    val checks = listOf(
        Criterion("replace-eslesme-yok", text, text.replace("x", "y"), "kotlim"),
        Criterion("replace-eslesme-var", text, text.replace("k", "k"), "kotlim"),
        Criterion("uppercase-lowercase", text, text.uppercase().lowercase(), "kotlim"),
        Criterion("trim-bosluksuz", text, text.trim(), "kotlim"),
    )
    for (check in checks) {
        check.report()
    }
    summarize(checks)
}
```

```
kotlin
KOTLIN
replace-eslesme-yok: ayni-nesne=true, ikiz=true, farkli=false -> tekil
replace-eslesme-var: ayni-nesne=false, ikiz=true, farkli=false -> icerik
uppercase-lowercase: ayni-nesne=false, ikiz=true, farkli=false -> icerik
trim-bosluksuz: ayni-nesne=true, ikiz=true, farkli=false -> tekil
tekil: 2
kimlik: 0
icerik: 2
kismi-icerik: 0
```

İlk iki satır değiştirilemezliği doğrular. `text` hâlâ `kotlin` iken `upper` ayrı bir nesne olarak `KOTLIN` değerini taşır. Ölçüm satırları ise iki ölçüte ayrıldı. `replace-eslesme-yok` tekildir: `"x"` metnini `"y"` ile değiştirmeye çalışan `replace`, eşleşme bulmayınca aynı nesneyi döndürür. `trim-bosluksuz` de tekildir: kırpılacak boşluğu olmayan dizgi için `trim` kendi nesnesini döner. `replace-eslesme-var` içeriktir: eşleşme bulunduğunda, yerine konan metin aynı olsa bile yeni bir dizgi kurulur. `uppercase-lowercase` de içeriktir, çünkü iki dönüşüm ardışık yeni nesneler üretir.

Bu, ikinci derste gördüğümüz kuralı genişletir: ölçütü belirleyen değerin nereden geldiğidir. Burada kaynak bir yöntemdir ve yöntem iş yapmadığında girdisini döndürür. Bu davranış dilin güvencesi değil, ölçülen bir sonuçtur; `===` ile test eden kod bu yüzden kırılgandır. Java'da `String` de değiştirilemez ve benzer biçimde yöntemler yeni dizgi döndürür, ama bu bölümde Java tarafı ölçülmedi.

**DT29.** Dört yöntem tek bir dizgiyle ölçüldü; `trim` dışındaki `trimStart`, `trimEnd` ve `substring` için aynı "iş yoksa aynı nesne" davranışının geçerli olduğu bu derste ölçülmedi.

#### Uzunluk, Kaçış ve UTF-16

`length` bir dizgideki karakter sayısını verir, ama "karakter" sözcüğü burada tuzaklıdır. Aşağıdaki blok kaçış dizili bir dizgiyi ham eşiyle karşılaştırır, ardından bir emoji karakterinin uzunluğunu ve kod noktası sayısını sorar. Sonda ham dizginin `trimMargin` ile satır başlarından kırpılması gösterilir.

```kotlin
// Main.kt — uzunluk, kacis dizisi ve UTF-16
fun main() {
    val escaped = "a\nb"
    val raw = """a\nb"""
    println(escaped.length)
    println(raw.length)
    val face = "\uD83D\uDE00"
    println(face.length)
    println(face.codePointCount(0, face.length))
    val margin = """
        |satir bir
        |satir iki
    """.trimMargin()
    println(margin.lines().size)
    println(margin.lines().last())
}
```

```
3
4
2
1
2
satir iki
```

`"a\nb"` dizgisinin uzunluğu `3` çıkar: `\n` tek bir satır sonu karakteridir. Aynı yazım ham dizgide `4` verir, çünkü ham dizgide kaçış çalışmaz ve ters bölü ile `n` ayrı iki karakter olarak sayılır. Kaçışın çalışıp çalışmadığı bu iki sayıdan okunur.

Emoji için `length` `2` verir, `codePointCount` ise `1`. Kotlin dizgileri UTF-16 kodlamasıyla tutulur ve bu karakter iki kod birimine (surrogate pair) ihtiyaç duyar. `length` kod birimi sayar, kod noktası saymaz. Sık yapılan hata, kullanıcıya gösterilen "karakter sayısı" için `length` kullanmaktır; emoji içeren metinde sayı yanlış çıkar. Java'da `String.length()` aynı kod birimi sayısını verir; bu Kotlin dizgisinin altında JVM dizgisi olmasından gelir.

Son iki satır `trimMargin` sonucudur. `|` işaretiyle başlayan iki satırdan iki satır kalır ve ikincisi `satir iki` olarak okunur. `trimIndent` ortak girintiyi siler, `trimMargin` ise her satırda `|` işaretine kadar olan kısmı atar; girintisi düzensiz metinde ikincisi daha öngörülebilirdir. Bu karşılaştırma yalnız bu iki satırlık örnekle ölçüldü.

**DT30.** Uzunluk ölçümü bir emoji ve bir kaçış dizisiyle yapıldı; birleşik karakterler (örneğin harf ile ayrı aksan işareti) için `length` sonucunun ne olduğu bu derste ölçülmedi.

#### Ölçümlerden Çıkan Alışkanlıklar

Şablon yazımında en sık hata, düz `$ad` biçimini ifade yazımıyla karıştırmaktır. Kural basittir: özelliğe, yönteme ya da herhangi bir işleme ihtiyaç varsa süslü parantez kullanılır. Ölçümde `"$name.length"` çıktısı `kotlin.length` oldu; bu hata derleme sırasında yakalanmaz çünkü yazım geçerlidir ve sonuç yalnızca yanlış görünür. Bu tür hatalar testte değil, çıktıya bakınca fark edilir. Bu yüzden şablonlu mesajlar yazarken, noktadan sonra gelen her şeyin süslü parantezin içinde olması bir alışkanlık hâline getirilmelidir.

Dizgilerin değiştirilemez olması iki sonuç doğurur. Birincisi, `uppercase` gibi bir yöntemin sonucunu bir değişkene atamayı unutmak, değişikliğin kaybolması demektir: `text.uppercase()` satırını tek başına yazan kod hiçbir şey yapmaz. İkincisi, bir döngüde dizgiyi `+` ile büyütmek her adımda yeni bir nesne üretir; çok adımlı birleştirmede `StringBuilder` ya da `buildString` kullanılır. Java'da da aynı kural geçerlidir ve Kotlin burada ayrı bir güvence getirmez.

Uzunluk konusunda ise "karakter" sözcüğüne güvenmemek gerekir. `length` UTF-16 kod birimi sayar; bu yüzden emoji ve bazı yazı sistemlerindeki karakterler iki birim sayılır. Kullanıcıya "en çok 20 karakter" sınırı gösteren bir form, bu sayıyı `length` ile denetlerse emoji kullanan biri için sınırı yanlış uygular. Kod noktası sayısı gerekiyorsa `codePointCount` kullanılır; kullanıcının algıladığı karakter (grafem) sayısı ise ayrı bir kütüphane gerektirir ve bu derste ölçülmedi.

Ham dizgilerde kaçış çalışmaz; bu yüzden dosya yolları, düzenli ifadeler ve çok satırlı metinler için uygundur, ama `\n` yazan kişi satır sonu elde etmez. Ölçümde aynı yazım ham dizgide dört, kaçışlı dizgide üç karakter verdi. Hangisinin kullanıldığını sayıdan okuyabilmek, hata ayıklarken işe yarar.

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

Şablonla kurulan dizginin ölçütü içeriktir, ama bu genelleme tek değişkenli şablonu dışarıda bırakır. `"$name"` yalnız bir değişkeni yerleştirdiğinde tekil çıktı; metin ya da ek geldiği anda içeriğe döndü. Aynı yazım biçimi, yani şablon, iki ölçüte düşebiliyor ve ayrım şablonun içinde bir şeyin daha olup olmadığına bakıyor.

Buradan bir kural çıkarılabilir: dizgileri `===` ile karşılaştırmak yerine `==` kullanın. Sınırlayıcı ölçüm bu kuralın gerekçesini gösterir: aynı şablon biçimi tekil de verebilir, içerik de verebilir; `==` her iki durumda da doğru yanıtı verir.

#### Özet

- `$ad` ve `${ifade}` bir değeri dizgiye yerleştirir; dizgide `$` karakterini yazmak için `\$` kullanılır.
- Üç tırnaklı ham dizgi satır sonlarını korur; `trimIndent` ortak girintiyi siler.
- İki ayrı şablon ifadesi aynı metni üretse de ayrı nesnelerdir.
- Yalnız bir değişken yerleştiren `"$name"` şablonu, Kotlin 2.4.21'de, değişkenin kendi nesnesini verir.
- Bu dersin ölçütü: **içerik**; sınırlayıcı ölçüm: tek değişkenli şablon tekildir.

#### Sonraki Adım

Dizgileri kurmayı gördük. Sıradaki derste programın akışına geçiyoruz: `if` ve `when` ifadeleri, `for` döngüleri ve aralıklar. `when` bir değeri dalla eşleştirirken `==` kullanır; hangi örneğin dalı tutturamadığını ölçeriz. O derste kimlik ölçütü yeniden karşımıza çıkacak ve `is` denetiminin `==` ile aynı soruyu sormadığını göreceğiz.

### Denetim Akışı

Source: https://academia.sh/tr/kurslar/kotlin/denetim-akisi

`if` ve `when` ifadelerini, `is` denetimini ve `for` döngüsünü tanıtır; `when` dalının `==` ile eşleştiğini ve sıradan sınıf örneğinde tutmadığını ölçer.

Dizgi şablonlarında aynı metnin iki biçimde kurulabildiğini gördük. Bu derste akışa geçiyoruz: bir koşula göre değer seçmek, bir değeri dallara dağıtmak, bir aralığı dolaşmak. Sonunda kimlik ölçütünü yeniden buluyoruz: `when` bir değeri bir dalla eşleştirirken `==` kullanır ve eşitliği olmayan bir örnek dalı tutturamaz.

#### if, when ve for

Kotlin'de `if` bir ifadedir (expression): bir değer üretir ve o değer bir değişkene atanabilir. Ayrı bir üçlü işleç gerekmez; `val label = if (score >= 50) "gecti" else "kaldi"` yazılır. `when` de bir ifadedir. Koşulsuz biçimde her dal bir mantıksal ifade taşır ve ilk doğru dal çalışır. Bir değere bağlı biçimde ise `when (value)` yazılır; her dal ya bir değerle ya da `is` ile bir tip denetimiyle başlar. `when` ifade olarak kullanıldığında derleyici bütün durumların kapsandığından emin olmak ister: öznesiz biçimde `else` dalı gerekir; özneli biçimde ise `Boolean`, numaralandırma ya da mühürlü sınıf tüm durumlarıyla sayılmışsa `else` zorunlu değildir.

`for` döngüsü bir aralığı ya da bir koleksiyonu dolaşır. Aralık `1..3` kapalı bir aralıktır: üst sınır dahildir. `1 until 7`, üst sınırı dışarıda bırakır. `step` adımı belirler ve `3 downTo 1` geriye sayar.

Bu derste de ölçümler ilk derste tanıtılan yardımcı dosyayla yapılır. `Criterion` sınıfı, bir değer ile ikizini ve farklı bir değeri alıp üç soruyu sorar; dosya aşağıda aynen verilmiştir.

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

Aşağıdaki program bunları bir arada çalıştırır. `grade` koşulsuz bir `when` ile bir puanı sözcüğe çevirir. `describe` ise `Any` bir değeri `is` ile tipine göre ayırır: `Int` ise iki katını, `String` ise uzunluğunu söyler.

```kotlin
// Main.kt — if, when, is ve for
fun grade(score: Int): String = when {
    score >= 90 -> "pekiyi"
    score >= 70 -> "iyi"
    else -> "gecer"
}

fun describe(value: Any): String = when (value) {
    is Int -> "tam sayi, iki kati ${value * 2}"
    is String -> "dizgi, uzunluk ${value.length}"
    else -> "bilinmeyen"
}

fun main() {
    val score = 82
    val label = if (score >= 50) "gecti" else "kaldi"
    println("$label, ${grade(score)}")
    println(describe(21))
    println(describe("kotlin"))
    println((1..3).joinToString(" "))
    println((1 until 7 step 2).joinToString(" "))
    println((3 downTo 1).joinToString(" "))
}
```

```
gecti, iyi
tam sayi, iki kati 42
dizgi, uzunluk 6
1 2 3
1 3 5
3 2 1
```

İlk satırda `label` değişkeni `if` ifadesiyle `gecti` değerini aldı ve `grade(82)` çağrısı `iyi` döndürdü, çünkü 82 doksanın altında, yetmişin üstündedir. İkinci ve üçüncü satırlar `describe` fonksiyonunun iki dalını gösterir: `21` bir `Int` olduğu için iki katı olan 42 basıldı, `"kotlin"` bir `String` olduğu için uzunluğu olan 6 basıldı. Bu dalların içinde `value` değişkenine tipin yeni hâliyle erişildi; bu özellik akıllı tip dönüşümü (smart cast) adını taşır. Son üç satır aralıklardır: `1..3` üçünü de verir, `1 until 7 step 2` bir, üç, beş değerlerini verir, `3 downTo 1` geriye sayar.

**DT31.** `when` ifadesi tam sayı ve dizgi dallarıyla ölçüldü; numaralandırma ve `Boolean` üzerindeki kapsayıcı `when` aşağıda ölçülür, mühürlü sınıf (sealed class) üzerindeki denetim bu dersin dışındadır.

**DT32.** Aralıklar yalnız `joinToString` ile yazdırıldı; boş bir aralığın döngüde ne yaptığı aşağıda ayrıca ölçülür.

#### when Dalı == ile Eşleşir

Dersin ölçümü `when` dalının eşleşme kuralına bakar. `when (value)` içinde bir dal ifadesi yazıldığında Kotlin, `value == dal` karşılaştırmasını yapar. Bu yüzden dalın tutup tutmaması eşitlik ölçütüne bağlıdır. `branchMatches` fonksiyonu bunu doğrudan sorar: aldığı `value` değerini `branch` ile `when` üzerinden eşleştirir ve `true` ya da `false` döndürür.

Üç durum deneniyor. Birincisi `Point(1, 2)` ile `Point(1, 2)`: veri sınıfı, yani içerik ölçütü. İkincisi `Box(1)` ile `Box(1)`: sıradan sınıf, yani kimlik. Üçüncüsü aynı `box` örneğinin kendisiyle eşleşmesi. Ardından aynı `Box(1)` değerine `is Box` dalı verilir. Son olarak `Criterion` üç gözlemi sınıflandırır.

```kotlin
// Main.kt — when dali == ile eslesir
class Box(val size: Int)

data class Point(val x: Int, val y: Int)

fun branchMatches(value: Any, branch: Any): Boolean = when (value) {
    branch -> true
    else -> false
}

fun main() {
    val box = Box(1)
    println(branchMatches(Point(1, 2), Point(1, 2)))
    println(branchMatches(Box(1), Box(1)))
    println(branchMatches(box, box))
    println(when (Box(1)) {
        is Box -> "is Box dali tutar"
        else -> "tutmaz"
    })
    val checks = listOf(
        Criterion("when-dali-box", Box(1), Box(1), Box(2)),
        Criterion("when-dali-point", Point(1, 2), Point(1, 2), Point(1, 3)),
        Criterion("aralik", 1..3, 1..3, 1..4),
    )
    for (check in checks) {
        check.report()
    }
    summarize(checks)
}
```

```
true
false
true
is Box dali tutar
when-dali-box: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
when-dali-point: ayni-nesne=false, ikiz=true, farkli=false -> icerik
aralik: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 0
kimlik: 1
icerik: 2
kismi-icerik: 0
```

`branchMatches(Point(1, 2), Point(1, 2))` `true` verdi: veri sınıfında `==` içeriğe bakar, dal tuttu. `branchMatches(Box(1), Box(1))` ise `false` verdi: iki `Box(1)` aynı kurucu argümanını taşısa da ayrı nesnedir ve sıradan sınıfta `==` kimliğe bakar. Aynı `box` örneği kendisiyle eşleşti, çünkü bir nesne kendisine eşittir. `is Box` dalı ise tipe bakar, değere değil: yeni kurulmuş bir `Box(1)` bile `is Box` dalını tutturur. Bu yüzden `is Box dali tutar` satırı basıldı.

Üç sınıflandırma satırı bunu özetler. `when-dali-box` kimliktir: ikiz ile `==` `false` verir. `when-dali-point` içeriktir: ikizle `==` `true`, farklı değerle `false` verir. `aralik` de içeriktir: `1..3` aralığı, ayrı kurulmuş başka bir `1..3` aralığına eşittir ve `1..4` aralığına eşit değildir. Özet satırlarında sıfır tekil, bir kimlik, iki içerik ve sıfır kısmi içerik görünür.

**DT33.** `branchMatches` fonksiyonu dalı bir değişken olarak alır; `when` içinde sabit bir dal yazılması durumunda aynı `==` kuralının geçerli olduğu bu derste ayrıca ölçülmedi.

**DT34.** Aralık eşitliği `IntRange` ile ölçüldü; `LongRange` ve `CharRange` için aynı davranış iddia edilmiyor.

#### when ile Aralık, Çok Değerli Dal ve Numaralandırma

`when` dalları yalnız tek bir değerle sınırlı değildir. Aşağıdaki blok üç şeyi sorar: virgülle ayrılmış çok değerli dal, `in` ile aralık dalı ve aralığın dışını yakalayan dal birlikte kullanılınca hangi sıra ile değerlendirilir; numaralandırma (enum) üzerindeki `when` `else` olmadan derlenir mi; ve `Boolean` üzerindeki `when` iki değerle kapanır mı.

```kotlin
// Main.kt — when: cok degerli dal, aralik ve numaralandirma
enum class Level { LOW, MEDIUM, HIGH }

fun classify(n: Int): String = when (n) {
    0 -> "sifir"
    1, 2, 3 -> "az"
    in 4..9 -> "orta"
    !in 0..99 -> "aralik disi"
    else -> "cok"
}

fun weight(level: Level): Int = when (level) {
    Level.LOW -> 1
    Level.MEDIUM -> 5
    Level.HIGH -> 10
}

fun main() {
    println(listOf(0, 2, 7, 50, 120).map { classify(it) })
    println(Level.entries.map { weight(it) })
    val flag = true
    println(when (flag) {
        true -> "evet"
        false -> "hayir"
    })
}
```

```
[sifir, az, orta, cok, aralik disi]
[1, 5, 10]
evet
```

Girdiler 0, 2, 7, 50 ve 120 için sonuç `sifir`, `az`, `orta`, `cok`, `aralik disi` oldu. 2 değeri `1, 2, 3` dalına, 7 ise `in 4..9` dalına düştü. 50 değeri aralık dışı dalını tutturmadı ve `else` dalına gitti, 120 ise aralık dışı dalını tuttu. Kural şudur: dallar yukarıdan aşağıya sınanır ve ilk tutan çalışır. Aralık dışı dalı `else` dalından önce yazıldığı için 120 değeri ona ulaştı; `else` dalı daha önce yazılsaydı `aralik disi` dalına hiç ulaşılamazdı; bu ters sıra derlenmediği için ölçülemedi.

İkinci satır `[1, 5, 10]` verdi. `weight` fonksiyonu `else` dalı taşımıyor ve yine de derleniyor: derleyici `Level` numaralandırmasının üç değerini de dalların kapsadığını görür. Sık yapılan hata, numaralandırmaya yeni bir değer ekleyip `when` ifadelerini güncellemeyi unutmaktır; `else` dalı olmayan ifade bu durumda derleme hatası verir ve eksik dalı bulur. `else` yazılırsa bu güvence kaybolur. Bu davranış `when` ifade olarak kullanıldığı için geçerlidir. Java'da `switch` ifadesi de numaralandırmada kapsayıcılık denetler, ama eski `switch` deyimi denetlemez.

Üçüncü satır `evet` verdi: `Boolean` iki değerli bir tip olduğundan `true` ve `false` dalları `else` olmadan kapsayıcıdır.

**DT35.** Kapsayıcılık `enum class` ve `Boolean` ile ölçüldü; mühürlü sınıf üzerindeki `when` denetimi bu derste ölçülmedi.

#### Boş Aralık, Adım ve Etiketli break

Aralıklar kolay görünür ve sınır durumları nedeniyle hata üretir. Aşağıdaki blok boş bir aralığı sayar, `step` kullanan bir `downTo` ve `..` kurar, sıfır adımı dener ve iç içe döngüden etiketli `break` ile çıkar.

```kotlin
// Main.kt — bos aralik, adim ve etiketli break
fun main() {
    println((5..1).count())
    println((1..0).toList())
    println((10 downTo 1 step 3).toList())
    println((1..10 step 4).last)
    try {
        println((1..3 step 0).toList())
    } catch (e: IllegalArgumentException) {
        println(e.message)
    }
    var visited = 0
    outer@ for (row in 1..3) {
        for (col in 1..3) {
            if (row * col == 4) break@outer
            visited++
        }
    }
    println(visited)
}
```

```
0
[]
[10, 7, 4, 1]
9
Step must be positive, was: 0.
4
```

`(5..1).count()` `0` verdi ve `(1..0).toList()` boş liste verdi: `..` işleci küçükten büyüğe çalışır, başlangıç bitişten büyükse aralık boştur ve döngü hiç dönmez. Geriye sayılacaksa `downTo` yazılmalıdır. Sık yapılan hata budur: `for (i in 5..1)` yazan kişi geri sayım bekler, program sessizce hiçbir şey yapmaz.

`10 downTo 1 step 3` listesi `[10, 7, 4, 1]` oldu. `1..10 step 4` aralığının `last` değeri `9` çıktı, yani 10 değil: adım üst sınıra tam oturmazsa son değer sınırın altında kalır. Sıfır adım ise `Step must be positive, was: 0.` iletisiyle `IllegalArgumentException` fırlattı; adım pozitif olmak zorundadır, geriye sayma `downTo` ile yapılır, negatif adımla değil.

Son satır `4` verdi. İç döngüde `row * col == 4` olduğunda `break@outer` hem iç hem dış döngüyü bitirdi. Etiketsiz `break` yalnız en içteki döngüyü bitirirdi. Ziyaret sayısı, satır 1'de üç, satır 2'de bir ve satır 2'nin ikinci sütununda kırılma olduğundan toplam dörttür. Java'da etiketli `break` aynı biçimde çalışır; sözdizimi `outer:` ile `break outer;` şeklindedir.

**DT36.** Aralık ölçümleri `Int` ile yapıldı; `Long` aralığında adım ve taşma davranışı bu derste ölçülmedi.

#### try Bir İfadedir

Kotlin'de `try` de değer üretir. Aşağıdaki blok `try` ifadesinin değerini bir fonksiyonun dönüşü yapar, `finally` bloğunun ne zaman çalıştığına bakar ve bir `if` ifadesinin içinde fonksiyon çağrısını kullanır. Son olarak `firstOrNull` ile elvis işleci birlikte denenir.

```kotlin
// Main.kt — try bir ifadedir
fun parse(text: String): Int = try {
    text.toInt()
} catch (e: NumberFormatException) {
    -1
} finally {
    println("denendi: $text")
}

fun main() {
    println(parse("42"))
    println(parse("kotlin"))
    val label = if (parse("7") > 5) "buyuk" else "kucuk"
    println(label)
    val firstNegative = listOf(3, -2, 5).firstOrNull { it < 0 } ?: 0
    println(firstNegative)
}
```

```
denendi: 42
42
denendi: kotlin
-1
denendi: 7
buyuk
-2
```

İlk çağrı `parse("42")` önce `finally` bloğunun çıktısı olan `denendi: 42` satırını, sonra dönüş değeri `42` satırını bastı: `finally`, değer çağırana ulaşmadan önce çalışır. İkinci çağrıda `"kotlin"` sayıya çevrilemediği için `catch` dalı çalıştı ve `-1` döndü; yine `denendi: kotlin` önce basıldı. Üçüncü satırda `parse("7")` çağrısı `if` içinde kullanıldı: `denendi: 7` basıldı ve 7 değeri 5'ten büyük olduğu için `buyuk` yazıldı.

Sık yapılan hata, `finally` bloğuna değer döndüren bir ifade koyup bunun try ifadesinin değerini değiştireceğini sanmaktır. Burada `println` son ifade olsa da `finally` bloğunun değeri ifadenin değerine katılmaz; döndürülen değer `try` ya da `catch` bloğunun son ifadesidir. Son satır `-2` verdi: listede `it < 0` koşulunu sağlayan ilk öğe `-2` idi; hiçbiri olmasaydı elvis işleci `0` döndürürdü, ama bu dal ölçülmedi. Java'da `try` bir deyimdir ve değer üretmez.

**DT37.** `try` ölçümleri `NumberFormatException` ile yapıldı; `finally` bloğunda `return` yazılması halinde değerin nasıl ezildiği bu derste ölçülmedi.

#### Ölçümlerden Çıkan Alışkanlıklar

Akış denetiminde ölçümlerin ortak dersi, sınır durumlarının sessiz olmasıdır. Boş aralık hata vermez, yalnız hiçbir şey yapmaz. Adım değeri `1..10 step 4` gibi sınıra tam oturmazsa son değer sınırın altında kalır. Sıfır adım ise tek bir iletiyle istisna verir. Bu üç durumdan yalnız sonuncusu hata olarak görünür; ötekiler yanlış çıktı üretir.

`when` ifadesinde `else` dalının bulunmaması bir tasarım aracıdır. Numaralandırmada `else` yazmazsanız, yeni bir değer eklendiğinde derleyici sizi bütün `when` ifadelerine yönlendirir. `else` yazarsanız bu uyarıyı kendiniz kapatmış olursunuz. Bu nedenle `else` dalı, gerçekten "geri kalan her şey" anlamına gelen yerlerde, örneğin tam sayı aralıklarında kullanılmalıdır; kapalı bir değer kümesinde kullanılmamalıdır. Sıra konusunda da dikkat gerekir: dallar yukarıdan aşağıya sınandığı için özel dal genel daldan önce yazılır, ölçümde aralık dışı dalı `else`'ten önce olduğu için 120 değeri ona ulaştı.

`try` ifadesinde `finally` bloğunun değeri sonuca katılmaz, ama yan etkisi her zaman çalışır. Kaynak kapatma ve günlük yazma gibi işler bu yüzden `finally` içinde yapılır, değer hesabı ise `try` ve `catch` içinde. Java'da `try` bir deyimdir; değeri bir değişkene atamak için dışarıda bir değişken tanımlamak gerekir. Kotlin'de `val result = try { ... } catch ...` yazımı bu değişkeni yeniden atanamaz bir `val` yapar ve daha kısa bir kod verir.

Etiketli `break` iç içe döngülerden çıkmak için en okunur yöntemdir, ama kullanımı sınırlı tutulmalıdır. Çoğu durumda aramayı ayrı bir fonksiyona taşıyıp `return` ile çıkmak, etiket gerektirmez ve test edilmesi de kolaydır.

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

Kimlik ölçütü yalnız `==` ile eşleştirilen dallar için geçerlidir. `is Box` dalı tutar, çünkü tip denetimi bir eşitlik sorusu değildir. Yani aynı `when` içinde iki dal yan yana durabilir: biri kimlik yüzünden tutmaz, ötekisi tip yüzünden tutar. Bir `Box(1)` örneğini ikinci kez kurup `when` içinde eşleştirmeye çalışan program, `Box` bir veri sınıfı olmadığı sürece dalı hiç tutturamaz; ama `is Box` yazıldığında dal tutar.

İkinci sınır aralıklardadır: `1..3` kimlik ölçütüne düşmez. İki aralık nesnesi ayrı kurulmuş olsa da eşittir. Bir aralığın eşitliği ile `in` dalının tutması farklı sorulardır: ilki bu bölümde, ikincisi `when` bölümünde ölçüldü.

#### Özet

- `if` ve `when` değer üreten ifadelerdir; `when` ifade olarak kullanıldığında bütün durumlar kapsanmalıdır; bunu `else` dalı ya da numaralandırma, `Boolean` veya mühürlü sınıf öznesinde tüm durumların sayılması sağlar.
- `is` bir tip denetimidir ve dal içinde değeri o tipin değişkeni olarak kullanmaya izin verir.
- `..`, `until`, `downTo` ve `step` aralıkları kurar; `for` bu aralıkları dolaşır.
- `when (value)` içindeki bir dal `value == dal` ile eşleşir; sıradan sınıf örneği kimliğe dayandığı için dalı tutturamaz.
- Bu dersin ölçütü: **kimlik**; sınırlayıcı ölçüm: `is Box` dalı tutar ve iki `1..3` aralığı içerik ölçütüne düşer.

#### Sonraki Adım

Akışı gördük; şimdi akışı paketlemenin aracına geçiyoruz: fonksiyonlar. Varsayılan değerli parametreler, adlandırılmış argümanlar ve `vararg` ile başlıyoruz. Aynı fonksiyonu iki kez referans olarak almak, aynı nesneyi mi verir, yoksa içerik mi, onu ölçeriz.

### Fonksiyonlar

Source: https://academia.sh/tr/kurslar/kotlin/fonksiyonlar

Varsayılan değerli parametreleri, adlandırılmış argümanları ve `vararg` parametresini tanıtır; fonksiyon referansının her alınışta ayrı ama eşit bir nesne verdiğini ölçer.

Denetim akışında bir değeri dallara dağıtmayı ve bir aralığı dolaşmayı gördük. Bu derste o akışı adlandırılmış, yeniden kullanılabilir parçalara paketliyoruz: fonksiyonlar. Önce bir fonksiyonun parametrelerini nasıl esnetebileceğimize bakıyoruz, sonra aynı soruyu fonksiyonların kendisine soruyoruz: aynı fonksiyonu iki kez bir değer olarak aldığımızda iki eşit nesne mi elde ederiz, yoksa tek nesne mi?

#### Parametreleri Esnetmek

Kotlin'de fonksiyon `fun` anahtar sözcüğüyle bildirilir: ad, parantez içinde `ad: Tip` biçiminde parametreler, ardından iki nokta ve dönüş tipi gelir. Gövde tek bir ifadeyse `=` ile yazılabilir. Üç özellik parametre listesini esnetir.

Birincisi varsayılan değerdir: bir parametreye `= değer` eklenirse çağıran onu atlayabilir. İkincisi adlandırılmış argümandır (named argument): çağrıda `punctuation = "?"` yazılırsa değer sıraya göre değil adına göre bağlanır ve aradaki parametreler varsayılanlarında kalır. Üçüncüsü `vararg` parametresidir: bir parametre `vararg` ile işaretlenirse çağıran istediği kadar değer verebilir, fonksiyon içinde bunlar bir dizi olarak görünür. Elindeki bir diziyi `vararg` parametresine vermek için dizinin önüne `*` konur.

Bu derste de ölçümler ilk derste tanıtılan yardımcı dosyayla yapılır. `Criterion` sınıfı, bir değer ile ikizini ve farklı bir değeri alıp üç soruyu sorar; dosya aşağıda aynen verilmiştir.

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

Varsayılan değer bir sabit olmak zorunda değildir; herhangi bir ifade olabilir. Çağıran parametreyi atladığında o ifade çağrı anında hesaplanır, bu yüzden aynı fonksiyonun iki çağrısı varsayılandan iki ayrı sonuç alabilir. Adlandırılmış argüman da yalnız sıra kolaylığı değildir: çağrıyı okuyan biri `punctuation = "?"` yazımından değerin neye ait olduğunu fonksiyonun bildirimine bakmadan anlar. İkinci bölümde varsayılan değerin bu davranışını ölçeceğiz.

Aşağıdaki program üç özelliği birlikte kullanır. `greet` iki varsayılan değerli parametre taşır, `sum` ise `vararg` alır.

```kotlin
// Main.kt — varsayilan deger, adlandirilmis arguman ve vararg
fun greet(name: String, greeting: String = "Merhaba", punctuation: String = "."): String =
    "$greeting, $name$punctuation"

fun sum(vararg numbers: Int): Int = numbers.sum()

fun main() {
    println(greet("Ada"))
    println(greet("Ada", punctuation = "?"))
    println(greet(greeting = "Selam", name = "Can"))
    println(sum())
    println(sum(1, 2, 3))
    val values = intArrayOf(4, 5)
    println(sum(*values, 6))
}
```

```
Merhaba, Ada.
Merhaba, Ada?
Selam, Can.
0
6
15
```

İlk iki satır adı tek başına verilen ve noktalamayı adlandırılmış argümanla değiştirilen çağrıları gösterir: `greeting` ve `punctuation` atlandığında `Merhaba` ile nokta kullanıldı, `punctuation = "?"` yazıldığında yalnız noktalama değişti. Üçüncü satırda argümanlar sıra dışı, adlarıyla verildi ve `Selam, Can.` çıktı. Son üç satır `vararg` içindir: hiç değer verilmediğinde toplam `0`, üç değerde `6` oldu; `sum(*values, 6)` ise dizinin iki öğesini ve ek altıyı birlikte topladı ve `15` verdi.

**DT38.** Varsayılan değerler ve `vararg` yalnız `String` ve `Int` parametreleriyle ölçüldü; türü bir sınıf olan parametreler için aynı davranış bu derste ayrıca sınanmadı.

**DT39.** Adlandırılmış argümanla sıra değiştirme yalnız bir çağrıda gösterildi; adlandırılmış ve konumsal argümanların aynı çağrıda karıştırılmasının kuralları bu dersin dışındadır.

#### Fonksiyon Referansı ve Varsayılan Değer

Dersin ölçümü iki şeye bakar. Birincisi fonksiyon referansıdır (function reference): `::plain` yazımı, `plain` fonksiyonunu çağırmadan bir değer olarak verir. Aynı yazımı iki kez kullanmak aynı nesneyi mi verir, ayrı nesneleri mi? İkincisi varsayılan değerdir: `into: MutableList<Int> = mutableListOf()` varsayılanı her çağrıda yeni bir liste mi kurar, yoksa tek bir listeyi mi paylaşır?

`collectMutable` bir öğeyi aldığı listeye ekler ve listeyi döndürür; liste verilmezse varsayılan olan boş değiştirilebilir liste kullanılır. `defaultList` ise `listOf()` varsayılanını doğrudan döndürür. Üç gözlem yazıyoruz: `fonksiyon-referansi`, iki `::plain` ve farklı olarak `::other`; `varsayilan-mutableListOf`, `collectMutable(1)` iki kez ve `collectMutable(2)` farklı değer; `varsayilan-listOf`, `defaultList()` iki kez ve `listOf(1)` farklı değer.

```kotlin
// Main.kt — fonksiyon referansi ve varsayilan degerler
fun plain(n: Int): Int = n

fun other(n: Int): Int = n + 1

fun collectMutable(item: Int, into: MutableList<Int> = mutableListOf()): MutableList<Int> {
    into.add(item)
    return into
}

fun defaultList(into: List<Int> = listOf()): List<Int> = into

fun main() {
    val checks = listOf(
        Criterion("fonksiyon-referansi", ::plain, ::plain, ::other),
        Criterion("varsayilan-mutableListOf", collectMutable(1), collectMutable(1), collectMutable(2)),
        Criterion("varsayilan-listOf", defaultList(), defaultList(), listOf(1)),
    )
    for (check in checks) {
        check.report()
    }
    summarize(checks)
}
```

```
fonksiyon-referansi: ayni-nesne=false, ikiz=true, farkli=false -> icerik
varsayilan-mutableListOf: ayni-nesne=false, ikiz=true, farkli=false -> icerik
varsayilan-listOf: ayni-nesne=true, ikiz=true, farkli=false -> tekil
tekil: 1
kimlik: 0
icerik: 2
kismi-icerik: 0
```

`fonksiyon-referansi` satırında `ayni-nesne=false` ve `ikiz=true` görünür: iki `::plain` yazımı ayrı nesne verdi, ama bu iki nesne `==` ile eşit çıktı. Ayrı nesne, eşit içerik: içerik ölçütü. `varsayilan-mutableListOf` da içeriktir. İki çağrı iki ayrı liste kurdu, ikisi de `[1]` taşıdığı için eşit. Varsayılan değer fonksiyon her çağrıldığında yeniden hesaplanır; bir çağrıda eklenen öğe sonraki çağrıya sızmaz.

`varsayilan-listOf` ise tekildir: `ayni-nesne=true`. Boş `listOf()` çağrısı iki seferde aynı nesneyi verdi. Özet satırları bir tekil, sıfır kimlik, iki içerik ve sıfır kısmi içerik gösterir.

**DT40.** Fonksiyon referansı eşitliği yalnız üst düzey fonksiyonlarla ölçüldü; bir nesneye bağlı referansın (`nesne::ad`) nasıl eşitlendiği bu derste ölçülmedi.

**DT41.** Boş `listOf()` çağrısının tek nesne vermesi Kotlin 2.4.21'in JVM hedefinde ölçüldü; bu davranış listenin gerçekleştirimine bağlı olduğundan başka sürümler için aynısı iddia edilmiyor.

#### Varsayılan Değer Her Çağrıda Hesaplanır

Varsayılan değerin bir ifade olabildiğini gördük. Asıl soru ifadenin ne zaman çalıştığıdır: fonksiyon tanımlanırken bir kez mi, yoksa her çağrıda mı? Aşağıdaki blok varsayılan değeri bir sayaç artıran fonksiyondan alır ve bir parametrenin varsayılanının öncekine bağlı olabildiğini de dener.

```kotlin
// Main.kt — varsayilan deger her cagrida hesaplanir
var counter = 0

fun nextId(): Int {
    counter++
    return counter
}

fun label(text: String, id: Int = nextId(), width: Int = text.length): String =
    "$text#$id/$width"

fun main() {
    println(label("ab"))
    println(label("abc"))
    println(label("abc", id = 100))
    println(label("abc", width = 1))
    println(counter)
}
```

```
ab#1/2
abc#2/3
abc#100/3
abc#3/1
3
```

İlk çağrı `ab#1/2` verdi: `id` için `nextId()` çalıştı ve 1 üretti, `width` varsayılanı `text.length` olduğu için 2 oldu. İkinci çağrı `abc#2/3` verdi; sayaç ikinci kez arttı. Demek ki varsayılan değer her çağrıda yeniden hesaplanır. Üçüncü çağrıda `id` açıkça 100 verildi ve `abc#100/3` basıldı; bu durumda `nextId()` hiç çalışmadı. Dördüncü çağrı `abc#3/1` verdi: `width` adlandırılmış argümanla 1 oldu, `id` için sayaç yeniden çalıştı ve 3 üretti. Son satır `counter` değerini `3` olarak verdi: dört çağrıdan yalnız üçü varsayılan `id` kullandı.

Bu iki şeyi kanıtlar. Birincisi, varsayılan ifade çağrı anında çalışır; yan etkisi varsa her çağrıda tekrar eder. İkincisi, varsayılanlar kendinden önceki parametrelere erişebilir (`width`, `text`'e bakar). Sık yapılan hata, varsayılan değerin tanım anında dondurulduğunu sanmaktır; Python'da değiştirilebilir varsayılan değerin tanımda bir kez hesaplanması bilinen bir tuzaktır, Kotlin'de böyle bir tuzak yoktur. Java'da varsayılan parametre yoktur; aynı etki aşırı yükleme ile elde edilir.

**DT42.** Varsayılan değer ölçümü üst düzey bir sayaçla yapıldı; çok iş parçacıklı bir ortamda sayaç davranışı bu derste ölçülmedi.

#### Unit, Nothing ve Yerel Fonksiyon

Dönüş değeri yazılmayan bir fonksiyon aslında bir şey döndürür. Aşağıdaki blok `Unit` dönen bir fonksiyonu yazdırır, `Nothing` dönen bir fonksiyonu `try` içinde yakalar ve başka fonksiyonun içine yerleştirilmiş bir fonksiyon kullanır.

```kotlin
// Main.kt — Unit, Nothing ve yerel fonksiyon
fun nothingToReturn() {
}

fun fail(message: String): Nothing = throw IllegalStateException(message)

fun total(values: List<Int>): Int {
    fun square(n: Int) = n * n
    var sum = 0
    for (v in values) {
        sum += square(v)
    }
    return sum
}

fun main() {
    println(nothingToReturn())
    println(nothingToReturn() === Unit)
    println(total(listOf(1, 2, 3)))
    val result = try {
        fail("durdu")
    } catch (e: IllegalStateException) {
        e.message
    }
    println(result)
}
```

```
kotlin.Unit
true
14
durdu
```

İlk satır `kotlin.Unit` verdi: bir şey döndürmediğini sandığımız fonksiyon aslında `Unit` adlı tekil nesneyi döndürür ve `println` bunu yazdırabilir. İkinci satırdaki `true`, `nothingToReturn() === Unit` karşılaştırmasının doğru olduğunu gösterir: `Unit` tek bir nesnedir. Bu, Java'daki `void` ile temel bir farktır; `void` bir değer değildir, `Unit` ise gerçek bir değerdir ve bu yüzden genel tiplerde `Unit` kullanılabilir.

Üçüncü satır `14`, yani 1, 4 ve 9'un toplamıdır. `square` fonksiyonu `total` içinde tanımlıdır ve yalnız oradan çağrılabilir. Yerel fonksiyon dış fonksiyonun değişkenlerine erişebilir; burada kullanılmadı ve erişim ölçülmedi.

Son satır `durdu` verdi. `fail` fonksiyonunun dönüş tipi `Nothing`: hiçbir zaman normal biçimde dönmez, hep istisna fırlatır. Derleyici bunu bilir; `fail(...)` çağrısı `try` içinde değer üretmek zorunda olduğu için yakalanan istisnanın iletisi sonuç oldu. Sık yapılan hata `Nothing` tipini `Unit` ile karıştırmaktır. `Unit` döner ve devam eder, `Nothing` ise hiç dönmez.

**DT43.** `Nothing` ölçümü bir istisna fırlatan fonksiyonla yapıldı; sonsuz döngü ya da `exitProcess` çağıran fonksiyonların `Nothing` ile işaretlenmesi bu derste ölçülmedi.

#### Aşırı Yükleme Seçimi ve Uzantı Fonksiyonu

Aynı adı taşıyan fonksiyonlar parametre tipine göre seçilir. Aşağıdaki blok dört `show` fonksiyonu tanımlar ve beş farklı çağrıyla hangisinin seçildiğine bakar. Sonra bir `Int` ve bir `String` üzerine uzantı fonksiyonu yazar ve bir `MutableList` parametresinin çağıran tarafı nasıl etkilediğini gösterir.

```kotlin
// Main.kt — asiri yukleme secimi, uzanti fonksiyonu ve parametre
fun show(x: Int) = "Int"
fun show(x: Long) = "Long"
fun show(x: Any) = "Any"
fun show(x: String) = "String"

fun Int.isEven(): Boolean = this % 2 == 0

fun String.initials(): String = split(" ").joinToString("") { it.take(1).uppercase() }

fun modify(list: MutableList<Int>) {
    list.add(99)
}

fun main() {
    println(show(1))
    println(show(1L))
    println(show("a"))
    val any: Any = "a"
    println(show(any))
    println(show(1.5))
    println(4.isEven())
    println("ada lovelace".initials())
    val nums = mutableListOf(1)
    modify(nums)
    println(nums)
}
```

```
Int
Long
String
Any
Any
true
AL
[1, 99]
```

`show(1)` `Int`, `show(1L)` `Long`, `show("a")` `String` verdi: derleyici en özel eşleşmeyi seçer. Dördüncü satır ilginçtir. `any` değişkeni çalışma anında bir `String` taşır, ama tipi `Any` olarak bildirildiği için `show(any)` `Any` sürümünü seçti. Aşırı yükleme çözümü derleme anında, değişkenin bildirilen tipine göre yapılır; çalışma anı tipi seçimi etkilemez. Java'da da aşırı yükleme derleme anında çözülür; sanal yöntem çağrısı ise çalışma anı tipine göre çözülür ve ikisi karıştırılmamalıdır. `show(1.5)` yalnız `Any` sürümüne uyduğu için `Any` verdi.

`4.isEven()` `true` verdi ve `"ada lovelace".initials()` `AL` verdi: uzantı fonksiyonu mevcut bir tipe, tipin kendisini değiştirmeden yeni bir çağrı biçimi ekler. Uzantı fonksiyonu statik olarak çözülür; bir sınıfın gerçek bir üyesi değildir.

Son satır `[1, 99]` verdi. `modify` fonksiyonu parametre olarak aldığı listeye ekleme yaptı ve çağıran taraf bunu gördü. Kotlin'de referansın kendisi değer olarak kopyalanır: nesne paylaşılır, ama fonksiyon çağıranın değişkenini başka bir nesneye bağlayamaz; parametre değişkeni de zaten yeniden atanamaz. Sık yapılan hata, fonksiyona geçen koleksiyonun içinde değişiklik yapıp bunun yerel kalacağını sanmaktır.

**DT44.** Aşırı yükleme seçimi dört tanımla ve beş çağrıyla ölçüldü; `Int` ve `Long` yanında `Short` ya da `Byte` argümanlarının hangi sürüme gittiği bu derste ölçülmedi.

#### Ölçümlerden Çıkan Alışkanlıklar

Fonksiyon tanımında ölçümlerin gösterdiği ilk şey, varsayılan değerin bir ifade olduğu ve her çağrıda yeniden çalıştığıdır. Bu, Python'daki değiştirilebilir varsayılan tuzağını Kotlin'de ortadan kaldırır, ama başka bir riski doğurur: varsayılan ifadenin yan etkisi varsa, çağrı sayısı program davranışını etkiler. Ölçümde `counter` değeri dört çağrıdan sonra üç oldu, çünkü yalnız üç çağrıda varsayılan değer kullanıldı. Varsayılan ifadeleri yan etkisiz tutmak, bu yüzden iyi bir alışkanlıktır; sayaç artırmak gibi işler açık bir çağrı olarak gövdede yapılmalıdır.

`Unit` ve `Nothing` ayrımı tip sisteminin kullanışlı bir parçasıdır. `Unit` döndüren fonksiyonun tekil bir değer döndürdüğünü ölçtük; bu yüzden `Unit` genel tiplerde sorunsuz kullanılır. `Nothing` ise hiç dönmeyen ifadenin tipidir ve bu sayede `val x = y ?: fail("yok")` gibi bir yazım `x`'in boş olmayan tipte kalmasını sağlar. Derleyici `fail` fonksiyonundan sonra kodun çalışmayacağını bilir, bu yüzden erişilemeyen kod uyarısı verebilir.

Aşırı yüklemede seçim derleme anında yapılır ve bildirilen tipe bakar. `any` değişkeni bir `String` taşıdığı hâlde `Any` sürümü seçildi. Bu davranış, sanal yöntem çağrısının çalışma anı tipine göre çözülmesinden farklıdır ve ikisini karıştıran kod beklenmedik bir sürümü çağırır. Bir aşırı yükleme kümesi büyüdükçe hangi çağrının hangi sürüme gittiğini izlemek güçleşir; bu durumda adlandırılmış fonksiyonlar (`showInt`, `showLong`) daha okunur olabilir.

Uzantı fonksiyonları tipi değiştirmeden çağrı biçimi ekler. Bir kütüphane sınıfına yöntem eklemek istediğinizde, alt sınıf oluşturmak yerine uzantı yazmak çoğu zaman yeterlidir. Ama uzantı gerçek bir üye değildir; sınıfın özel alanlarına erişemez ve statik olarak çözülür, yani çalışma anındaki tipe göre seçilmez.

Parametre olarak geçen değiştirilebilir koleksiyon, çağıran tarafta da değişir. Bunu istemiyorsanız fonksiyon parametresini salt okunur `List` tipinde alın ya da kopyasını çıkarın. Ölçümdeki `[1, 99]` çıktısı bu tuzağın kısa bir kanıtıdır.

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

İçerik ölçütü fonksiyon referansı için geçerlidir ama referansın her alınışta ayrı bir nesne olduğunu bir sözleşme gibi okumamak gerekir: ölçülen şey, iki alınışın `==` ile eşit çıkmasıdır. Aynı derste `listOf()` varsayılanı tekil çıktı. Yani varsayılan değer yazımı tek başına ölçütü belirlemez: `mutableListOf()` her çağrıda yeni bir nesne kurduğu için içerik, `listOf()` aynı boş listeyi döndürdüğü için tekildir. Bir parametrenin varsayılanına bakarken ölçüt, o ifadenin ne kurduğuna bağlıdır.

#### Özet

- `fun` ile bildirilen fonksiyonda varsayılan değerli parametre, adlandırılmış argüman ve `vararg` çağrıyı esnetir; `*` bir diziyi `vararg` parametresine açar.
- Varsayılan değer her çağrıda yeniden hesaplanır; `mutableListOf()` varsayılanı çağrılar arasında paylaşılmaz.
- `::plain` gibi iki fonksiyon referansı ayrı nesnelerdir ama `==` ile eşittir: içerik.
- Boş `listOf()` varsayılanı iki çağrıda aynı nesneyi verdi: tekil.
- Bu dersin ölçütü: **içerik**; sınırlayıcı ölçüm: `listOf()` varsayılanı tekildir, `mutableListOf()` varsayılanı içeriktir.

#### Sonraki Adım

Fonksiyonları bir değer olarak alıp vermeyi gördük. Bir sonraki derste fonksiyonu yerinde yazmaya geçiyoruz: lambdalar ve yüksek mertebeden fonksiyonlar. Orada aynı soruyu iki lambda için soruyoruz; iki lambda aynı nesne mi, eşit mi, yoksa ikisi de değil mi.

### Lambdalar ve Yüksek Mertebeden Fonksiyonlar

Source: https://academia.sh/tr/kurslar/kotlin/lambda-ve-yuksek-mertebeden-fonksiyonlar

Lambda yazımını, fonksiyon tiplerini ve fonksiyon alan fonksiyonları tanıtır; iki lambda ifadesinin eşit olmadığını, yakalama yapmayan bir lambdanın ise tek nesne olduğunu ölçer.

Önceki derste `::plain` gibi bir fonksiyon referansının her alınışta ayrı ama eşit bir nesne verdiğini gördük. Şimdi fonksiyonu yerinde yazıyoruz. Lambda, adı olmayan bir fonksiyon değeridir; bir değişkene atanabilir, bir fonksiyona argüman olarak verilebilir ya da bir fonksiyondan döndürülebilir. Dersin sorusu önceki dersin sorusunun devamıdır: aynı gövdeye sahip iki lambda eşit midir?

#### Lambda ve Fonksiyon Tipi

Lambda süslü parantez içinde yazılır: önce parametreler, ardından `->`, sonra gövde. Parametresi tek olan ve tipi bağlamdan bilinen bir lambdada parametre bildirilmez, onun yerine örtük `it` adı kullanılır. Fonksiyon tipi (function type) `(Int) -> Int` biçimindedir: parantez içinde parametre tipleri, okun ardından dönüş tipi bulunur.

Fonksiyon alan ya da fonksiyon döndüren bir fonksiyona yüksek mertebeden fonksiyon (higher-order function) denir. Son parametresi bir fonksiyon tipi olan fonksiyon çağrılırken lambda parantezin dışına yazılabilir: buna sondaki lambda (trailing lambda) denir. `inline` anahtar sözcüğü bu derste yalnız tanım olarak geçer: satır içi olarak işaretlenen bir yüksek mertebeden fonksiyonun gövdesi ve ona verilen lambda çağrı yerine derleme zamanında yerleştirilir. Bu derste `inline` ölçülmedi.

Bu derste de ölçümler ilk derste tanıtılan yardımcı dosyayla yapılır. `Criterion` sınıfı, bir değer ile ikizini ve farklı bir değeri alıp üç soruyu sorar; dosya aşağıda aynen verilmiştir.

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

Fonksiyon tipi sıradan bir tiptir: bir değişkenin, bir parametrenin ya da bir dönüş değerinin tipi olarak yazılabilir. `val increment: (Int) -> Int = { it + 1 }` satırında tipi açıkça yazdığımız için lambdada parametre bildirmeye gerek kalmaz ve `it` kullanılabilir. Tip yazılmadığında ise parametre lambdanın içinde `n: Int ->` biçiminde bildirilir; derleyici iki yoldan birinden parametre tipini öğrenmek zorundadır.

Aşağıdaki program bunları birlikte gösterir. `applyTwice` bir işlemi iki kez uygular, `makeAdder` ise bir fonksiyon döndürür.

```kotlin
// Main.kt — lambda, it, sondaki lambda ve fonksiyon tipi
fun applyTwice(value: Int, operation: (Int) -> Int): Int = operation(operation(value))

fun makeAdder(amount: Int): (Int) -> Int = { it + amount }

fun main() {
    val twice = { n: Int -> n * 2 }
    val increment: (Int) -> Int = { it + 1 }
    println(twice(4))
    println(applyTwice(3, increment))
    println(applyTwice(3) { it * 10 })
    val addFive = makeAdder(5)
    println(addFive(10))
    println(listOf(1, 2, 3).map { it * it })
}
```

```
8
5
300
15
[1, 4, 9]
```

`twice(4)` doğrudan bir lambdayı çağırdı ve `8` verdi. `applyTwice(3, increment)` üçe iki kez bir ekledi ve `5` çıktı. `applyTwice(3) { it * 10 }` sondaki lambda yazımıdır: üç, otuz, üç yüz; sonuç `300`. `makeAdder(5)` beş ekleyen bir fonksiyon döndürdü ve bu fonksiyon `10` üzerinde `15` verdi. Son satırda `map` her öğeye lambdayı uyguladı ve `[1, 4, 9]` çıktı.

**DT45.** `it` yalnız tek parametreli lambdalarda gösterildi; birden çok parametreli bir lambdada parametrelerin adlandırılması yalnız `twice` örneğindeki tek parametreyle sınırlı kaldı.

**DT46.** Yüksek mertebeden fonksiyonlar yalnız `Int` üzerinde ve `map` ile gösterildi; `filter`, `fold` gibi öteki koleksiyon fonksiyonları sonraki konunun işidir.

#### İki Lambda Eşit Midir

Ölçüm dört gözlemden kurulur. `iki-lambda`: iki ayrı yerde yazılmış `{ 1 }` ifadesi. `ayni-cagri-noktasi`: aynı yerde yazılmış `{ 1 }` lambdasını döndüren `makeConstant` fonksiyonu, iki kez çağrılır. `yakalayan-lambda`: dış bir değişkeni, yani `amount` parametresini kullanan lambda, `makeAdderFor(1)` iki kez çağrılır. `fonksiyon-referansi`: `::inc` iki kez, önceki dersteki gözlemin bir daha tekrarı.

Yakalama (capture), bir lambdanın kendi gövdesinin dışındaki bir değişkeni kullanmasıdır. `makeAdderFor` içindeki lambda `amount` değerini yakalar; `{ 1 }` ise dışarıdan hiçbir şey yakalamaz.

```kotlin
// Main.kt — lambdalar ve kimlik
fun makeConstant(): () -> Int = { 1 }

fun makeAdderFor(amount: Int): (Int) -> Int = { it + amount }

fun inc(n: Int): Int = n + 1

fun dec(n: Int): Int = n - 1

fun main() {
    val checks = listOf(
        Criterion("iki-lambda", { 1 }, { 1 }, { 2 }),
        Criterion("ayni-cagri-noktasi", makeConstant(), makeConstant(), { 2 }),
        Criterion("yakalayan-lambda", makeAdderFor(1), makeAdderFor(1), makeAdderFor(2)),
        Criterion("fonksiyon-referansi", ::inc, ::inc, ::dec),
    )
    for (check in checks) {
        check.report()
    }
    summarize(checks)
}
```

```
iki-lambda: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
ayni-cagri-noktasi: ayni-nesne=true, ikiz=true, farkli=false -> tekil
yakalayan-lambda: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
fonksiyon-referansi: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 1
kimlik: 2
icerik: 1
kismi-icerik: 0
```

`iki-lambda` kimliktir: ayrı yerde yazılan iki aynı gövdeli lambda eşit değildir, `ikiz=false`. `yakalayan-lambda` da kimliktir: `makeAdderFor(1)` iki kez çağrıldığında iki ayrı lambda nesnesi doğdu, aynı değeri yakalamış olsalar bile eşit değiller.

`ayni-cagri-noktasi` ise tekildir: `makeConstant()` iki çağrıda aynı nesneyi döndürdü, `ayni-nesne=true`. Bu lambda dışarıdan bir şey yakalamıyor; sandbox'ta her çağrıda aynı örnek olarak gözlendi. `fonksiyon-referansi` içeriktir, önceki dersin sonucu burada yeniden doğrulandı. Özet satırları bir tekil, iki kimlik, bir içerik ve sıfır kısmi içerik verir.

**DT47.** Yakalama yapmayan lambdanın tek nesne olması Kotlin 2.4.21'in JVM hedefinde ölçüldü; bu bir dil sözleşmesi değil, o sürümde gözlenen sonuçtur.

**DT48.** `yakalayan-lambda` gözlemi tek bir `Int` yakalamasıyla yapıldı; yakalanan değişkenin tipi ya da sayısı değiştiğinde sonucun değişmediği iddia edilmiyor.

#### Yakalanan Değişken Her Yaratılışta Ayrıdır

Bir lambda kendi dışındaki bir `var` değişkenini okuyabilir ve değiştirebilir. Soru, bu değişkenin kaç kopyasının bulunduğudur. Aşağıdaki blok bir sayaç üreten fonksiyon yazar, iki ayrı sayaç kurar ve aynı sayaca iki ayrı ad verir.

```kotlin
// Main.kt — yakalanan var her yaratilista ayri
fun makeCounter(): () -> Int {
    var count = 0
    return { ++count }
}

fun main() {
    val first = makeCounter()
    val second = makeCounter()
    println(first())
    println(first())
    println(second())
    println(first())
    val shared = makeCounter()
    val alias = shared
    println(shared())
    println(alias())
}
```

```
1
2
1
3
1
2
```

`first` iki kez çağrıldı ve `1`, `2` verdi; `second` ilk çağrıda `1` verdi. İki sayaç ayrı `count` değişkenlerini yakaladı, çünkü `makeCounter` her çağrıldığında yeni bir `count` yaratılır. `first` yeniden çağrıldığında `3` bastı ve `second`'ın çağrısı bunu etkilemedi. Son iki satır `1` ve `2` verdi: `alias`, `shared` ile aynı lambda nesnesine işaret ettiği için ikisi aynı sayacı artırdı.

Kural şudur: yakalanan değişken lambda nesnesi ile birlikte yaşar ve nesne başına bir kopya vardır. Sık yapılan hata, sayacın fonksiyon her çağrıldığında sıfırlandığını sanmak ya da tüm lambdaların aynı değişkeni paylaştığını varsaymaktır. Java'da lambda yalnız fiilen yeniden atanmayan (effectively final) değişkenleri yakalayabilir, Kotlin ise `var` yakalamaya izin verir; bunun arkasında değişkenin bir referans kutusuna sarıldığı bilinir, ama bu derste ölçülmedi.

**DT49.** Sayaç ölçümü tek iş parçacığında yapıldı; birden çok iş parçacığı aynı yakalanan değişkeni artırırsa ne olduğu bu derste ölçülmedi.

#### Lambdadan Dönmek

Bir lambdanın içinde `return` yazmak bazen fonksiyonu bitirir, bazen de hata verir. Aşağıdaki blok üç durumu dener: `forEach` içinde etiketsiz `return`, `forEach` içinde `return@forEach` ve `map` içinde kendi etiketi ile `return@label`.

```kotlin
// Main.kt — lambdadan donmek
fun findFirstEven(values: List<Int>): Int {
    values.forEach {
        if (it % 2 == 0) return it
    }
    return -1
}

fun main() {
    println(findFirstEven(listOf(1, 3, 4, 6)))
    println(findFirstEven(listOf(1, 3)))
    val visited = mutableListOf<Int>()
    listOf(1, 2, 3, 4).forEach {
        if (it == 2) return@forEach
        visited.add(it)
    }
    println(visited)
    val labeled = listOf(1, 2, 3).map label@{
        if (it == 2) return@label 20
        it
    }
    println(labeled)
}
```

```
4
-1
[1, 3, 4]
[1, 20, 3]
```

`findFirstEven(listOf(1, 3, 4, 6))` `4` verdi: `forEach` bir satır içi (inline) fonksiyondur, bu yüzden lambdanın içindeki `return` dış fonksiyonu bitirir ve ilk çift sayı olan 4 döner. İkinci çağrıda çift sayı olmadığı için döngü bitti ve `-1` döndü.

Üçüncü satır `[1, 3, 4]` verdi. `return@forEach` yalnız o lambda çalışmasını bitirir, yani `continue` gibi davranır: 2 atlandı, geri kalanlar eklendi. Dördüncü satır `[1, 20, 3]` verdi; `return@label` lambdanın değerini belirler, yani `map` içinde 2 değeri 20 ile değiştirildi.

Sık yapılan hata, etiketsiz `return` ile `forEach` içinde yalnız bir öğeyi atlamayı beklemektir: bu bütün fonksiyonu bitirir. Bu davranış `forEach` satır içi olduğu için mümkündür; satır içi olmayan bir lambdada etiketsiz `return` derleme hatası verir, ama bu dal ölçülmedi. Java'da lambda içindeki `return` yalnız lambdadan çıkar ve dış metodu bitirmez.

**DT50.** Dönüş davranışı `forEach` ve `map` ile ölçüldü; satır içi olmayan bir yüksek mertebeden fonksiyonda etiketsiz `return` yazılırsa verilen hata iletisi bu derste ölçülmedi.

#### Alıcılı Lambda ve SAM Dönüşümü

Bazı lambdalar bir alıcı nesnenin üyelerine doğrudan erişir. Aşağıdaki blok `StringBuilder.() -> Unit` tipinde bir lambda kurar, Java arayüzü `Runnable` ve `Comparator` için lambda yazar ve iki fonksiyonu birleştiren bir lambda üretir.

```kotlin
// Main.kt — alicili lambda ve SAM donusumu
fun main() {
    val build: StringBuilder.() -> Unit = {
        append("kot")
        append("lin")
    }
    println(StringBuilder().apply(build).toString())
    val task = Runnable { println("calisti") }
    task.run()
    val byLength = Comparator<String> { a, b -> a.length - b.length }
    println(listOf("ccc", "a", "bb").sortedWith(byLength))
    val compose: ((Int) -> Int, (Int) -> Int) -> (Int) -> Int = { f, g -> { x -> g(f(x)) } }
    val doubleThenInc = compose({ it * 2 }, { it + 1 })
    println(doubleThenInc(5))
}
```

```
kotlin
calisti
[a, bb, ccc]
11
```

İlk satır `kotlin` verdi. `build` değişkeni alıcılı bir lambdadır: gövdesinde `append` doğrudan çağrılır, çünkü `this` olarak bir `StringBuilder` verilir. `apply(build)` onu yeni bir `StringBuilder` üzerinde çalıştırdı. İkinci satır `calisti`: `Runnable { ... }` yazımı, tek soyut metodu olan bir Java arayüzünü lambda ile oluşturur. Bu dönüşüme SAM dönüşümü (single abstract method) denir. Üçüncü satır `[a, bb, ccc]` verdi: `Comparator` de aynı biçimde lambda ile yazıldı ve uzunluğa göre sıralama yaptı.

Dördüncü satır `11` verdi. `compose` iki fonksiyon alıp üçüncüsünü döndürür; `doubleThenInc(5)` önce 5'i ikiyle çarpar (10), sonra bir ekler (11). Sıra önemlidir: `g(f(x))` yazıldığı için önce `f` çalışır. Sık yapılan hata, birleşimde sıranın tersini varsaymaktır.

**DT51.** SAM dönüşümü Java arayüzleriyle ölçüldü; Kotlin'de yazılmış bir arayüz için ayrı `fun interface` bildirimi gerektiği bu derste ölçülmedi.

#### Ölçümlerden Çıkan Alışkanlıklar

Lambdalarla çalışırken üç soru sorulmalıdır: ne yakalanıyor, nereden dönülüyor ve hangi tip bekleniyor. Yakalama konusunda ölçüm net bir kural verdi: yakalanan değişken lambda nesnesiyle birlikte yaşar ve her yaratılışta ayrı bir kopya oluşur. Bu, sayaç ya da önbellek gibi durum taşıyan küçük nesneleri sınıf yazmadan kurmayı mümkün kılar; ama durumun nerede saklandığı kodda görünmediği için, okuyan kişi için gizli bir bağımlılıktır. Durumun uzun ömürlü ya da paylaşılan olması gerekiyorsa açık bir sınıf daha okunur olur.

Dönüş konusunda en önemli ayrım, lambdanın satır içi olup olmadığıdır. `forEach` içinde etiketsiz `return` dış fonksiyonu bitirir; ölçümde `findFirstEven` ilk çift sayıyı bulup döndü. Bu davranış kullanışlıdır, çünkü erken çıkışı döngü yazmadan ifade eder. Ama `continue` beklenen yerde yanlış kullanılırsa tüm fonksiyonu sessizce bitirir. Atlamak için `return@forEach`, değeri belirlemek için `return@label` yazılır. Okunurluk açısından, birden fazla dönüş noktası olan lambdalar küçük adlandırılmış fonksiyonlara bölünmelidir.

Tip konusunda, alıcılı lambda (`StringBuilder.() -> Unit`) gövdede `this` ile çalışır ve nesnenin üyelerine doğrudan erişir. Bu, yapı kurucu kod yazmak için (builder) temeldir ve sonraki derste kapsam fonksiyonlarının mekanizmasıdır. SAM dönüşümü ise Java arayüzlerini lambda ile kurmayı sağlar; Kotlin'de yazılmış arayüzler için `fun interface` gerekir, bu ölçülmedi.

Fonksiyonları birleştiren `compose` örneği, fonksiyonların değer olduğunu gösterir. Sıra `g(f(x))` olduğu için `f` önce çalışır ve 5 girdisi için sonuç 11 oldu. Ters sıra farklı bir sonuç verirdi, ama o hesap ölçülmedi; yalnız 11 çıktısı sandbox'tan geldi. Bu yüzden birleştirme sırasını bir örnekle sınamak yeterlidir. Fonksiyon birleştirme kütüphanelerinde bu sıra sık karıştırılır, bu yüzden küçük bir örnekle doğrulanması gerekir.

Java ile karşılaştırma: Java lambdası yalnız fiilen yeniden atanmayan değişkenleri yakalar ve `return` yalnız lambdadan çıkar. Kotlin ikisinde de daha geniş bir davranış sunar; esnekliğin bedeli, okurun hangi `return`'ün neyi bitirdiğini bilmesi gerektiğidir. Kodu gözden geçirirken her `return` için önce çevreleyen fonksiyonun satır içi olup olmadığına, sonra etiket bulunup bulunmadığına bakmak, bu belirsizliği çabuk giderir.

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

Lambdalar için kimlik ölçütü iki sınırla kırılır. Birincisi yakalamadır: yakalamayan lambda çağrı yerinde tek nesne olur ve tekil çıkar, yakalayan lambda her çağrıda yeni nesne olur ve kimlik çıkar. Yani iki lambdanın eşit olup olmadığını gövdeleri değil, hangi yerde yazıldıkları ve bir şey yakalayıp yakalamadıkları belirler. İkincisi fonksiyon referansıdır: `::inc` iki kez alındığında nesneler ayrı ama eşittir.

#### Özet

- Lambda `{ parametreler -> gövde }` biçiminde yazılır; tek parametre `it` adını alabilir ve son parametre bir fonksiyon tipiyse lambda parantezin dışına yazılabilir.
- Fonksiyon tipi `(Int) -> Int` biçimindedir; fonksiyon alan ya da döndüren fonksiyona yüksek mertebeden fonksiyon denir.
- Aynı gövdeli iki lambda ayrı yazıldıysa eşit değildir: kimlik. Yakalayan lambda her çağrıda yeni nesnedir.
- Yakalama yapmayan lambda aynı çağrı noktasından her seferinde aynı nesneyi verdi: tekil. `::inc` iki kez içeriktir.
- Bu dersin ölçütü: **kimlik**; sınırlayıcı ölçüm: yakalamayan lambda tekildir, `::inc` referansı içeriktir.

#### Sonraki Adım

Lambdaları değer olarak taşımayı gördük. Son derste lambdayı bir nesnenin bağlamında çalıştıran beş hazır yüksek mertebeden fonksiyonu inceliyoruz: kapsam fonksiyonları. `let`, `run`, `with`, `apply` ve `also` aynı nesne üzerinde çalışır ama biri nesneyi `this`, biri `it` olarak verir; hangisinin neyi döndürdüğünü de ölçüyoruz.

### Kapsam Fonksiyonları

Source: https://academia.sh/tr/kurslar/kotlin/kapsam-fonksiyonlari

`let`, `run`, `with`, `apply` ve `also` kapsam fonksiyonlarını tanıtır; alıcının `this` mi `it` mi olduğunu ve dönüş değerini ölçer, sonucun eşitlik ölçütünü değiştirmediğini gösterir.

Önceki derste lambdaları değer olarak taşıdık. Kapsam fonksiyonları (scope functions) bu lambdaları bir nesnenin bağlamında çalıştıran beş hazır yüksek mertebeden fonksiyondur: `let`, `run`, `with`, `apply` ve `also`. Aralarındaki fark iki sorudan çıkar: nesneye lambdanın içinde nasıl ulaşılır ve fonksiyon ne döndürür? Dersin sonunda bir üçüncü soru geliyor: kapsam fonksiyonundan geçen bir nesnenin eşitlik ölçütü değişir mi?

#### Alıcı ve Dönüş Değeri

Bir kapsam fonksiyonu, üzerinde çağrıldığı nesneyi lambdaya iki yoldan verir. `let` ve `also` nesneyi lambda parametresi `it` olarak verir. `run`, `with` ve `apply` ise nesneyi alıcı (receiver) yapar: lambdanın içinde nesnenin üyelerine `this` ile, çoğunlukla da hiçbir önek yazmadan ulaşılır. Bu ayrım, bir ifadenin hangi nesne üzerinde çalıştığını okurken ilk bakılacak yerdir.

Dönüş değeri iki biçimdedir. `let`, `run` ve `with` lambdanın son ifadesinin sonucunu döndürür. `apply` ve `also` ise nesnenin kendisini döndürür. Yani `apply` bir nesneyi kurarken, `also` nesneye dokunmadan yan bir iş yaparken kullanılır.

Bu derste de ölçümler ilk derste tanıtılan yardımcı dosyayla yapılır. `Criterion` sınıfı, bir değer ile ikizini ve farklı bir değeri alıp üç soruyu sorar; dosya aşağıda aynen verilmiştir.

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

Bu seçim bir okunabilirlik kararıdır. Lambdanın içinde nesnenin üyelerini önek yazmadan kullanmak istiyorsanız alıcı yazımlı üç fonksiyondan biri uygundur; nesneyi bir ad altında başka bir şeye argüman olarak geçirecekseniz `it` yazımlı ikisi uygundur. Dönüş değeri ise bloktan sonra elinizde ne kalacağına karar verir: nesnenin kendisi mi, hesaplanmış bir sonuç mu.

Aşağıdaki program beş fonksiyonu sırayla çalıştırır. `Point` iki değiştirilebilir alanı olan bir veri sınıfıdır.

```kotlin
// Main.kt — let, run, with, apply ve also
data class Point(var x: Int, var y: Int)

fun main() {
    val letResult = Point(1, 2).let { it.x + it.y }
    val runResult = Point(3, 4).run { x * y }
    val withResult = with(Point(5, 6)) { "$x,$y" }
    val applied = Point(0, 0).apply {
        x = 7
        y = 8
    }
    val alsoResult = Point(9, 9).also { println("gozlem: ${it.x}") }
    println(letResult)
    println(runResult)
    println(withResult)
    println(applied)
    println(alsoResult)
}
```

```
gozlem: 9
3
12
5,6
Point(x=7, y=8)
Point(x=9, y=9)
```

İlk satır `also` içindeki `println` çağrısından geldi ve `gozlem: 9` yazdı; `also` bloğu nesneyi gözlemledi ve nesne olduğu gibi döndü. `let` bloğunda iki alan `it` üzerinden toplandı ve `3` çıktı. `run` bloğunda `x * y` alıcıyla, önek olmadan yazıldı ve `12` çıktı. `with` de aynı alıcı yazımını kullandı ve lambdanın sonucu olan `"5,6"` metnini verdi. `apply` bloğunda alanlar atandı ve fonksiyon nesnenin kendisini döndürdü: `Point(x=7, y=8)`. Son satır `also`ın döndürdüğü nesneyi gösterir: `Point(x=9, y=9)`.

**DT52.** Beş fonksiyon yalnız `Point` üzerinde ve yalnız tam sayı alanlarla ölçüldü; boş değer alabilen alıcı üzerindeki `?.let`, `?.apply` ve `takeIf` kullanımı dersin ilerleyen bölümünde yalnız `String?` ile ölçülür.

**DT53.** `with` bir uzantı değil, nesneyi argüman olarak alan sıradan bir fonksiyondur; bu ayrım burada yalnız söylendi, ölçülmedi.

#### Kapsam Fonksiyonu Ölçütü Değiştirmez

Ölçüm şunu sorar: bir nesneyi kapsam fonksiyonundan geçirmek, ikizinin eşit sayılıp sayılmayacağını değiştirir mi? Burada `Point` değişmeyen alanlı bir veri sınıfıdır. Üç gözlem yazıyoruz. `let-copy`: `Point(1, 2).let { it.copy() }`. `run-copy`: `Point(1, 2).run { copy() }`; aynı kopyalama, bu sefer alıcı yazımıyla. `also-ikiz`: aynı `source` değişkeni `also { }` ile geçirilir ve kendisiyle karşılaştırılır.

`let` ve `run` lambdanın sonucunu döndürdüğü için döndürülen şey `copy()` ile kurulan ayrı bir nesnedir. `also` ise kendi nesnesini döndürür.

```kotlin
// Main.kt — kapsam fonksiyonunun dondurdugu
data class Point(val x: Int, val y: Int)

fun main() {
    val source = Point(1, 2)
    val checks = listOf(
        Criterion("let-copy", Point(1, 2), Point(1, 2).let { it.copy() }, Point(1, 3)),
        Criterion("run-copy", Point(1, 2), Point(1, 2).run { copy() }, Point(1, 3)),
        Criterion("also-ikiz", source, source.also { }, Point(1, 3)),
    )
    for (check in checks) {
        check.report()
    }
    summarize(checks)
}
```

```
let-copy: ayni-nesne=false, ikiz=true, farkli=false -> icerik
run-copy: ayni-nesne=false, ikiz=true, farkli=false -> icerik
also-ikiz: ayni-nesne=true, ikiz=true, farkli=false -> tekil
tekil: 1
kimlik: 0
icerik: 2
kismi-icerik: 0
```

`let-copy` ve `run-copy` içeriktir: `ayni-nesne=false`, `ikiz=true`. Kapsam fonksiyonundan çıkan kopya, kaynakla aynı nesne değildir ama aynı değerleri taşıdığı için veri sınıfının `equals`'i onları eşit sayar. Hangi kapsam fonksiyonunun kullanıldığı ölçütü belirlemedi; ölçütü belirleyen `Point`ın veri sınıfı olmasıdır.

`also-ikiz` tekildir: `ayni-nesne=true`. `also` nesnenin kendisini döndürdüğü için ikiz ile birinci değer aynı nesne çıktı. Özet satırları bir tekil, sıfır kimlik, iki içerik ve sıfır kısmi içerik verir.

**DT54.** `also-ikiz` gözleminde ikiz kurulmadı; kapsam fonksiyonu nesnenin kendisini döndürdüğü için karşılaştırma bir nesnenin kendisiyle yapıldı. Bu gözlem ölçüm koşulunu bozar, aşağıda açıklanıyor.

**DT55.** `copy()` çağrısı yüzeysel kopya üretir; iç içe nesne taşıyan bir veri sınıfında kopyanın nasıl davrandığı bu derste ölçülmedi.

#### Beş Fonksiyon Neyi Döndürür

Kapsam fonksiyonları arasındaki asıl fark ikiye iner: nesneye `it` ile mi `this` ile mi erişilir ve ifade neyi döndürür. Aşağıdaki blok aynı `Point` nesnesi üzerinde beşini çalıştırır ve dönen nesneyi `===` ile kaynağa karşı sınar.

```kotlin
// Main.kt — kapsam fonksiyonlari neyi dondurur
data class Point(var x: Int, var y: Int)

fun main() {
    val p = Point(1, 2)
    println(p.let { it } === p)
    println(p.run { this } === p)
    println(with(p) { this } === p)
    println(p.apply { x = 10 } === p)
    println(p.also { it.y = 20 } === p)
    println(p.let { 5 })
    println(p.apply { x = 11 }.run { x + y })
    println(p)
}
```

```
true
true
true
true
true
5
31
Point(x=11, y=20)
```

Beş satırın hepsi `true` verdi, ama beşi aynı şey için değil. `p.let { it } === p` ve `p.run { this } === p` doğrudur çünkü lambdanın son ifadesi nesnenin kendisidir. `with(p) { this } === p` aynı sebeple doğrudur. `apply` ve `also` ise lambdanın değerine bakmaz, her zaman alıcı nesneyi döndürür.

Altıncı satır `5` verdi: `let { 5 }` ifadesinin değeri lambdanın son ifadesidir, nesne değil. Yedinci satır `31`: `apply { x = 11 }` nesneyi döndürdü, `run { x + y }` ise toplamı hesapladı, 11 ile 20'nin toplamı 31. Son satır `Point(x=11, y=20)` verdi, yani önceki beş çağrıdaki atamalar nesnenin kendisini değiştirdi. Bu atamalar yerel bir kopyada değil, `p` üzerinde yapılmıştı.

Kural: `let`, `run` ve `with` lambdanın sonucunu verir; `apply` ve `also` nesneyi verir. Sık yapılan hata, `apply` içinde son satıra bir değer yazıp onun döneceğini sanmaktır. Java'da buna denk bir yapı yoktur; zincirleme için yöntemlerin `this` döndürmesi (builder kalıbı) gerekir.

**DT56.** Dönüş değerleri `Point` ve tam sayı sonuçlarla ölçüldü; `with` bir uzantı değil sıradan bir fonksiyondur, ama bu ayrım ölçülmedi.

#### Null ile Kapsam Fonksiyonları ve takeIf

En sık kullanım `?.let` ile boş olabilecek bir değeri güvenle işlemektir. Aşağıdaki blok bunu dolu ve boş bir `String?` ile dener, `?.let` ile `.let { it?.... }` arasındaki farka bakar ve `takeIf` ile `takeUnless` fonksiyonlarını sınar.

```kotlin
// Main.kt — null ile kapsam fonksiyonlari ve takeIf
fun main() {
    val present: String? = "kotlin"
    val missing: String? = null
    println(present?.let { it.length })
    println(missing?.let { it.length })
    println(missing.let { it?.length })
    println(missing?.apply { length })
    println(missing?.let { it.length } ?: -1)
    println(present.takeIf { it?.startsWith("k") == true })
    println(present.takeUnless { it?.startsWith("k") == true })
    println("abc".takeIf { it.length > 5 })
}
```

```
6
null
null
null
-1
kotlin
null
null
```

`present?.let { it.length }` `6` verdi; `missing?.let { it.length }` ise `null`. Güvenli çağrı `?.` alıcı boşsa lambdayı hiç çalıştırmadı. Üçüncü satır da `null` verdi ama sebebi farklıdır: `missing.let { it?.length }` ifadesinde `let` boş değer üzerinde de çağrılır, lambda çalışır ve içeride `it?.length` boş sonuç üretir. Bu iki biçimin sonucu aynı olsa bile lambda birincisinde çalışmadı, ikincisinde çalıştı. Dördüncü satır `null`: `missing?.apply { length }` de alıcı boş olduğu için hiçbir şey çalıştırmadı, sonuç boş değerdir. Beşinci satır `-1`: elvis işleci boş sonucu varsayılana çevirdi.

`takeIf` koşul doğruysa nesneyi, yanlışsa `null` döndürür. `present.takeIf { ... }` `kotlin` verdi, çünkü dizgi `k` ile başlıyor. `takeUnless` ters çalışır ve aynı koşulla `null` verdi. `"abc".takeIf { it.length > 5 }` `null` verdi, çünkü koşul sağlanmadı. Sık yapılan hata, `takeIf` sonucunu boş olabilir tipte olduğunu unutarak doğrudan kullanmaktır; derleyici bu durumda derleme hatası verir, ama bu hata ölçülmedi.

**DT57.** Boş değer ölçümü `String?` ile yapıldı; `takeIf` koşulunun yan etki taşıması ya da `takeIf` zincirleri bu derste ölçülmedi.

#### İç İçe Kapsam Fonksiyonları ve this

İki kapsam fonksiyonu iç içe yazıldığında `this` ve `it` hangi nesneye bakar? Aşağıdaki blok bir `run`'ın içinde ikinci bir `run` çalıştırır, dıştaki alıcıya etiketle erişir, ardından `run` ile nesnesiz bir blok kullanır ve iç içe `apply` ile `size` okunur.

```kotlin
// Main.kt — ic ice kapsam fonksiyonlari ve this
data class Box(var size: Int)

fun main() {
    val box = Box(1)
    val result = box.run outer@{
        size = 5
        Box(size * 2).run { "ic: $size, dis: ${this@outer.size}" }
    }
    println(result)
    val note = run {
        val temp = 21
        temp * 2
    }
    println(note)
    Box(3).apply {
        Box(4).apply {
            println(size)
        }
        println(size)
    }
}
```

```
ic: 10, dis: 5
42
4
3
```

İlk satır `ic: 10, dis: 5` verdi. Dış `run`'da `size = 5` atandı, iç `run`'da `size * 2` ile yeni bir `Box(10)` kuruldu. İç blokta `size` en yakın alıcıya, yani `Box(10)` nesnesine bakar. Dış alıcıya erişmek için `this@outer.size` yazılır ve `5` okunur. İç içe alıcı kullanımı bu yüzden okunması zor kod üretir; `also` ve `let` ile `it` adını vermek daha açıktır. Sık yapılan hata, iç blokta `size` yazıp dış nesnenin alanını okuduğunu sanmaktır.

İkinci satır `42` verdi: nesnesiz `run { }` yalnız bir blok açıp son ifadeyi döndürür. Üçüncü ve dördüncü satırlar `4` ve `3` verdi: iç `apply` içinde `size` iç nesnenin 4 değeridir, dış `apply` içinde ise dış nesnenin 3 değeri. İç blok bitince kapsam dışa döner. Java'da aynı etkiyi iç sınıflarda `Dis.this.alan` yazımı verir; Kotlin'de etiket bunun karşılığıdır.

**DT58.** İç içe alıcı ölçümü iki düzeyle yapıldı; üç ya da daha derin iç içe kullanımda etiketsiz `this` çözümü bu derste ölçülmedi.

#### Ölçümlerden Çıkan Alışkanlıklar

Kapsam fonksiyonlarını seçerken iki soruya bakılır: nesneye `it` mi `this` mi ile erişilecek ve sonuç nesne mi lambdanın değeri mi olacak. Ölçüm tabloyu doğruladı: `apply` ve `also` nesneyi döndürür, `let`, `run` ve `with` lambdanın değerini. Pratikte `apply` nesne kurarken, `also` yan etki (günlük yazma gibi) eklerken, `let` boş olabilecek değeri dönüştürürken, `run` ve `with` ise nesne üzerinde bir hesap yapıp sonuç alırken kullanılır. Bu eşleşme bir kural değil, yaygın bir alışkanlıktır; aynı iş çoğu zaman başka bir fonksiyonla da yapılabilir.

Aşırı kullanım gerçek bir risktir. İç içe üç kapsam fonksiyonu, `this` ve `it` adlarının hangi nesneye gittiğini izlemeyi zorlaştırır. Ölçümde iç `run` içindeki `size` değeri iç nesneye, dış değere erişim ise `this@outer.size` ile yapıldı. İç içe iki düzeyi aşan kullanımda adlandırılmış yerel değişkenler daha açıktır. Kural olarak, bir kapsam fonksiyonunun içine bir başkası yazılıyorsa, ya dıştaki `it` ile adlandırılmalı ya da kod ayrı bir fonksiyona taşınmalıdır.

Boş değerlerle `?.let` yaygın kalıptır ve ölçüm iki biçim arasındaki farkı gösterdi: `missing?.let { ... }` lambdayı hiç çalıştırmaz, `missing.let { it?.... }` ise lambdayı boş değerle çalıştırır. Sonuçlar aynı görünse de yan etki taşıyan bir lambdada fark ortaya çıkar. `takeIf` ve `takeUnless` ise bir koşulu zincire sokar: koşul sağlanmazsa `null` döner ve sonraki `?.` adımları atlanır.

Bir diğer sık hata, `apply` bloğunun son satırına bir değer yazıp onun döneceğini sanmaktır. `apply` her zaman nesneyi döndürür, bu yüzden sondaki ifade göz ardı edilir. Sonucu başka bir değere çevirmek istiyorsanız `let` ya da `run` seçilmelidir. Java'da bu işlerin karşılığı builder kalıbıdır ve her yöntemin `this` döndürmesine dayanır; Kotlin bunu dil düzeyinde, mevcut sınıfı değiştirmeden sağlar.

Son olarak, `with` bir uzantı değil, nesneyi argüman alan sıradan bir fonksiyondur. Bu yüzden boş olabilecek bir nesneye `?.` ile bağlanamaz; boş değerli alıcıda `?.run` ya da `?.let` seçilmelidir.

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

`also-ikiz` gözlemi ölçüm koşulunu bozar: `Criterion` bir değerin ayrı kurulmuş ikizini bekler, oysa `also` aynı nesneyi geri verir. Çıkan tekil sonuç ölçütün bir bulgusu değil, fonksiyonun dönüş değeri kuralının yansımasıdır. Bu yüzden kapsam fonksiyonunun ölçütü değiştirmediğini göstermek için `let` ve `run` gözlemleri anlamlıdır, `also` gözlemi ise yalnız sınırı gösterir. Bir ifadede kapsam fonksiyonu görürseniz şu iki soruyu sorun: bu ifade hangi nesne üzerinde çalışıyor ve fonksiyon bu nesneyi mi yoksa lambdanın sonucunu mu döndürüyor? Eşitlik sorusunun yanıtı hep nesnenin kendi tipinde kalır.

#### Özet

- `let` ve `also` nesneyi `it` olarak, `run`, `with` ve `apply` alıcı olarak (`this`) verir.
- `let`, `run` ve `with` lambdanın sonucunu, `apply` ve `also` nesnenin kendisini döndürür.
- `Point(1, 2).let { it.copy() }` ve `run { copy() }` ayrı ama eşit nesne verdi: içerik.
- `also` aynı nesneyi geri verdiği için `also-ikiz` tekil çıktı ve ölçüm koşulunu bozdu.
- Bu dersin ölçütü: **içerik**; sınırlayıcı ölçüm: `also` ikizi tekildir ve ikiz kurmadığı için ölçüm koşulunu bozar.

#### Sonraki Adım

Dil temelleri burada kapanıyor. Sonraki konuda eşitlik sorusuna `null` giriyor: bir değer hiç olmayabildiğinde `null` ile `null` aynı mıdır ve derleyici bu durumu tipin içinde nasıl izler? Konu, `Null Güvenliği` dersiyle başlıyor.

## Tipler ve Null Güvenliği

Boş değer alabilen ve alamayan tipler, güvenli çağrı ve Elvis işleci, birincil ve ikincil yapıcılar, veri sınıfları, mühürlü tipler ve numaralandırmalar, nesne bildirimleri, arayüzler ve devretme, uzantı fonksiyonları ile genellikler ve varyans.

### Null Güvenliği

Source: https://academia.sh/tr/kurslar/kotlin/null-guvenligi

Boş değer alabilen ve alamayan tiplerin ayrı tipler olduğunun, null denetiminden sonra tipin daraldığının ve null ile null eşitliğinin hangi ölçütü verdiğinin ölçümü.

Önceki ders kapsam fonksiyonlarının hangi değeri döndürdüğünü ölçtü. Döndürülen şey bazen hiçbir şeydir: bir arama sonuç bulamaz, bir alan doldurulmamıştır, bir dönüşüm tamamlanamaz. Bu durum bir değerle, örneğin `null` ile ifade edilebilir; o zaman o değerin her kullanımdan önce denetlenmesi gerekir ve denetim unutulduğunda hata çalışma anında ortaya çıkar.

Kotlin yokluğu tipin kendisine yazar. `String` her zaman bir dizgidir; `String?` ise ya bir dizgidir ya da `null`. Bu iki tip aynı tip değildir ve derleyici birini ötekinin yerine kullandırmaz. Buna **null güvenliği** denir: boş değer alabilen tip ile boş değer alamayan tip ayrı tiplerdir, dolayısıyla `null` olasılığı olan her yer kaynak kodda görünür. Bu ders o ayrımı derleyicinin vereceği red üzerinden ölçer, sonra aynı ayrımın eşitlik sorusuna ne söylediğine bakar.

Kursun ölçü ekseni, bir değerin kendisine eşit sayılan başka bir değerle ilişkisidir. Bir **ölçüt** dört biçimden birini alır: **tekil**, **kimlik**, **içerik** ve **kısmi içerik**. Kural sırayla işler. İlk değer ile ikizi aynı nesneyse ölçüt tekildir. Değilse ve ikiz eşit değilse kimliktir: yalnızca aynı nesne eşit sayılır. İkiz eşit ama ilkinden bir bakımdan ayrılan üçüncü değer eşit değilse içeriktir: nesneler ayrı, eşitlik içerikten gelir. Üçüncü değer de eşitse kısmi içeriktir: ayrılan bakım eşitliği etkilemez. Açılış dersindeki bağlayıcı ölçümde sekiz değerin dağılımı 2/2/3/1 idi; bu ders yalnız kendi kesitini ölçer.

Sınıflandırmayı yapan `Criterion` sınıfı aşağıda olduğu gibi verilir. Kod blokları yalnızca sandbox içinde derlenip koşturulur; çıktı bloklarındaki her satır sandbox'ın bastığıdır.

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

Parametreler `Any?` tipindedir; yani `null` da bir argüman olabilir. Bu dersin ilk sorusu tam olarak budur: `null` bir değer olarak ele alındığında hangi ölçütü verir?

#### İki Ayrı Tip

Aşağıdaki blokta aynı içeriğe sahip bir `String` ve bir `String?` yan yana durur. Karşılaştırma derlenir, çünkü `==` iki tarafın tipini birbirine denk getirmeyi gerektirmez; yalnız içeriğe bakar. Ama uzunluk gibi tipe bağlı bir işlem yalnız boş değer alamayan tipte sorunsuzdur.

```kotlin
// Main.kt — iki tip
fun main() {
    val a: String = "kutu"
    val b: String? = "kutu"
    println(a == b)
    println(a.length)
    val c: String? = null
    println(c == null)
    println(c)
}
```

```
true
4
true
null
```

`a == b` doğrudur: iki dizgi aynı içeriği taşır ve `String?` tipinin dolu hâli bir dizgi olarak karşılaştırılır. `c` ise `null` taşır ve ekrana `null` yazılır; boş değer alabilen bir değişkenin yazdırılması bir hata değildir. Dikkat edilecek nokta, `a.length` satırında `a`'nın boş değer alamayan bir tip olmasıdır. Aynı satırı `b` ile yazmak derleyicinin reddine yol açar.

İki red vardır ve ikisi de sandbox'ta ölçüldü. Birincisi `String` tipli bir değişkene `null` atamaktır. Derleyici bunu Kotlin 2.4.21'de reddeder: `null` boş değer alamayan bir tipin değeri olamaz. Hata derleme sırasında çıkar, program hiç koşmaz.

```kotlin
fun main() {
    val text: String = null
    println(text)
}
```

İkincisi, `String?` tipli bir değişkende doğrudan `length` istemektir. Derleyici bunu da reddeder: alıcı boş değer alabilen bir tip olduğunda yalnızca güvenli çağrıya ya da null olmama iddiasına izin verilir. Güvenli çağrı bir sonraki dersin konusudur; null olmama iddiası da orada, hatasıyla birlikte ölçülür.

```kotlin
fun main() {
    val text: String? = "kutu"
    println(text.length)
}
```

**TN1.** Bu iki red yalnız yerel değişken için ölçüldü; sınıf özelliği, fonksiyon parametresi ya da dönüş tipi için ayrıca denenmedi. Hata metni derleyici sürümüne bağlıdır ve burada aktarılmaz; ölçülen şey, programın hiç koşmamasıdır.

#### Null Denetiminden Sonra Tip Daralır

Bir `String?` değerinin uzunluğunu almak için önce `null` olmadığından emin olmak gerekir. Kotlin bunun için ayrı bir dönüştürme yazdırmaz. Bir `if` içinde `null` denetimi yapıldığında, o dalın içinde değişkenin tipi derleyici tarafından `String`'e daraltılır. Bu davranışa **akıllı tip dönüşümü** (`smart cast`) denir: programcı bir dönüştürme yazmaz, denetimin kendisi tipi değiştirir.

Aşağıdaki blokta `lengthOf` fonksiyonu `String?` alır; `if (text != null)` dalının içinde `text.length` doğrudan çağrılır. Blok ayrıca içinde iki `null` bulunan bir listeyi `filterNotNull` ile süzer; sonuç `List<String?>` değil `List<String>` olur ve elemanlar dönüştürme yapılmadan `length` verir.

```kotlin
// Main.kt — akilli tip donusumu
fun lengthOf(text: String?): Int {
    if (text != null) {
        return text.length
    }
    return -1
}

fun main() {
    println(lengthOf("kutu"))
    println(lengthOf(null))
    val values = listOf("a", null, "bb", null)
    println(values.size)
    val present = values.filterNotNull()
    println(present.size)
    println(present.sumOf { it.length })
}
```

```
4
-1
4
2
3
```

`lengthOf("kutu")` dört, `lengthOf(null)` ise `-1` verir. Liste dört elemanlıdır ama `filterNotNull` iki `null`'u atar ve geriye iki dizgi kalır; uzunluklarının toplamı üçtür. Burada ölçülen şey, `null` denetiminin yalnızca bir koşul olmadığıdır: denetimden sonra aynı değişken başka bir tiptir.

**TN2.** Akıllı tip dönüşümü yalnız `if` ve `filterNotNull` üzerinden ölçüldü; `when` içinde denetim, `&&` ile zincirlenen denetimler ve değiştirilebilir değişkenler için daralmanın ne zaman bozulduğu denenmedi. Daralma, denetimle kullanım arasında değişkenin değişmeyeceği bilindiği sürece geçerlidir.

#### Soru İşareti Nereye Yazılır

`?` işareti bir tipin yalnızca bir yerine yazılabilir ve yeri anlamı değiştirir. `List<String?>` elemanları boş olabilen bir listedir: liste vardır, içindeki bazı yerler `null`'dur. `List<String>?` ise listenin kendisi boş olabilen bir tiptir: liste ya vardır ya da yoktur, ama varsa hiçbir elemanı `null` değildir. İkisi birbirinden bağımsızdır ve ikisi aynı anda da yazılabilir.

Aşağıdaki blok iki durumu yan yana koyar. Elemanları boş olabilen listenin boyutu doğrudan okunur, çünkü liste kendisi boş olamaz. Listenin kendisi boş olabilen tipte ise önce `null` denetimi gerekir; denetimden sonra aynı değişken `List<String>` olarak davranır ve `size` doğrudan çağrılır.

```kotlin
// Main.kt — soru isareti nereye yazilir
fun main() {
    val items: List<String?> = listOf("a", null)
    val maybeList: List<String>? = null
    println(items.size)
    println(maybeList == null)
    if (maybeList != null) {
        println(maybeList.size)
    } else {
        println("liste-yok")
    }
}
```

```
2
true
liste-yok
```

**TN3.** Bu ölçüm boş bir listeyi değil, hiç bulunmayan bir listeyi (`null`) ele alır; boş ama var olan bir listenin davranışı denenmedi. İki `?` işaretinin birlikte yazıldığı `List<String?>?` biçimi de ölçülmedi.

#### null ile null

Kursun ölçü ekseninde asıl soru şudur: iki `null` aynı nesne midir? Kotlin'de `null` bir sınıfın örneği değil, tek bir değerdir. Her `null` ötekiyle aynıdır; birini diğerinden ayıracak bir kimlik yoktur. Dolayısıyla kimlik eşitliği (`===`) ve yapısal eşitlik (`==`) iki `null` için aynı yanıtı verir. Kural ilk soruda durur: ilk değer ile ikizi aynı nesneyse ölçüt tekildir ve geri kalan sorulara bakılmaz.

Aşağıdaki blokta dört gözlem vardır. İlk ikisinde ilk değer ile ikizi `null`'dur ve üçüncü değer birinde dolu, ötekinde yine `null`'dur. Üçüncü ve dördüncü gözlemde dolu dizgiler kullanılır: iki dizgi `buildString` ile ayrı ayrı kurulur, böylece aynı içeriği taşıyan iki ayrı nesne elde edilir.

```kotlin
// Main.kt — null ile null
fun main() {
    val nullFirst: String? = null
    val nullTwin: String? = null
    val full1 = buildString { append("ku"); append("tu") }
    val full2 = buildString { append("ku"); append("tu") }
    val checks = listOf(
        Criterion("null-null", nullFirst, nullTwin, "kutu"),
        Criterion("null-null-null", nullFirst, nullTwin, null),
        Criterion("dolu-dolu", full1, full2, "baska"),
        Criterion("dolu-null", full1, nullTwin, "baska"),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

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

Sonuç dört satırda okunur. `null` ile `null` tekildir. İki dolu dizgi, aynı içerikle ama ayrı nesneler olarak içerik ölçütünü verir: `ayni-nesne=false`, `ikiz=true`, `farkli=false`. Dolu bir dizgi ile `null` ise kimliktir: ikiz eşit değildir. Dolu dizgilerin ölçütü, bu derste yeniden tanımlanan hiçbir tipin değil, `String`'in kendi `equals`'inin sonucudur.

Burada bir tuzak vardır: iki `null`'un ölçütü tekil çıktı diye `null` ile karşılaştırılan her şeyin tekil olduğu sonucuna varılamaz. Tekil olan yalnızca `null` ile `null`'dur. Dolu bir değer `null`'a karşı kimliktir, çünkü ikiz eşit değildir.

**TN4.** Bu ölçüm yalnız `String?` tipli `null` değerleri için; başka tipli iki `null`'un aynı nesne sayılması denenmedi. Dolu dizgilerin ayrı nesne olduğu, `buildString` ile kurulduklarında `===` sonucundan okunmuştur; sabit dizgilerin aynı nesne olarak paylaşılıp paylaşılmadığı denenmedi.

#### Null Olabilen Değer Üzerinde Hazır Fonksiyonlar

`null` denetimi her zaman bir `if` ile yazılmaz. Standart kitaplık, alıcısı `String?` olan birkaç fonksiyonu hazır sunar: `toString`, `isNullOrEmpty` ve `orEmpty`. Bu fonksiyonlar `null` alıcıda istisna fırlatmaz, çünkü tanımları alıcının `null` olabileceğini baştan hesaba katar. Aşağıdaki blok üç değerle (`null`, boş dizgi ve dolu dizgi) bu fonksiyonları dener. Bir de dizgiyle `null` toplandığında ne çıktığına bakar.

```kotlin
// Main.kt — null olabilen alici uzerinde hazir islevler
fun main() {
    val missing: String? = null
    val empty: String? = ""
    val present: String? = "ab"
    println(missing.toString())
    println("[" + missing + "]")
    println(missing.isNullOrEmpty())
    println(empty.isNullOrEmpty())
    println(present.isNullOrEmpty())
    println(missing == null)
    println(missing.orEmpty().length)
}
```

```
null
[null]
true
true
false
true
0
```

Çıktıdan üç şey okunur. Birincisi, `missing.toString()` istisna fırlatmaz; `null` alıcıda `"null"` dizgisini verir. İlk satırdaki `null` bu yüzden bir dizgidir, `null` değer değildir. İkincisi, dizgiyle `null` toplamak da çalışır ve sonuç `[null]` olur: `String.plus` argüman olarak `Any?` aldığı için derleyici itiraz etmez; boş olabilen taraf sağdaki argümandır. Üçüncüsü, `isNullOrEmpty` hem `null` hem boş dizgi için `true` döner, dolu dizgi için `false`; `orEmpty` ise `null`'ı boş dizgiye çevirdiği için uzunluk `0` çıkar.

Bu fonksiyonların ortak yanı alıcı tipinin `String?` olmasıdır. Sıradan bir `String` yöntemi bu tipte doğrudan çağrılamaz, çünkü alıcı yok olabilir. Sık yapılan hata, `toString` sonucunu `null` değerle karıştırmaktır. Sonucun tipi her zaman `String`'dir; bir değerin yokluğunu `"null"` dizgisini arayarak ölçmek yanlıştır. Yokluk `== null` ile sorulur. Dizgiyi günlüğe yazan kodda iki durumun aynı görünmesi, hatanın geç fark edilmesinin başlıca nedenidir.

#### Tip Dönüşümü ve Null

Tip dönüşümü de `null` ile karşılaşır. `as` işleci dönüşümü zorlar, `as?` ise başarısız olunca `null` verir, `is` ise yalnızca soru sorar. Blok, değeri `null` olan bir `Any?` üzerinde üçünü dener; dönüşüm başarısız olursa fırlayan istisnanın yalnızca sınıf adı basılır. Son satırda dolu ama yanlış tipteki bir değer `as?` ile dönüştürülür.

```kotlin
// Main.kt — as ve as? null uzerinde
fun main() {
    val value: Any? = null
    println(value as? String)
    println(value is String)
    println(value is String?)
    try {
        val text = value as String
        println(text.length)
    } catch (e: Exception) {
        println(e::class.simpleName)
    }
    val other: Any? = 5
    println(other as? String)
}
```

```
null
false
true
NullPointerException
null
```

Üç ifade üç ayrı tavır sergiler. `as?` başarısız olduğunda istisna fırlatmaz, `null` verir; hem `null` değer hem de yanlış tipteki değer için sonuç `null`'dır. `is String` `null` için `false` döner, ama `is String?` `true` döner, çünkü `null` değeri `String?` tipinin bir üyesidir. Zorlayıcı `as String` ise `null` değerde `NullPointerException` fırlatır; blok bunu yakalayıp yalnızca sınıf adını basar.

Buradaki ders, iki `null` çıktısının aynı anlama gelmediğidir. İlk satırdaki `null`, dönüştürülecek değerin zaten yok olmasından gelir. Son satırdaki `null` ise değerin var olup tipinin uymamasından gelir ve yalnızca çıktıya bakarak bu ikisini ayırt etmek mümkün değildir. Ayrım gerekiyorsa dönüşümden önce `value == null` ayrıca sorulur. Dönüşümün zorlayıcı biçimi yalnızca tipin kesin bilindiği yerde yazılır; yanlış varsayım çalışma anında istisna olarak geri döner, derleme anında değil.

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

Bu dersin kuralı şudur: ölçüt, ilk sorunun yanıtıdır. Kural dört soruyu sırayla sorar ve ilk "evet"te durur. Önceki bölümdeki ilk iki satır bunu zaten gösterdi, ama farkı açıkça görmek gerekir: `null-null` satırında üçüncü değer dolu (`"kutu"`), `null-null-null` satırında üçüncü değer `null`. Üçüncü değerin eşitliği (`farkli`) iki satırda zıt çıktı, ölçüt ise ikisinde de tekildir. `farkli=false` ile `farkli=true` arasında ölçütü değiştiren hiçbir şey yoktur; çünkü `Criterion` ilk soruda durur ve üçüncü değer ölçüte hiçbir bilgi katmaz.

Bu, sınırın nerede olduğunu gösterir. `farkli` sütunu basılır, çünkü `Criterion` üçüncü karşılaştırmayı her durumda hesaplar; ama tekil bir ölçütte o sütun okunmaz. Bir değerin tekil olduğunu söylemek, ona ne kadar çok değerin eşit olduğunu söylemez. Yalnızca ikizin aynı nesne olduğunu söyler.

Aynı sınır tersinden de okunur. İlk değer `null`, ikizi dolu bir dizgi olsaydı `ayni-nesne=false` ve `ikiz=false` çıkardı: ölçüt kimlik olurdu. Yani `null` ile başlamak ölçütü tekil yapmaz; tekil olmak için ikizin de `null` olması gerekir. Ölçütü belirleyen, ilk değerin tipi değil, ilk değer ile ikizinin aynı nesne olup olmadığıdır.

**TN5.** Sınırlayıcı ölçüm iki `null` ve bir dolu ya da `null` üçüncü değerle yapıldı; ilk değerin dolu, ikizin `null` olduğu sıralamanın ayrı bir çıktısı alınmadı, yalnızca `dolu-null` satırındaki kimlik sonucundan çıkarıldı. `Criterion`'ın kuralı başka sıralarla denenmedi.

#### Özet

- `String` ile `String?` ayrı tiplerdir: `String`'e `null` atamak ve `String?` üzerinde doğrudan `length` istemek derlenmez.
- Bir `if` içindeki null denetimi, o dalda değişkenin tipini `String`'e daraltır; bu akıllı tip dönüşümüdür ve dönüştürme yazılmaz.
- `filterNotNull` bir `List<String?>`'i `List<String>`'e çevirir ve `null` elemanları atar.
- İki `null` aynı nesnedir ve ölçüt tekildir; dolu ayrı iki dizgi içerik, dolu dizgi ile `null` kimlik verir.
- Kural ilk soruda durur: ilk iki satırda üçüncü değerin eşitliği zıt çıktı, ölçüt aynı kaldı.
- Bu dersin ölçütü: **tekil**. `null` ile `null` aynı nesnedir.

#### Sonraki Adım

Bu derste `null`'u tipte gördük ve `if` ile denetledik. Her `null` denetimi ayrı bir `if` ister ve denetimler art arda dizildikçe kod uzar. Bir sonraki ders aynı işi kısa yoldan yapan işleçleri ölçer: `?.` ile güvenli çağrı zinciri `null` gördüğünde nerede durur, `?:` ile Elvis işleci sağ tarafını ne zaman değerlendirir ve null olmama iddiası yanlış çıktığında hangi istisna fırlar.

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

Source: https://academia.sh/tr/kurslar/kotlin/guvenli-cagri-ve-elvis-isleci

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.

### Sınıflar ve Yapıcılar

Source: https://academia.sh/tr/kurslar/kotlin/siniflar-ve-yapicilar

Birincil yapıcının, init bloğunun ve ikincil yapıcının çalışma sırasının ve equals yazılmamış bir sınıfın örneklerinin neden kimlik ölçütüne düştüğünün ölçümü.

Önceki ders Elvis işlecinin sağ tarafında `equals`'i yazılmamış bir sınıfın ölçütü kimliğe çevirdiğini gösterdi. Bu ders o sınıfların nasıl yazıldığına bakar: birincil yapıcı, `init` bloğu, ikincil yapıcı ve özellikler. Önce hangisinin ne zaman çalıştığı ölçülür, sonra aynı değerli iki örneğin neden eşit sayılmadığı ölçüt üzerinden açıklanır.

Ölçü ekseni aynı kalır: 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. Bu ders kimlik kesitini ölçer ve sınırlayıcı ölçümde içerik ile kısmi içeriğe geçer.

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

#### Yapıcıların Çalışma Sırası

Kotlin'de bir sınıfın birincil yapıcısı sınıf başlığına yazılır: `class Member(val name: String, val level: Int)`. Parametrenin önündeki `val` hem parametreyi hem aynı adlı özelliği tanımlar. Gövdede başlatıcılı özellikler ve `init` blokları yer alabilir; bunlar birincil yapıcının gövdesi gibi davranır ve dosyadaki yazılış sırasıyla çalışır. İkincil yapıcı `constructor` anahtar sözcüğüyle yazılır ve `: this(...)` ile birincil yapıcıya yönlenmek zorundadır. Yönlenilen yapıcı önce koşar, ikincil yapıcının kendi gövdesi en sonda.

Aşağıdaki blok bu sırayı çıktıdan okunur hâle getirir. `trace` fonksiyonu her çağrıldığında bir satır basar. `title` özelliğinin başlatıcısı `trace` çağırır, `init` bloğu bir satır basar, ikincil yapıcının gövdesi de bir satır basar. `Member("ada")` ikincil yapıcıyı, `Member("can", 2)` doğrudan birincil yapıcıyı çağırır.

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

class Member(val name: String, val level: Int) {
    val title = trace("ozellik")

    init {
        println("init: $name")
    }

    constructor(name: String) : this(name, 1) {
        println("ikincil: $name")
    }
}

fun main() {
    val first = Member("ada")
    println(first.level)
    val second = Member("can", 2)
    println(second.level)
}
```

```
adim: ozellik
init: ada
ikincil: ada
1
adim: ozellik
init: can
2
```

İki kurulumda da `adim: ozellik` satırı `init: ...` satırından önce gelir, çünkü `title` bloktan önce yazılmıştır. İkincil yapıcı yalnızca ilk kurulumda koşar ve `ikincil: ada` satırı `init: ada` satırından sonra basılır: birincil yapıcı ve onun başlatıcıları ikincil yapıcının gövdesinden önce tamamlanır. İkinci kurulumda `ikincil` satırı yoktur, çünkü birincil yapıcı doğrudan çağrılmıştır.

**TN11.** Bu ölçüm yalnızca bu sınıf için sıra gösterir. Başlatıcı ile `init` bloğunun yer değiştirmesi çıktıyı değiştirir; sıra yazılış sırasıdır. Hangi başlatıcının hangi `init` bloğundan önce geleceği, ölçülen sınıfın yazılışına bağlıdır.

#### Özellikler, Varsayılan Değerler ve Erişimciler

Birincil yapıcıdaki `val` ve `var` parametreleri özellik olur. Özelliklerin hesaplanan türü de vardır: `val area: Int get() = width * height` bir alanda saklanmaz, her okunduğunda hesaplanır. Parametrelere varsayılan değer verilebilir ve çağıran yer parametreleri adıyla, sırayı bozarak da verebilir. `var` bir özelliğe özel `set` yazılırsa atanan değer alana geçmeden önce işlenebilir; alanın kendisine `field` adıyla erişilir.

Aşağıdaki blok üç şeyi ölçer. `Rectangle(3)` yüksekliği varsayılan değer olan 1'e bırakır. `Rectangle(height = 2, width = 5)` parametreleri sırasını bozarak adıyla verir. Son olarak `label` özelliğine baş ve sonunda boşluk olan bir dizgi atanır; `set` bu boşlukları temizler.

```kotlin
// Main.kt — ozellikler
class Rectangle(val width: Int, val height: Int = 1) {
    val area: Int get() = width * height

    var label: String = "adsiz"
        set(value) {
            field = value.trim()
        }
}

fun main() {
    val narrow = Rectangle(3)
    println(narrow.area)
    val wide = Rectangle(height = 2, width = 5)
    println(wide.area)
    wide.label = "  kutu  "
    println("[" + wide.label + "]")
}
```

```
3
10
[kutu]
```

Alan 3 çarpı 1 ve 5 çarpı 2 olarak hesaplanmıştır. Etiket atandığı gibi değil, `set` içinden geçtikten sonra saklanmıştır: köşeli parantezler boşluk kalmadığını gösterir.

**TN12.** `area` bir alanda saklanmaz. Ölçülen blok yalnızca okuma sonucunu gösterir; `width` ve `height` değişebilir olsaydı `area`'nın değişeceği bu bloktan çıkmaz. Bu yalnızca yapıcıdan gelen `val` değerleri için ölçüldü.

#### Equals Yazılmamış Bir Sınıf

Şimdi ölçüte dönülür. `Coordinate` sınıfının iki yapıcısı vardır: birincil yapıcı iki sayı alır, ikincil yapıcı `"3,4"` biçimindeki bir dizgiyi ayırarak aynı birincil yapıcıya yönlenir. Sınıfta `equals` yoktur. Kotlin'de her sınıf `Any`'den türer ve `Any`'nin `equals` yöntemi iki referansın aynı nesneyi gösterip göstermediğine bakar. Sınıf bunu yeniden tanımlamadıkça `==` işleci de kimliğe bakar.

```kotlin
// Coordinate.kt — equals yazilmamis sinif
class Coordinate(val x: Int, val y: Int) {
    constructor(text: String) : this(text.substringBefore(",").toInt(), text.substringAfter(",").toInt())
}
```

Ölçüm üç satırdan oluşur. İlkinde üç değerden ilk ikisi aynı nesnedir. İkincisinde iki ayrı `Coordinate(3, 4)` birincil yapıcıdan kurulur. Üçüncüsünde ikiz ikincil yapıcıdan, yani dizgiden kurulur; değerler yine 3 ve 4'tür.

```kotlin
// Main.kt — iki yoldan kurulan ornekler
fun main() {
    val same = Coordinate(1, 2)
    val checks = listOf(
        Criterion("ayni-nesne", same, same, Coordinate(1, 3)),
        Criterion("ayni-yol", Coordinate(3, 4), Coordinate(3, 4), Coordinate(3, 5)),
        Criterion("iki-yol", Coordinate(3, 4), Coordinate("3,4"), Coordinate(3, 5)),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
ayni-nesne: ayni-nesne=true, ikiz=true, farkli=false -> tekil
ayni-yol: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
iki-yol: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
tekil: 1
kimlik: 2
icerik: 0
kismi-icerik: 0
```

Kural ilk satırda ilk soruda durur: aynı nesne olduğu için ölçüt tekildir. İkinci ve üçüncü satırlarda alanlar birebir aynı olduğu hâlde `ikiz=false` çıkar ve ölçüt kimliktir. Kurulum yolu, yani birincil mi ikincil mi olduğu, sonucu değiştirmez. Aynı değerli iki örneği eşit yapan şey sınıfın alanları değildir; eşit yapan, `equals`'in ne yaptığıdır.

Bu dersin ölçüsünü bir ayrımla tamamlamak gerekir. Burada ölçülen, iki örneğin birbirine eşit sayılıp sayılmadığıdır. Bir örneğin başka bir yerden paylaşılıp paylaşılmadığı ya da bir değişikliğin başka bir referansta görünüp görünmediği ayrı bir sorudur ve bu derste ölçülmedi. Kimlik ölçütü yalnızca şunu söyler: `==` iki ayrı nesne için `false` verir.

**TN13.** Ölçüm yalnızca `Coordinate` ve iki yapıcı yolu içindir. Alanları başka tipte olan, ya da `Any`'den başka bir üst sınıftan türeyen bir sınıf için aynı sonuç ölçülmedi. Üst sınıf `equals`'i yeniden tanımlamışsa tablo başka olabilir.

#### Kalıtımda Yapıcı Sırası

Bir sınıf başka bir sınıftan türediğinde yapıcı sırasına bir halka daha eklenir. Kotlin sınıfları varsayılan olarak kapalıdır; türetilecek sınıfa `open` yazılır ve alt sınıf, üst sınıfın yapıcısını başlık satırında çağırır. Aşağıdaki blokta hem üst sınıfın hem alt sınıfın bir `init` bloğu vardır. Hangisinin önce çalıştığı çıktıdan okunur.

```kotlin
// Main.kt — ust sinif yapicisi
open class Base(val name: String) {
    init {
        println("Base init: $name")
    }
}

class Derived(name: String, val extra: Int) : Base(name) {
    init {
        println("Derived init: $extra")
    }
}

fun main() {
    val item = Derived("ada", 3)
    println(item.name + item.extra)
}
```

```
Base init: ada
Derived init: 3
ada3
```

Çıktı sırayı açıkça gösterir: `Base init: ada` satırı `Derived init: 3` satırından önce gelir. Alt sınıfın başlık satırındaki `Base(name)` çağrısı, alt sınıfın kendi başlatıcıları çalışmadan önce üst sınıfı kurar. Nedeni basittir: alt sınıfın durumu üst sınıfın durumuna dayanabilir, bu yüzden dayanılan taraf önce hazır olmalıdır. Son satır, `name` değerinin üst sınıftan, `extra` değerinin alt sınıftan geldiğini ve ikisine tek nesne üzerinden erişildiğini gösterir.

Bu sıra, önceki bölümdeki çalışma sırasını genişletir. Tek sınıfta sıra özellik başlatıcıları, `init` blokları ve ikincil yapıcı gövdesidir; türetmede bunun önüne üst sınıfın tamamı eklenir. Üst sınıfın yapıcısı içinde alt sınıfın bir özelliğine dokunulursa o özellik henüz kurulmamıştır; bu nedenle üst sınıf yapıcıları alt sınıfa ait durumu okumaz. Sorumluluğun sınırı bu çıktıda görünür: her sınıf yalnızca kendi `init` bloğunda kendi alanlarını kurar.

#### Okuma Başına Hesap ve Tek Seferlik Başlatıcı

Daha önce `area` özelliğinin bir alanda saklanmadığı, her okunduğunda hesaplandığı söylendi. Bunun ölçülebilir bir sonucu var: hesaplayan fonksiyon kaç kez çağrılır? Blokta iki özellik aynı fonksiyonu çağırır. Biri başlatıcıyla tanımlanmıştır, öteki `get()` ile. İkisi de üçer kez okunur; sayaçlar fonksiyonun kaç kez koştuğunu tutar.

```kotlin
// Main.kt — okuma basina hesap
var storedCalls = 0
var computedCalls = 0

fun compute(kind: String): Int {
    if (kind == "stored") storedCalls++ else computedCalls++
    return 10
}

class Holder {
    val stored: Int = compute("stored")
    val computed: Int get() = compute("computed")
}

fun main() {
    val holder = Holder()
    println(holder.stored + holder.stored + holder.stored)
    println(holder.computed + holder.computed + holder.computed)
    println("stored=$storedCalls computed=$computedCalls")
}
```

```
30
30
stored=1 computed=3
```

Çıktının son satırı sayaçları verir: `stored=1 computed=3`. İki özellik de üçer kez okundu ve ikisi de `30` toplamını verdi, ama hesaplayan fonksiyon farklı sayıda çalıştı. Başlatıcıyla tanımlanan özellik değeri nesne kurulurken bir kez hesaplar ve bir alanda saklar. `get()` ile tanımlanan özellik ise alan taşımaz; her okumada fonksiyon yeniden çalışır.

İki sonuç da aynı değeri döndürdüğü için fark çıktıdan değil, yalnızca sayaçtan görünür. Bu yüzden `get()` ile tanımlanan özellik, ucuz ve her seferinde güncel olması gereken değerler için uygundur: başka özelliklerden türeyen bir alan ya da bir toplam. Pahalı bir hesap ya da yan etkisi olan bir fonksiyon `get()` içine konursa her okuma o bedeli öder ve yan etki okuma sayısı kadar tekrarlanır. Hesabın bir kez yapılması gerekiyorsa başlatıcı, her seferinde güncel olması gerekiyorsa erişimci seçilir.

#### Yazılan Equals ve Başka Tipler

Elle yazılan bir `equals` yalnızca aynı tipteki değerlerle karşılaşmaz. Parametresi `Any?` olduğu için `null` ve alakasız tipler de gelebilir. Yazdığımız `other is ValueCoordinate` denetimi bu durumları ayıklar. Blok `equals`'i dört kez çağırır: `null` ile, bir dizgiyle, aynı değerli başka bir örnekle ve nesnenin kendisiyle.

```kotlin
// Main.kt — equals ve baska tipler
class ValueCoordinate(val x: Int, val y: Int) {
    override fun equals(other: Any?): Boolean =
        other is ValueCoordinate && x == other.x && y == other.y

    override fun hashCode(): Int = 31 * x + y
}

fun main() {
    val point = ValueCoordinate(1, 2)
    val anything: Any = "1,2"
    println(point.equals(null))
    println(point.equals(anything))
    println(point == ValueCoordinate(1, 2))
    println(point.equals(point))
}
```

```
false
false
true
true
```

Dört çağrının hepsi `Boolean` döner ve çıktı `false`, `false`, `true`, `true` olur. `equals(null)` `false` verir, çünkü `is` denetimi `null` için başarısız olur. Dizgi `Any` olarak verildiğinde de `false` çıkar; tip uyuşmadığı için alanlara bakılmaz bile. Aynı değerli ayrı örnekte `true`, nesnenin kendisinde yine `true` döner.

Parametrenin `Any?` olması bir sözleşmedir: `equals` her türlü değerle karşılaşabilir ve istisna fırlatmadan `false` demelidir. Sık yapılan hata, parametreyi `ValueCoordinate` varsayıp doğrudan dönüştürmek, ya da tip denetimini atlayıp alanlara erişmektir. Doğru kalıp önce `is` denetimidir; `&&` zinciri sayesinde alan karşılaştırması yalnızca denetim geçtikten sonra yapılır ve derleyici sağ taraftaki `other` değerini `ValueCoordinate` olarak görür. `hashCode` aynı alanlardan hesaplanır; eşit nesneler eşit karma kodu üretmelidir, aksi halde karma tabanlı koleksiyonlar tutarsız çalışır.

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

Eşitliği sınıfın alanları değil, yazılan `equals` belirliyorsa aynı alanlara sahip sınıflar `equals`'e göre farklı ölçütlere düşmelidir. Sınırlayıcı ölçüm bunu üç satırla dener. `ValueCoordinate` iki alana da bakan bir `equals` ve onunla uyumlu bir `hashCode` yazar. `LeftCoordinate` yalnızca `x` alanına bakar. Üç satırdan ilki tam eşitliği, ikincisi `x` aynı `y` farklı durumu, üçüncüsü `x` farklı durumu dener. Bloğun sonundaki iki satır `hashSetOf` ile aynı değerli iki örneği bir kümeye koyar ve kümenin boyutunu basar; `equals` ile birlikte `hashCode`'un da yazıldığını bu yoldan gözlemler. `hashCode` değerlerinin kendisi basılmaz.

```kotlin
// Main.kt — equals yazilinca
class ValueCoordinate(val x: Int, val y: Int) {
    override fun equals(other: Any?): Boolean =
        other is ValueCoordinate && x == other.x && y == other.y

    override fun hashCode(): Int = 31 * x + y
}

class LeftCoordinate(val x: Int, val y: Int) {
    override fun equals(other: Any?): Boolean = other is LeftCoordinate && x == other.x

    override fun hashCode(): Int = x
}

fun main() {
    val checks = listOf(
        Criterion("tam", ValueCoordinate(3, 4), ValueCoordinate(3, 4), ValueCoordinate(3, 5)),
        Criterion("yalniz-x-y-farkli", LeftCoordinate(3, 4), LeftCoordinate(3, 4), LeftCoordinate(3, 5)),
        Criterion("yalniz-x-x-farkli", LeftCoordinate(3, 4), LeftCoordinate(3, 4), LeftCoordinate(2, 4)),
    )
    for (check in checks) check.report()
    summarize(checks)
    println(hashSetOf(Coordinate(3, 4), Coordinate(3, 4)).size)
    println(hashSetOf(ValueCoordinate(3, 4), ValueCoordinate(3, 4)).size)
}
```

```
tam: ayni-nesne=false, ikiz=true, farkli=false -> icerik
yalniz-x-y-farkli: ayni-nesne=false, ikiz=true, farkli=true -> kismi-icerik
yalniz-x-x-farkli: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 0
kimlik: 0
icerik: 2
kismi-icerik: 1
2
1
```

Aynı sınıf kalıbı üç satırda iki ölçüt verir. `ValueCoordinate` iki alanı da karşılaştırdığı için ikiz ve farklı değer ayrışır: içerik. `LeftCoordinate` `y` farklı olduğunda bile eşit çıkar, dolayısıyla üçüncü değer ikizle aynı sonucu verir ve ölçüt kısmi içeriğe düşer; `x` farklı olduğunda ise içerik çıkar. Aynı sınıfın iki ayrı satırı iki ayrı ölçüt verir; demek ki ölçütü bir sınıf değil, `equals`'in baktığı alanlar ve karşılaştırılan değer çifti birlikte belirler. Kümelerde `Coordinate` iki ayrı öğe olarak kalır, `ValueCoordinate` tek öğeye iner.

**TN14.** Bu ölçüm iki alanlı küçük bir sınıf içindir. Daha çok alanı olan bir sınıfta hangi alanların `equals`'e girdiği her sınıfa özgüdür; aynı davranış ölçülmeden varsayılamaz.

#### Özet

- Birincil yapıcı sınıf başlığına yazılır; başlatıcılar ve `init` blokları yazılış sırasıyla koşar, ikincil yapıcının gövdesi en sonda.
- İkincil yapıcı `: this(...)` ile birincil yapıcıya yönlenmek zorundadır ve yönlenilen yapıcı önce tamamlanır.
- Özelliklere varsayılan değer, adlı argüman, hesaplanan okuma ve özel `set` ile davranış verilebilir.
- `equals` yazılmamış bir sınıfın aynı değerli örnekleri, hangi yapıcıyla kurulurlarsa kurulsunlar, kimlik ölçütüne düşer.
- Bu dersin ölçütü: **kimlik**. Sınırlayıcı ölçümde aynı sınıf kalıbı `equals`'e göre içeriğe ve kısmi içeriğe de düştü.

#### Sonraki Adım

Bir sınıfı değer gibi davranacak hâle getirmek için `equals`, `hashCode` ve `toString`'i elle yazmak gerekti. Bir sonraki ders bu üçünü birincil yapıcıdaki özelliklerden üreten veri sınıflarını ölçer: `copy` ile kurulan ayrı nesnenin eşit sayılıp sayılmadığına ve yapısöküm bildiriminin ne yaptığına bakar.

### Veri Sınıfları

Source: https://academia.sh/tr/kurslar/kotlin/veri-siniflari

Veri sınıfının birincil yapıcıdaki özelliklerden ürettiği yöntemlerin, copy ile kurulan ayrı nesnenin ve alan tipinin ölçüte etkisinin ölçümü.

Önceki ders `equals`'i yazılmamış bir sınıfın aynı değerli örneklerinin kimlik ölçütüne düştüğünü, `equals` ve `hashCode` elle yazılınca içeriğe geçtiğini gösterdi. O iki yöntemi her sınıfta elle yazmak sıkıcı ve hataya açıktır. Kotlin bu işi `data class` anahtar sözcüğüyle derleyiciye bırakır: birincil yapıcıdaki özelliklerden `equals`, `hashCode`, `toString`, `copy` ve `componentN` yöntemlerini üretir. Bu ders üretilenlerin ne yaptığını ve ölçütü nasıl kurduğunu ölçer.

Ölçü ekseni aynı kalır: 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. Bu ders içerik kesitini ölçer.

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

#### Üretilen Yöntemler

`data class Ticket(val id: Int, val owner: String)` tek satırdır ve gövdesi yoktur. Aşağıdaki blok üretilenleri sırayla dener. `println(ticket)` üretilmiş `toString`'i gösterir. İki karşılaştırma `equals`'e bakar: aynı değerli ayrı bir `Ticket` ile ve `owner`'ı farklı bir `Ticket` ile. Dördüncü satır iki eşit nesnenin `hashCode` değerlerinin eşit olup olmadığını sorar; değerin kendisi basılmaz. Son iki satır `copy`'yi dener: `owner` değiştirilerek kurulan kopya ile ilk nesnenin kendisi.

```kotlin
// Main.kt — uretilen yontemler
data class Ticket(val id: Int, val owner: String)

fun main() {
    val ticket = Ticket(7, "ada")
    println(ticket)
    println(ticket == Ticket(7, "ada"))
    println(ticket == Ticket(7, "can"))
    println(ticket.hashCode() == Ticket(7, "ada").hashCode())
    val moved = ticket.copy(owner = "can")
    println(moved)
    println(ticket)
}
```

```
Ticket(id=7, owner=ada)
true
false
true
Ticket(id=7, owner=can)
Ticket(id=7, owner=ada)
```

`toString` alanları adlarıyla basar. `equals` aynı değerli ayrı nesneyi eşit, `owner`'ı farklı olanı eşit değil bulur ve `hashCode` eşit nesnelerde eşit çıkar. `copy(owner = "can")` ayrı bir nesne kurar; ilk nesne değişmeden kalır.

**TN15.** Ölçüm iki alanlı küçük bir veri sınıfı içindir. Çok alanlı bir sınıfta ya da alanlardan biri başka bir veri sınıfı olduğunda üretilen yöntemlerin aynı davranıp davranmadığı bu bloktan çıkmaz.

#### Copy ve Ölçüt

Şimdi aynı yöntemler ölçüte bağlanır. Üç satır vardır. İlkinde üç değerden ilk ikisi aynı nesnedir. İkincisinde ikiz `ticket.copy()` ile, yani hiçbir alan değiştirilmeden kurulur. Üçüncüsünde ikiz birincil yapıcıyı doğrudan çağırarak kurulur. Her satırda üçüncü değer `id`'si ya da `owner`'ı farklı bir `Ticket`'tır.

```kotlin
// Main.kt — copy ve olcut
data class Ticket(val id: Int, val owner: String)

fun main() {
    val ticket = Ticket(7, "ada")
    val checks = listOf(
        Criterion("ayni-nesne", ticket, ticket, ticket.copy(id = 8)),
        Criterion("copy", ticket, ticket.copy(), ticket.copy(id = 8)),
        Criterion("kurucu", ticket, Ticket(7, "ada"), Ticket(7, "can")),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
ayni-nesne: ayni-nesne=true, ikiz=true, farkli=false -> tekil
copy: ayni-nesne=false, ikiz=true, farkli=false -> icerik
kurucu: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 1
kimlik: 0
icerik: 2
kismi-icerik: 0
```

Kural ilk satırda ilk soruda durur: aynı nesne olduğu için ölçüt tekildir. İkinci ve üçüncü satırlarda ikiz ayrı bir nesnedir, `ikiz=true` çıkar, üçüncü değer eşit olmadığı için ölçüt içeriktir. `copy()` ile kurulan nesne ile yapıcıyla kurulan nesne arasında ölçüt farkı yoktur. Önceki dersteki `Coordinate` ise aynı iki satırda kimlik veriyordu. Fark kurulum yolunda değil, `equals`'in kim tarafından yazıldığındadır.

Bu dersin ayrımı da buradadır: ölçülen, `copy`'nin bir yöntem olarak bulunması değil, eşitliği hangi ölçütün verdiğidir. `copy` ayrı bir nesne kurar; o nesnenin eşit sayılması üretilen `equals`'ten gelir.

**TN16.** Ölçüm yalnızca `Ticket` içindir. `copy()` yüzeysel bir kopya kurar; alanlar değişebilir bir nesneyse iki kopyanın o alanı paylaşıp paylaşmadığı bu blokta ölçülmedi.

#### Yapısöküm Bildirimi ve componentN

Veri sınıfının ürettiği `component1`, `component2` gibi yöntemler yapısöküm bildirimini mümkün kılar: `val (id, owner) = ticket` satırı, her ada sırayla bir `componentN` çağrısı atar. Sıra birincil yapıcıdaki parametre sırasıdır. Aşağıdaki blok bildirimi ve iki `componentN` çağrısını dener. Ardından üretilen yöntemlerin hangi alanlara baktığını sorar: `Stamped` sınıfında `note` alanı birincil yapıcıda değil gövdede tanımlıdır. İki `Stamped` nesnesi aynı yapıcı değerleriyle ama farklı `note` ile kurulur ve karşılaştırılır.

```kotlin
// Main.kt — yapisokum ve componentN
data class Ticket(val id: Int, val owner: String)

data class Stamped(val id: Int, val owner: String) {
    var note: String = ""
}

fun main() {
    val ticket = Ticket(7, "ada")
    val (id, owner) = ticket
    println("$id $owner")
    println(ticket.component1())
    println(ticket.component2())
    val first = Stamped(1, "ada")
    first.note = "a"
    val second = Stamped(1, "ada")
    second.note = "b"
    println(first == second)
    println(first)
}
```

```
7 ada
7
ada
true
Stamped(id=1, owner=ada)
```

Bildirim `7 ada` verir. `component1()` ve `component2()` aynı değerleri doğrudan döndürür. `first == second` ise `true` çıkar, oysa `note` alanları `a` ve `b`'dir. `toString` da `note`'u basmaz. Üretilen yöntemler yalnızca birincil yapıcıdaki özelliklere bakar; gövdedeki özellik eşitliğe ve çıktıya girmez.

**TN17.** Bu yalnızca gövdede `var` ile tanımlanan bir özellik için ölçüldü. Gövdede tanımlanıp hesaplanan bir özelliğin ya da üst sınıftan gelen bir özelliğin ayrı bir davranışı olup olmadığı ölçülmedi.

#### Copy Yüzeyseldir

`copy` alanları kopyalar, ama bir alan değişebilir bir koleksiyonsa kopyalanan şey koleksiyonun kendisi değil, ona giden referanstır. Blokta `Basket` bir `MutableList` taşır. İlk kopyaya öğe eklenince ilk nesne etkilenir mi? İkinci denemede liste açıkça kopyalanır ve aynı soru yeniden sorulur.

```kotlin
// Main.kt — copy yuzeyseldir
data class Basket(val owner: String, val items: MutableList<String>)

fun main() {
    val first = Basket("ada", mutableListOf("elma"))
    val second = first.copy(owner = "can")
    second.items.add("armut")
    println(first.items)
    println(second.items)
    println(first.items === second.items)
    val deep = first.copy(items = first.items.toMutableList())
    deep.items.add("kiraz")
    println(first.items)
    println(deep.items)
}
```

```
[elma, armut]
[elma, armut]
true
[elma, armut]
[elma, armut, kiraz]
```

Çıktının ilk iki satırı `[elma, armut]` olarak aynıdır. `second` üzerinden eklenen `armut`, `first` içinde de görünür. Üçüncü satır nedenini söyler: `first.items === second.items` `true` verir, yani iki nesne aynı listeye gidiyor. `copy` alanları tek tek kopyalar, ama liste alanında kopyalanan şey listenin kendisi değil, ona giden referanstır. Kopyalama yüzeyseldir.

Son iki satırda liste `toMutableList()` ile açıkça kopyalanır. Bu kez `deep` içine eklenen `kiraz`, `first` içinde görünmez. Sık yapılan hata, `copy`'yi tam bağımsız bir kopya sanmaktır. Değişebilir bir koleksiyon taşıyan veri sınıfında `copy` çağrısı, bir yandan eşitlik semantiğini korur, öte yandan iki nesneyi aynı durumun sahibi yapar. Çözüm ya alanı salt okunur `List` yapmak ya da koleksiyonu `copy` çağrısında açıkça kopyalamaktır. İçerik ölçütü burada da geçerlidir: iki nesne `equals` bakımından eşittir, ama biri değişince öteki de değişir.

#### Anahtar Olarak Kullanım ve Sık Yapılan Hata

Üretilen `hashCode`, veri sınıfını harita anahtarı ve küme öğesi olarak kullanılabilir kılar. Aynı değerli ayrı bir nesneyle arama bulur. Ama alanı `var` olan bir veri sınıfı kümeye girdikten sonra değiştirilirse karma kodu değişir ve nesne kümede bulunduğu yerden ayrılır. Blok önce haritadan aynı değerli bir anahtarla okur, sonra kümeye konan bir nesnenin alanını değiştirip aramayı yineler.

```kotlin
// Main.kt — anahtar olarak veri sinifi
data class Ticket(val id: Int, var owner: String)

fun main() {
    val prices = hashMapOf(Ticket(1, "ada") to 10)
    println(prices[Ticket(1, "ada")])
    val key = Ticket(2, "can")
    val set = hashSetOf(key)
    println(set.contains(key))
    key.owner = "eda"
    println(set.contains(key))
    println(set.size)
    println(set.contains(Ticket(2, "can")))
    println(set.any { it == key })
}
```

```
10
true
false
1
false
true
```

Haritadan okuma `10` verir: aynı değerli ayrı bir `Ticket` anahtar olarak bulunur, çünkü eşitlik ve karma kodu alan değerlerinden gelir. Kümeye konan nesne için `contains` önce `true` döner. `key.owner` değiştirildikten sonra aynı nesneyle yapılan arama `false` olur, ama `size` hâlâ `1`'dir: nesne kümededir, yalnızca bulunamaz. Eski değerle kurulan `Ticket(2, "can")` de bulunamaz, çünkü kümedeki öğe şimdi `eda` sahibini taşır ve eşit değildir. Doğrusal `any` taraması ise nesneyi bulur.

Mekanizmanın nedeni şudur: karma tabanlı koleksiyon öğeyi, eklendiği andaki karma koduna göre yerleştirir. Alan değişince kod değişir ve arama başka bir yere bakar. Sık yapılan hata, anahtar ya da küme öğesi olarak kullanılan bir veri sınıfında `var` alan bırakmaktır. Kural: koleksiyona konan bir nesnenin eşitliğe giren alanları o nesne koleksiyondayken değiştirilmez. Anahtar olacak veri sınıfları `val` alanlarla yazılır, değişiklik gerekiyorsa `copy` ile ayrı bir nesne üretilip koleksiyondan eskisi çıkarılır.

#### Yapısöküm Döngülerde

Yapısöküm bildirimi yalnızca `val` satırlarında değil, `for` döngülerinde ve lambda parametrelerinde de çalışır. Kullanılmayan bir konum `_` ile atlanır. Harita girdileri de `component1` ve `component2` sağladığı için aynı biçimde açılır. Blok bu biçimlerin hepsini dener.

```kotlin
// Main.kt — yapisokum dongude
data class Ticket(val id: Int, val owner: String)

fun main() {
    val tickets = listOf(Ticket(1, "ada"), Ticket(2, "can"))
    for ((id, owner) in tickets) println("$id=$owner")
    for ((_, owner) in tickets) println(owner)
    val (first) = tickets[1]
    println(first)
    val prices = mapOf("elma" to 3, "armut" to 5)
    for ((name, price) in prices) println("$name:$price")
    println(tickets.map { (id, _) -> id })
}
```

```
1=ada
2=can
ada
can
2
elma:3
armut:5
[1, 2]
```

İlk iki döngü `1=ada`, `2=can`, sonra yalnızca sahip adlarını basar; `_` ilk konumu atlar. `val (first) = tickets[1]` satırı dikkat ister: `first` ilk konumdaki değerdir (`component1`), yani `id`'dir ve çıktı `2` olur, ilk bilet değil. Harita döngüsünde `(name, price)` girdileri `component1` ve `component2` ile açar. Son satırdaki lambda parametresi de aynı biçimde açılır ve `[1, 2]` verir.

Yapısökümün belirleyici yanı, alan adlarına değil sıraya dayanmasıdır. `componentN` fonksiyonları, birincil yapıcıdaki parametre sırasıyla numaralanır. Sık yapılan hata, sınıfa ek bir alan eklerken sıralamayı bozmaktır; yapıcıda `id` ile `owner` arasına bir alan girerse eski yapısöküm satırları hâlâ derlenir ama başka alanı okur. Bu nedenle alanlar yalnızca sonuna eklenir ve yapısöküm yalnızca küçük, kararlı veri sınıflarında kullanılır. Gerisi için `ticket.owner` biçimi, sıraya değil adın anlamına dayandığı için daha dayanıklıdır.

#### İç İçe Veri Sınıfları

Bir veri sınıfının alanı da veri sınıfıysa üretilen yöntemler iç içe çalışır. `toString` iç nesneyi de açar, `equals` iç nesnenin `equals`'ine iner, `copy` ise iç nesneyi olduğu gibi taşır. Blok, `null` olabilen bir iç alanla bunu dener.

```kotlin
// Main.kt — ic ice veri siniflari
data class Address(val city: String)
data class Person(val name: String, val address: Address?)

fun main() {
    val person = Person("ada", Address("izmir"))
    println(person)
    println(person == Person("ada", Address("izmir")))
    println(person.copy(address = null))
    println(person.copy(address = Address("ankara")) == person)
    println(Person("ada", null) == Person("ada", null))
}
```

```
Person(name=ada, address=Address(city=izmir))
true
Person(name=ada, address=null)
false
true
```

Üretilen `toString` iç nesneyi de açar: `Person(name=ada, address=Address(city=izmir))`. `equals` iç nesnenin `equals`'ine iner, bu yüzden aynı değerli iki ayrı `Person` eşit çıkar (`true`). `copy(address = null)` yalnızca belirtilen alanı değiştirir ve `null` bir alan olarak sorunsuz taşınır; `toString` bunu `address=null` olarak gösterir. Farklı şehirle kurulan kopya özgün nesneye eşit değildir (`false`), iki `null` adresli kişi ise eşittir (`true`).

Bu zincirleme davranış, iç nesne de bir veri sınıfı olduğu sürece geçerlidir. İç alan `equals` yazılmamış sıradan bir sınıfsa, dış nesnenin eşitliği o alanın kimliğine düşer: aynı değerli iki dış nesne yine eşit çıkmaz. Sık yapılan hata, yalnızca dış sınıfı veri sınıfı yapıp bunun tüm ağaca yeteceğini varsaymaktır. Eşitlik, ağacın her düğümünde ayrı ayrı sağlanır. Bu dersin ölçütü olan içerik, ancak içerideki alanlar da içerik ölçütüne sahipse en dışa kadar taşınır.

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

Üretilen `equals` alanları `==` ile karşılaştırır. O hâlde bir alanın tipi, alanın kendi `equals`'ine göre ölçütü etkilemelidir. Sınırlayıcı ölçüm aynı şekle sahip iki veri sınıfını karşılaştırır: `Packet` bir `IntArray` taşır, `Message` aynı sayıları bir `List<Int>` içinde taşır. İki satırda da ikiz aynı değerlerle ayrıca kurulur, üçüncü değerin yalnızca `id`'si farklıdır. Bloğun son satırı farklı bir şey sorar: iki `Packet` aynı `IntArray` örneğini paylaşırsa eşit midir?

```kotlin
// Main.kt — alan tipine gore
data class Packet(val id: Int, val bytes: IntArray)
data class Message(val id: Int, val bytes: List<Int>)

fun main() {
    val checks = listOf(
        Criterion("dizi", Packet(1, intArrayOf(1, 2)), Packet(1, intArrayOf(1, 2)), Packet(2, intArrayOf(1, 2))),
        Criterion("liste", Message(1, listOf(1, 2)), Message(1, listOf(1, 2)), Message(2, listOf(1, 2))),
    )
    for (check in checks) check.report()
    summarize(checks)
    val shared = intArrayOf(1, 2)
    println(Packet(1, shared) == Packet(1, shared))
}
```

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

Aynı şekle sahip iki sınıf iki ayrı ölçüt verir. `Packet`, içeriği aynı ama ayrı iki dizi ile kurulduğunda eşit çıkmaz: dizinin `equals`'i kimliğe bakar ve veri sınıfının ürettiği `equals` alanı bu `equals` ile karşılaştırır. `Message` ise listenin `equals`'i içeriğe baktığı için içerik verir. Son satır bunu doğrular: aynı dizi örneği paylaşılınca `Packet` de eşit çıkar. Üretilen yöntem alan tipinin eşitliğini devralır; ölçütü veri sınıfı değil, alanların tipi belirler.

**TN18.** Ölçüm yalnızca `IntArray` ve `List<Int>` içindir. Başka dizi ya da koleksiyon tiplerinin aynı kuralı izlediği ölçülmedi.

#### Özet

- `data class` birincil yapıcıdaki özelliklerden `equals`, `hashCode`, `toString`, `copy` ve `componentN` üretir.
- `copy` ayrı bir nesne kurar; o nesne üretilen `equals` ile ilk nesneye eşit sayılır.
- Yapısöküm bildirimi `component1`, `component2` çağrılarına karşılık gelir ve sırası yapıcı sırasıdır.
- Gövdedeki özellikler üretilen yöntemlere girmez; alan tipi eşitliği belirler, `IntArray` kimlik, `List<Int>` içerik verir.
- Bu dersin ölçütü: **içerik**. Sınırlayıcı ölçümde aynı şekle sahip iki veri sınıfı alan tipine göre içeriğe ve kimliğe düştü.

#### Sonraki Adım

Bir değerin birkaç kapalı biçimden yalnızca birine ait olduğunu anlatmak için sınıflar tek başına yetmez. Bir sonraki ders `enum class` ile numaralandırmaları ve `sealed interface` ile mühürlü hiyerarşileri ölçer: tümlü `when`'in eksik dalı nasıl reddettiğine ve numaralandırma girdisinin hangi ölçüte düştüğüne bakar.

### Mühürlü Tipler ve Numaralandırmalar

Source: https://academia.sh/tr/kurslar/kotlin/muhurlu-tipler-ve-numaralandirmalar

Numaralandırma girdisinin, data object nesnesinin, veri sınıfının ve sıradan sınıfın aynı mühürlü hiyerarşide hangi ölçüte düştüğünün ve tümlü when'in eksik dalı reddedişinin ölçümü.

Önceki ders veri sınıfının `equals`'i birincil yapıcıdaki özelliklerden ürettiğini ve ölçütün alan tiplerine bağlı olduğunu gösterdi. Şimdiye kadar ölçülen her sınıf, kaç örneği olursa olsun açık bir tipti: ayrı bir örnek her yerde kurulabilir. Bir değerin yalnızca önceden sayılmış birkaç biçimden biri olabildiği durumlar için Kotlin iki araç sunar: `enum class` ile numaralandırmalar ve `sealed` anahtar sözcüğüyle mühürlü sınıflar ve arayüzler. Bu ders ikisini ölçer ve her birinin ölçüte nasıl düştüğüne bakar.

Ölçü ekseni aynı kalır: 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. Bu ders, önceki üç dersin kesitlerini tek hiyerarşide bir araya getirir.

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

#### Numaralandırma

Numaralandırma, sınıfın bütün örneklerinin tanımda tek tek sayıldığı bir sınıftır. `enum class Level { LOW, MEDIUM, HIGH }` tam üç `Level` değeri tanımlar ve dışarıdan dördüncüsü kurulamaz. Her girdi sınıfın tek örneğidir. `entries` bütün girdileri tanım sırasıyla verir, `ordinal` sıra numarasını sıfırdan başlayarak döndürür, `valueOf` bir dizgiden girdiyi bulur ve bulamazsa istisna fırlatır.

Aşağıdaki blok bunları dener. `describe` fonksiyonu `when` ile her girdiye bir dizgi eşler ve `else` dalı yoktur: derleyici üç girdinin hepsinin karşılandığını bilir. Blok ayrıca `valueOf("MEDIUM")` ile bulunan girdinin kodda doğrudan yazılan `Level.MEDIUM` ile aynı nesne olup olmadığını `===` ile sorar. Tanımsız bir ad verilince fırlayan istisna yakalanır ve yalnızca sınıf adı basılır.

```kotlin
// Main.kt — numaralandirma
enum class Level { LOW, MEDIUM, HIGH }

fun describe(level: Level): String = when (level) {
    Level.LOW -> "dusuk"
    Level.MEDIUM -> "orta"
    Level.HIGH -> "yuksek"
}

fun main() {
    println(Level.entries)
    println(Level.HIGH.ordinal)
    println(Level.valueOf("MEDIUM"))
    println(Level.valueOf("MEDIUM") === Level.MEDIUM)
    println(describe(Level.LOW))
    try {
        Level.valueOf("EXTREME")
    } catch (e: IllegalArgumentException) {
        println(e::class.simpleName)
    }
}
```

```
[LOW, MEDIUM, HIGH]
2
MEDIUM
true
dusuk
IllegalArgumentException
```

`entries` üç girdiyi sırayla verir, `HIGH` ikinci sıfırdan sayılınca 2 numaralıdır ve `valueOf` ile bulunan girdi doğrudan yazılanla aynı nesnedir. Bu, ölçütün ilk sorusudur: girdi kendisinin ikizidir ve ölçüt tekildir. Tanımsız ad `IllegalArgumentException` fırlatır.

**TN19.** Ölçüm yalnızca üç girdili, alanı ve yöntemi olmayan bir numaralandırma içindir. Girdilerin kendi alanları ya da yöntemleri olduğunda, ya da numaralandırma bir arayüz uyguladığında davranışın aynı kalıp kalmadığı ölçülmedi.

#### Mühürlü Arayüz ve Tümlü When

`sealed interface Shape`, `Shape`'i uygulayabilecek tüm tiplerin aynı pakette ve aynı derlemede önceden bilindiği bir arayüzdür. Aşağıdaki hiyerarşi üç alt tipten oluşur: `data object Empty` tek bir örneği olan bir nesne bildirimidir, `data class Circle` bir veri sınıfıdır, `class Blob` ise `equals`'i yazılmamış sıradan bir sınıftır. Bu üç tip, önceki dersleri tek hiyerarşide toplar. Derleyici alt tiplerin tam listesini bildiği için `when (shape)` ifadesinin hepsini karşılayıp karşılamadığını denetleyebilir.

```kotlin
// Main.kt — kapali hiyerarsi
sealed interface Shape
data object Empty : Shape
data class Circle(val radius: Int) : Shape
class Blob(val size: Int) : Shape

fun describe(shape: Shape): String = when (shape) {
    Empty -> "bos"
    is Circle -> "daire " + shape.radius
    is Blob -> "leke " + shape.size
}

fun main() {
    val shapes = listOf(Empty, Circle(2), Blob(3))
    for (shape in shapes) println(describe(shape))
    println(Empty)
    println(Circle(2))
}
```

```
bos
daire 2
leke 3
Empty
Circle(radius=2)
```

Üç dal üç alt tipi karşılar ve `else` yazılmaz. `println(Empty)` yalnızca adı basar: `data object`, `toString` yöntemini nesnenin adıyla üretir. `println(Circle(2))` ise veri sınıfının alanlı biçimini basar. Sıradan bir `object` için varsayılan `toString` bir ad ve karma kodu verirdi; `data` değiştiricisi bunu okunur kılar.

Tümlü `when`'in asıl değeri başka yerdedir: hiyerarşiye bir alt tip eklenip dallardan biri unutulursa derleyici reddeder. Aşağıdaki parça `Blob` dalını bırakır. Parça, açılış dersinde olduğu gibi ölçüldü ve derlenmedi; derleyici eksik dalı adıyla gösteren bir hata verdi. Hata metni burada basılmaz.

```kotlin
sealed interface Shape
data object Empty : Shape
data class Circle(val radius: Int) : Shape
class Blob(val size: Int) : Shape

fun describe(shape: Shape): String = when (shape) {
    Empty -> "bos"
    is Circle -> "daire " + shape.radius
}

fun main() {
    println(describe(Empty))
}
```

**TN20.** Derleme reddi Kotlin 2.4.21'de ölçüldü. `when` bir ifade olarak kullanılmadığında, yalnızca deyim olduğunda, aynı denetimin uygulanıp uygulanmadığı bu derste ölçülmedi.

#### Alanlı Numaralandırma

Numaralandırma girdileri alan ve yöntem taşıyabilir. Girdiler parantez içinde yapıcı değerleri alır, girdi listesi `;` ile biter ve ardından üyeler gelir. Aşağıdaki blok boyut ve kod taşıyan bir numaralandırmayı dener: girdi üzerinde yöntem çağırır, `entries` üzerinde dönüştürme yapar, girdileri sıra numarasıyla kıyaslar ve olmayan bir kodu arar.

```kotlin
// Main.kt — alanli numaralandirma
enum class Planet(val code: String, val size: Int) {
    SMALL("s", 1),
    MEDIUM("m", 5),
    LARGE("l", 9);

    fun isBigger(other: Planet): Boolean = size > other.size
}

fun main() {
    println(Planet.LARGE.isBigger(Planet.SMALL))
    println(Planet.entries.map { it.code })
    println(Planet.entries.maxByOrNull { it.size })
    println(Planet.MEDIUM.name + Planet.MEDIUM.ordinal)
    println(Planet.SMALL < Planet.LARGE)
    println(Planet.entries.firstOrNull { it.code == "x" })
    println(Planet.entries.firstOrNull { it.code == "x" }?.name ?: "yok")
}
```

```
true
[s, m, l]
LARGE
MEDIUM1
true
null
yok
```

`LARGE.isBigger(SMALL)` `true` verir; yöntem, girdinin kendi `size` alanını kullanır. `entries` girdileri tanım sırasıyla verir, dolayısıyla kodların listesi `[s, m, l]` çıkar. `maxByOrNull` en büyük boyutlu girdiyi bulur ve `LARGE` basar. `name` ile `ordinal` birleşince `MEDIUM1` elde edilir: ikinci girdinin sırası `1`'dir. `SMALL < LARGE` `true` verir, çünkü girdiler sıra numarasına göre karşılaştırılabilir. Olmayan bir kod aranınca sonuç `null` olur; `?.name ?: "yok"` bunu `yok`'a çevirir.

Numaralandırma burada kapalı ve sabit bir değerler kümesidir ve her girdi tek bir örnektir. Alan eklemek, girdilerin tanımlandığı satırda değer vermek anlamına gelir; girdi listesi `;` ile bitirilmeden üye yazılamaz. Aramanın `null` dönmesi aynı zamanda `valueOf`'tan farkı gösterir: `valueOf` olmayan ada istisna fırlatır, `firstOrNull` ise yokluğu değer olarak taşır. Hangisinin seçileceği, yokluğun hata mı yoksa beklenen bir durum mu olduğuna bağlıdır.

#### Else Dalı Tümlülüğü Susturur

Tümlü `when`'in eksik dalı reddetmesi bir güvencedir; ama bu güvence `else` yazıldığı anda kaybolur. Aşağıdaki blok yukarıda reddedilen hiyerarşinin aynısını kullanır ve `Blob` dalı yerine bir `else` koyar. Derleme bu kez geçer. Bloğun gösterdiği, sonradan bir alt tip eklendiğinde ne olacağıdır: o alt tip sessizce `else` dalına düşer.

```kotlin
sealed interface Shape
data object Empty : Shape
data class Circle(val radius: Int) : Shape
class Blob(val size: Int) : Shape

fun describe(shape: Shape): String = when (shape) {
    Empty -> "bos"
    is Circle -> "daire"
    else -> "bilinmiyor"
}

fun main() {
    println(describe(Empty))
    println(describe(Circle(1)))
    println(describe(Blob(1)))
}
```

```
bos
daire
bilinmiyor
```

Çıktı `bos`, `daire`, `bilinmiyor` olur. Derleme geçti, çünkü `else` her kalan durumu karşılar. `Blob` örneği `bilinmiyor` dalına düştü; yukarıdaki tümlü sürümde bu durum derleme hatasıydı. Fark, `else` dalının derleyicinin eksik alt tip denetimini kapatmasıdır.

Sorun bugünün kodunda değil, yarının değişikliğinde ortaya çıkar. Hiyerarşiye sonradan eklenen bir alt tip, `else` dalı olan her `when` ifadesinde sessizce o dala düşer ve derleyici hiçbir uyarı vermez. Tümlü ifadede aynı ekleme, ilgili her `when` ifadesini derleme hatasıyla işaretlerdi. Sık yapılan hata, "her ihtimale karşı" diye `else` eklemektir. Mühürlü tipin sağladığı güvence tam da bu dalı yazmamaktan gelir. Gerçekten varsayılan bir davranış gerekiyorsa o davranış açık bir alt tip olarak modellenir, `else` bu işi sessizce üstlenmez.

#### Mühürlü Sınıf ve Akıllı Tip Dönüşümü

Mühürlü arayüzün yanı sıra `sealed class` da vardır; fark, sınıfın yapıcı durumu taşıyabilmesidir. Alt tipler genellikle sınıfın içine yazılır. Aşağıdaki `Outcome` üç durumu anlatır: değer taşıyan başarı, neden taşıyan hata ve durum taşımayan bekleme. Tümlü `when`, her dalda alt tipe akıllı tip dönüşümü yapar ve `outcome.value` gibi alanlara doğrudan erişir.

```kotlin
// Main.kt — muhurlu sinif ve akilli tip donusumu
sealed class Outcome {
    data class Success(val value: Int) : Outcome()
    data class Failure(val reason: String) : Outcome()
    data object Pending : Outcome()
}

fun render(outcome: Outcome): String = when (outcome) {
    is Outcome.Success -> "deger " + (outcome.value + 1)
    is Outcome.Failure -> "hata " + outcome.reason.length
    Outcome.Pending -> "bekliyor"
}

fun main() {
    val outcomes = listOf(Outcome.Success(1), Outcome.Failure("kotu"), Outcome.Pending)
    for (outcome in outcomes) println(render(outcome))
    println(Outcome.Pending === Outcome.Pending)
    println(outcomes.filterIsInstance<Outcome.Success>().size)
    println(outcomes.count { it is Outcome.Failure })
}
```

```
deger 2
hata 4
bekliyor
true
1
1
```

`sealed class` ile `sealed interface` aynı güvenceyi sağlar: alt tipler derleme anında bilinir. Fark, sınıfın yapıcı durumu ve üst sınıf davranışı taşıyabilmesidir. Çıktıda üç dal çalışır: `deger 2`, `hata 4` ve `bekliyor`. Her dalda derleyici, `outcome` değerini o dalın alt tipine daraltır; bu yüzden `outcome.value` ve `outcome.reason` doğrudan yazılabilir, ayrıca dönüşüm gerekmez.

Dördüncü satır `Pending === Pending` ifadesini sorar ve `true` döner: `data object` tek bir örnektir, bu yüzden kimlik ve içerik eşitliği örtüşür. Son iki satır koleksiyon fonksiyonlarıyla hiyerarşiyi süzer: `filterIsInstance` ve `is` ile sayım ikisi de `1` verir. İç içe yazım alt tiplerin adını `Outcome.Success` biçimine sokar. Bu, alt tiplerin hiyerarşiye ait olduğunu görünür kılar ve aynı ada sahip başka bir tipin karışmasını önler. Alt tipler aynı pakette olduğu sürece dışarıya da yazılabilir; ama iç içe yazım, tümlülüğün nereye bakıldığını okuyan kişiye gösterir.

#### Numaralandırma Koleksiyonlarda

Numaralandırma girdileri tekil olduğu için koleksiyonlarda anahtar olarak kullanılması sorunsuzdur. Girdiler `Comparable` olduğundan sıra numarasına göre sıralanır. Blok bir listede tekrarlanan girdileri sayar, sıralar ve ayırır, sonra iki girdinin sırasını `compareTo` ile sorar.

```kotlin
// Main.kt — numaralandirma koleksiyonlarda
enum class Level { LOW, MEDIUM, HIGH }

fun main() {
    val levels = listOf(Level.HIGH, Level.LOW, Level.HIGH, Level.MEDIUM)
    println(levels.groupingBy { it }.eachCount())
    println(levels.sorted())
    println(levels.distinct().size)
    println(Level.HIGH.compareTo(Level.LOW))
    println(Level.valueOf("LOW") == Level.LOW)
}
```

```
{HIGH=2, LOW=1, MEDIUM=1}
[LOW, MEDIUM, HIGH, HIGH]
3
2
true
```

`groupingBy { it }.eachCount()` girdileri sayar ve ilk görülme sırasını korur: `{HIGH=2, LOW=1, MEDIUM=1}`. Girdiler tekil olduğu için anahtar olarak güvenle kullanılır; iki `HIGH` aynı örneğe işaret eder ve tek anahtara toplanır. `sorted` girdileri sıra numarasına göre dizer, `[LOW, MEDIUM, HIGH, HIGH]` çıkar. `distinct` üç girdi bırakır. `HIGH.compareTo(LOW)` bu örnekte `2` verir, sıra numaraları farkıyla uyumlu bir değer. `valueOf("LOW") == LOW` ise `true`'dur, çünkü ad aynı örneği döndürür.

Buradaki mekanizma, numaralandırmanın hem tekil hem sıralı olmasıdır. Sıralama tanım sırasına bağlıdır, yani girdilerin dosyadaki konumu davranışın parçasıdır. Sık yapılan hata, girdileri sonradan araya yerleştirmek ve sıralamaya dayanan kodu bozmaktır: sıra numarası değişince `sorted` sonucu da, `compareTo` değeri de değişir. Sıralamanın önemli olduğu bir numaralandırmada eklenecek girdi sona eklenir, ya da sıra açık bir alanda tutulur ve karşılaştırma o alan üzerinden yapılır.

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

Üç alt tip aynı mühürlü arayüzün altındadır. Mühür, kimin alt tip olabileceğini kapatır; ama `equals`'i belirlemez. O hâlde aynı hiyerarşi aynı anda birden fazla ölçüt vermelidir. Sınırlayıcı ölçüm bunu dört satırla dener. İlk satır bir numaralandırma girdisini `valueOf` ile bulunan ikiziyle karşılaştırır. Sonraki üç satır `Shape` alt tiplerini sırayla dener: `Empty`, `Circle(1)` ve `Blob(1)`. Her alt tipte ikiz aynı değerle ayrıca kurulur, üçüncü değerin değeri farklıdır; `Empty` için üçüncü değer olarak `Circle(1)` verilir çünkü `Empty`'nin başka örneği yoktur.

```kotlin
// Main.kt — alt tipin turu
sealed interface Shape
data object Empty : Shape
data class Circle(val radius: Int) : Shape
class Blob(val size: Int) : Shape

enum class Level { LOW, MEDIUM, HIGH }

fun main() {
    val checks = listOf(
        Criterion("girdi", Level.HIGH, Level.valueOf("HIGH"), Level.LOW),
        Criterion("data-object", Empty, Empty, Circle(1)),
        Criterion("data-class", Circle(1), Circle(1), Circle(2)),
        Criterion("sinif", Blob(1), Blob(1), Blob(2)),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
girdi: ayni-nesne=true, ikiz=true, farkli=false -> tekil
data-object: ayni-nesne=true, ikiz=true, farkli=false -> tekil
data-class: ayni-nesne=false, ikiz=true, farkli=false -> icerik
sinif: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
tekil: 2
kimlik: 1
icerik: 1
kismi-icerik: 0
```

Aynı hiyerarşi üç ayrı ölçüt verir. Numaralandırma girdisi de `data object` de tek örnekli olduğu için tekildir: ikiz zaten aynı nesnedir. `Circle` veri sınıfı olduğu için içerik verir. `Blob`'un `equals`'i yazılmamıştır; aynı değerli iki örnek farklı nesnedir ve ölçüt kimliktir. Ölçütü mühür belirlemez; alt tipin hangi türde yazıldığı belirler. Tekil, kimlik ve içerik tek bir `when` ile işlenen tek bir arayüzün altında yan yana durabilir. Dağılım 2 tekil, 1 kimlik, 1 içerik, 0 kısmi içeriktir.

#### Özet

- Numaralandırma girdileri sınıfın tek örnekleridir: `valueOf` ile bulunan girdi doğrudan yazılanla aynı nesnedir ve ölçüt tekildir.
- `entries`, `ordinal` ve `valueOf` girdileri tanım sırasıyla listeler, sıralar ve ada göre bulur; tanımsız ad `IllegalArgumentException` fırlatır.
- Mühürlü arayüzde alt tiplerin listesi derleyici tarafından bilinir; tümlü `when` eksik bir dalı derleme sırasında reddeder.
- `data object` tekil, veri sınıfı alt tipi içerik, `equals`'i yazılmamış sıradan sınıf alt tipi kimlik verir.
- Bu dersin ölçütü: **tekil**. Sınırlayıcı ölçümde aynı mühürlü hiyerarşi üç ölçütü birden verdi; ölçütü hiyerarşi değil alt tipin türü belirler.

#### Sonraki Adım

`data object Empty`, tek örnekli bir nesneyi bildirimle kurdu. Kotlin'de bu kalıbın genel biçimi `object` bildirimidir: sınıfı ve tek örneğini birlikte tanımlar. Bir sonraki ders nesne bildirimlerini, bir sınıfın içindeki yardımcı nesneyi ve ad vermeden kurulan nesne ifadesini ölçer ve üçünün ölçüte nasıl düştüğüne bakar.

### Nesne Bildirimleri

Source: https://academia.sh/tr/kurslar/kotlin/nesne-bildirimleri

Nesne bildirimi, yardımcı nesne, `data object` ve nesne ifadesini iki değeri eşit yapan ölçütle ölçer; bildirimin tekil, ifadenin her değerlendirmede yeni nesne olduğunu gösterir.

Önceki derste, "Mühürlü Tipler ve Numaralandırmalar" dersinde, numaralandırma girdisi ve `data object` değerlerinin tekil olduğunu gördük: aynı girdiyi iki kez okuyunca aynı nesne geliyordu. Bu ders o tekilliğin kaynağına iniyor. `object` sözcüğü Kotlin'de üç ayrı yerde karşınıza çıkar: bir bildirimde, bir sınıfın içinde yardımcı nesne olarak ve bir ifadenin başında. Üçü de aynı sözcükle yazılır; bu ders üçünün aynı şeyi yapıp yapmadığını ölçüyor.

Ölçü, bu kursun her dersinde aynı soru: bu iki değeri eşit yapan ne? Dört olası cevap var: aynı nesne olmaları (tekil), eşitliğin kimliğe dayanması (kimlik), içeriklerinin eşit olması (içerik) ve yalnız bazı içeriklerin eşit olması (kısmi içerik).

**Nesne bildirimi (object declaration)**, bir tipi tanımlar ve o tipin tek örneğini aynı anda kurar. Bildirimin adı hem tipin hem de o tek değerin adıdır. Yapıcı çağrılmaz; adı yazmak değerin kendisini okumaktır.

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

`Criterion` her derste aynı biçimde ilk blokta durur. Her gözlem üç değer alı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: `first === twin` ise `tekil`; değilse ve `first == twin` yanlışsa `kimlik`; `first == other` yanlışsa `icerik`; hiçbiri değilse `kismi-icerik`.

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

**TN21.** Bir nesne bildiriminde `twin` ayrıca kurulamaz, çünkü ikinci bir örnek yoktur; bu derste `twin` olarak aynı bildirimin ikinci kez okunan değerini verdik. `other` olarak aynı gövdeli ama ayrı adlı bir bildirim kullandık.

#### Bildirim Tek Nesnedir

Aşağıdaki blok iki nesne bildirimi yazar. `IdSource` bir sayaç tutar; `OtherSource` aynı gövdeye sahip ayrı bir bildirimdir. `IdSource` iki ayrı değişkene okunur, sayaç da bu iki değişken ve doğrudan adı üzerinden ilerletilir.

```kotlin
// Main.kt — nesne bildirimi iki kez okunur
object IdSource {
    private var next = 1
    fun take(): Int = next++
}

object OtherSource {
    private var next = 1
    fun take(): Int = next++
}

fun main() {
    val a = IdSource
    val b = IdSource
    println(a.take())
    println(b.take())
    println(IdSource.take())
    Criterion("nesne-bildirimi", a, b, OtherSource).report()
}
```

```
1
2
3
nesne-bildirimi: ayni-nesne=true, ikiz=true, farkli=false -> tekil
```

Üç `take()` çağrısı, hangi değişken üzerinden yapılırsa yapılsın aynı sayacı ilerletti, çünkü `a` ve `b` aynı nesneyi gösteriyor. Kural da bunu `tekil` olarak yazdı: `===` doğru çıktığı için ikizin eşitliğine bakılmadı bile. `OtherSource`'un sayacı ayrı olduğu için o, ilk nesneye eşit değil.

Bildirim bir yapıcı çağrısı olmadığından, adın sonuna parantez koymak da çalışmaz. Ö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
object IdSource

fun main() {
    val source = IdSource()
}
```

**TN22.** Reddin hata metnini derse yazmıyoruz; yalnız parçanın derlenmediği doğrulandı. Bildirimin başka bir sınıftan kalıtım alıp alamayacağı ya da bir arayüz gerçekleştirip gerçekleştirmeyeceği bu derste ölçülmedi.

#### Yardımcı Nesne ve Fabrika

**Yardımcı nesne (companion object)**, bir sınıfın içinde `companion object` diye yazılan nesne bildirimidir. Sınıf adıyla çağrılabilen üyeleri tutar. Bildirim olduğu için tekildir. Ama içine yazılan bir fabrika fonksiyonu, sınıfın örneklerini kurar ve o örnekler tekil değildir. İkisini yan yana ölçüyoruz.

```kotlin
// Main.kt — yardimci nesne tekildir, fabrika her seferinde yeni nesne uretir
class Account private constructor(val owner: String) {
    companion object {
        fun open(owner: String): Account = Account(owner)
    }
}

fun main() {
    val checks = listOf(
        Criterion("yardimci-nesne", Account.Companion, Account.Companion, Any()),
        Criterion("fabrika-sonucu", Account.open("ada"), Account.open("ada"), Account.open("can")),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
yardimci-nesne: ayni-nesne=true, ikiz=true, farkli=false -> tekil
fabrika-sonucu: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
tekil: 1
kimlik: 1
icerik: 0
kismi-icerik: 0
```

`yardimci-nesne` satırı `tekil`, `fabrika-sonucu` satırı `kimlik` çıktı. Aynı sınıfın içinde yazılan iki `object` karşılığı, iki ayrı ölçüt verdi: yardımcı nesnenin kendisi tek, onun ürettiği hesaplar her çağrıda ayrı. İki `Account`'un sahibi aynı olsa bile `==` doğru dönmedi, çünkü `Account` sıradan bir sınıftır ve `equals`'ü yeniden tanımlamaz; eşitlik kimliğe düşer. Bu, kural dilinde `kimlik` demektir. Yapıcıyı özel yapmak (`private constructor`) örnek kurma yolunu yardımcı nesnenin fonksiyonuna daralttı; fabrika böylece tek giriş kapısı oldu.

**TN23.** Fabrikanın her çağrıda yeni bir `Account` kurduğu burada yalnız bu sınıf için ölçüldü. Bir fabrikanın önceden kurulmuş bir örneği döndürmesi de mümkündür; o durumda ölçüt `tekil` olurdu, ama bu derste öyle bir fabrika yazmadık.

#### data object

`data object`, bir nesne bildirimine veri sınıfının okunabilir `toString`'ini ve `equals`/`hashCode`'unu ekler. Düz bir `object`'in varsayılan `toString` çıktısını bu kursta basmıyoruz; `data object` ise yalnız adını yazar. Mühürlü hiyerarşilerde durum nesnelerine `data object` yazılmasının nedeni budur: çıktıda okunur bir ad görünür.

```kotlin
// Main.kt — data object okunabilir bir toString tasir
data object Accepted

fun main() {
    println(Accepted)
    println(Accepted == Accepted)
    val checks = listOf(Criterion("data-object", Accepted, Accepted, "Accepted"))
    for (check in checks) check.report()
}
```

```
Accepted
true
data-object: ayni-nesne=true, ikiz=true, farkli=false -> tekil
```

Çıktının ilk satırı bildirimin adından başka bir şey içermiyor. Ölçüt yine `tekil`: `data object` yeni bir örnek üretmez, yalnız mevcut tekil nesneye okunabilirlik ekler. `other` olarak verdiğimiz `"Accepted"` dizgisi ise `Accepted`'e eşit değil; ikisinin yazılışı aynı olsa da tipleri ayrı.

#### İlk Erişimde Kurulum ve Arayüz

Bir nesne bildiriminin tek örneği ne zaman kurulur? Program başlarken mi, yoksa ilk kullanıldığı anda mı? Bu sorunun cevabı, bildirimin gövdesindeki `init` bloğunda görünür: blok her ne zaman koşarsa örnek o zaman kurulmuştur. Bildirim ayrıca bir arayüz gerçekleştirebilir, yani sıradan bir sınıf gibi davranışı sözleşmeye bağlayabilir. Son olarak bildirim, `when` ifadesinde bir sabit gibi eşleştirilebilir.

Aşağıdaki blokta `Polite`, `Greeter` arayüzünü gerçekleştiren ve kurulduğunda bir satır basan bir nesne bildirimidir. `Idle` ise bir `data object`'tir. `describe` fonksiyonu bir değeri üç yoldan eşleştirir: `Idle` ile eşitlik, `is Polite` ile tip denetimi ve geri kalan her şey. `main` ilk satırda `basladi` yazdırır; böylece `init` satırının nerede göründüğü, bildirimin hangi anda kurulduğunu gösterir.

```kotlin
// Main.kt — nesne ilk erisimde kurulur, arayuz gerceklestirir, when ile eslesir
interface Greeter {
    fun greet(name: String): String
}

object Polite : Greeter {
    init {
        println("polite kuruldu")
    }

    override fun greet(name: String): String = "merhaba " + name
}

data object Idle

fun describe(state: Any): String = when (state) {
    Idle -> "bos"
    is Polite -> "nazik"
    else -> "baska"
}

fun main() {
    println("basladi")
    val greeter: Greeter = Polite
    println(greeter.greet("ada"))
    println(Polite.greet("can"))
    println(describe(Idle))
    println(describe(Polite))
    println(describe(Any()))
    Criterion("arayuz-nesnesi", greeter, Polite, Idle).report()
}
```

```
basladi
polite kuruldu
merhaba ada
merhaba can
bos
nazik
baska
arayuz-nesnesi: ayni-nesne=true, ikiz=true, farkli=false -> tekil
```

`polite kuruldu` satırı `basladi`'dan sonra ve ilk `greet` çağrısından önce göründü. `Polite` programın başında kurulmadı: bildirim, adına ilk erişildiği anda kuruldu ve `init` bloğu o sırada koştu. İkinci erişim yeni bir satır basmadı, çünkü örnek zaten vardı. `greeter` ve `Polite` aynı nesneyi gösterdi, ölçüt `tekil` çıktı; `greeter` değişkeninin tipi `Greeter` olsa da altındaki nesne değişmedi.

`describe` üç ayrı sonuç verdi. `Idle` için `bos`, `Polite` için `nazik`, sıradan bir `Any()` için `baska` yazıldı. Bir nesne bildirimi tekil olduğu için `when` içinde sabit gibi eşleşir: `Idle ->` satırı `Idle == state` karşılaştırmasına iner. `is Polite` da çalıştı, çünkü bildirim hem bir değer hem bir tip tanımlar; tek örneği olan bir tip için ikisi aynı sonucu verir.

Bu ölçümden çıkan pratik sonuç iki yanlıdır. Birincisi, `init` bloğunda yan etki bırakmak, yani bir satır basmak ya da bir kaynağı açmak, bildirimin kurulma anını programın akışına bağlar; hangi satırın önce koşacağı ilk erişimin yerine bağlıdır. İkincisi, bir nesne bildirimi arayüz gerçekleştirdiğinde, o arayüz tipli her değişken yine aynı tek örneği taşır; bu yüzden ölçüt tipe göre farklılaşmaz, nesnenin kendisine bağlı kalır.

**TN24.** Nesne bildirimine ilk erişimin birden çok iş parçacığından aynı anda yapılması bu derste ölçülmedi; yalnız tek akışta kurulum sırası gözlendi.



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

Bu noktaya kadar ölçtüğümüz satırlar ve her satır aynı soruya cevap verdi: iki okuma aynı nesneyi mi veriyor? Satırları yan yana koyunca bir okuma kuralı çıkıyor. Bildirim, adı yazıldığında bir değer üretir; `Stamp`, `Polite`, `Accepted` ve `Idle` bu yüzden `tekil` çıktı. İfade ise her değerlendirildiğinde yeni bir nesne kurar; `makeMarker()` bu yüzden `kimlik` çıktı. Sınıfın fabrikası bir fonksiyondur, bildirim değildir; `Account.open` her çağrıda yeni bir `Account` kurdu ve sonuç yine `kimlik` oldu. Kural, sözcüğün yazıldığı yere bakar: ad tek bir değere gidiyorsa tekil, çağrı ya da ifade yeni değer kuruyorsa kimlik.

Sık yapılan hata bu kuralı tersine çevirmektir. Bir `object` sözcüğü gören okuyucu "bu tekildir" der; oysa `object : Tip { ... }` biçimi bir ifadedir. Tersi de yanlıştır: bir sınıf içinde `companion object` yazıldığında yardımcı nesnenin ürettikleri tekil sanılır. Ölçüm, yardımcı nesnenin kendisinin tek, onun fabrikasının ürettiklerinin ayrı olduğunu gösterdi. Bu yüzden bir tekil nesneye bağlanan durum, örneğin bir sayaç ya da bir önbellek, programın bütün parçalarınca paylaşılır; bir fabrikanın ürettiği nesneye bağlanan durum ise yalnız o nesnenin sahibine aittir.

Kurulum anı ikinci bir ayrıntıdır. `polite kuruldu` satırı ilk erişimde göründü ve ikinci erişimde görünmedi. Bir tekil nesnenin kuruluş maliyeti ya da yan etkisi, programın başında değil, ilk kullanıldığı yerde ödenir. Bu, `init` bloğuna yazdığınız şeyin ne zaman koşacağını sıralamak gerektiğinde işe yarar: sıralama, hangi koddan ilk erişildiğine bağlıdır ve o kod yer değiştirirse satır da yer değiştirir. Bu yüzden `init` bloğunu kısa tutmak, yalnız alan kurmak ve çıktı üretmemek akıllıca olur; bu derste yalnız ölçümü görünür kılmak için çıktı bastırdık.

Üçüncü ayrıntı `when` ifadesidir. `Idle` ile eşleştirme ve `is Polite` ile tip denetimi aynı işi yapar, çünkü bildirimin tek örneği vardır. Tek örneği olan bir tipte "bu değer o değere eşit mi" ile "bu değer o tipte mi" soruları aynı cevabı verir. İki örnek mümkün olan bir sınıfta bu iki soru ayrılırdı; bu ayrılmanın olmaması tekilliğin gözlenebilir bir sonucudur.

#### Ölçmeden Önce Tahmin Etmek

Bir satırı koşturmadan önce ölçütü tahmin etmek, kuralı gerçekten anlayıp anlamadığınızı sınar. `Criterion` düzeneği üç soru sorar: aynı nesne verildiğinde `===` doğru mu, ikiz verildiğinde `==` doğru mu, farklı nesne verildiğinde `==` doğru mu? Nesne bildiriminde ilk soru her zaman doğru cevaplanır, çünkü ad tek bir değere gider; bu yüzden ikizin de aynı nesne olması gerekir ve üçüncü soru yalnızca farklı bir tip verildiğinde yanlış çıkar. Ölçüt `tekil` olarak yazılır.

Nesne ifadesinde ilk soru bile yanlış cevaplanır. Aynı fonksiyonu iki kez çağırmak iki ayrı nesne verir ve ikizler birbirine eşit sayılmaz; ikisi de `false` olunca ölçüt `kimlik` olur. Bu dersteki her satırı bu iki yoldan birine koyabilirsiniz: ad bir bildirimi gösteriyorsa `tekil`, bir çağrıyı ya da ifadeyi gösteriyorsa `kimlik`. `data object` satırı bile bu kuralın dışına çıkmadı; `data` önekinin eklediği şey eşitlik değil, okunabilir bir yazı oldu.

Tahminin tutmadığı yer, bir kuralı eksik hatırladığınız yerdir. Örneğin `describe` fonksiyonunda `Idle` için `bos` yazacağını bildiyseniz ama `Polite` için `nazik` yerine `baska` bekliyorsanız, `is` denetiminin bildirim adıyla çalışmadığını düşünmüşsünüz demektir. Ölçüm bu varsayımı düzeltti: bildirim hem bir değer hem bir tiptir.

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

**Nesne ifadesi (object expression)**, `object` sözcüğünün bir ifadenin başında kullanılmasıdır: `object { ... }` ya da bir tip adından sonra `object : Tip { ... }`. Ad taşımaz ve ifade her değerlendirildiğinde yeni bir nesne kurar. Bildirimle ifade arasındaki fark tam burada: aynı sözcük, bildirimde tekil bir nesne, ifadede her seferinde yeni bir nesne üretir. Bu yüzden bu kursun ölçüsü onları ayırır.

```kotlin
// Main.kt — nesne ifadesi her degerlendirmede yeni nesne kurar
object Stamp

fun makeMarker(): Any = object {
    val side = 4
}

fun main() {
    val checks = listOf(
        Criterion("bildirim", Stamp, Stamp, makeMarker()),
        Criterion("ifade", makeMarker(), makeMarker(), makeMarker()),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

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

`bildirim` satırı `tekil`, `ifade` satırı `kimlik` çıktı. İfadenin bir adı, dolayısıyla bir yapıcısı olmadığı hâlde, her `makeMarker()` çağrısı yeni bir nesne kurdu ve bu nesneler `equals`'ü yeniden tanımlamadığı için birbirine eşit çıkmadı. Bu ölçüm bir sınırdır: `object` sözcüğünü gördüğünüzde tekil olduğunu varsaymak yetmez; sözcüğün bildirimde mi yoksa ifadede mi durduğuna bakmak gerekir.

**TN25.** Nesne ifadesinin yakaladığı bir değişkenin nasıl göründüğü ve ifadenin bir tipi genişletmesi bu derste ölçülmedi; yalnız adsız, tipsiz bir ifade kullanıldı.

#### Özet

- Nesne bildirimi bir tipi ve o tipin tek örneğini birlikte kurar; adı yazmak değeri okumaktır ve iki okuma aynı nesneyi verir.
- Yardımcı nesne bir sınıfın içindeki nesne bildirimidir; tekildir, ama onun fabrikası sınıfın örneklerini her çağrıda ayrı kurabilir.
- `data object`, tekil nesneye yalnız okunabilir bir `toString` ve eşitlik ekler; ölçütü değiştirmez.
- Nesne ifadesi her değerlendirmede yeni bir nesne kurar; aynı `object` sözcüğü bildirimde tekil, ifadede kimliktir.
- Bu dersin ölçütü: **tekil**. İki okuma aynı nesneyi verdiğinde `===` doğru çıkar ve eşitliğin gerekçesi aranmaz.

#### Sonraki Adım

Bu derste bir nesnenin tek mi, yoksa her seferinde yeni mi kurulduğuna baktık ve davranışı yalnız sınıfın kendi gövdesinde tuttuk. Sıradaki ders davranışı bir tipten ayırıyor: arayüzler, bir sözleşmeyi yazar ve bir sınıf onu başka bir nesneye devredebilir. Devretmenin eşitliği de devredip devretmediği ölçülecek.

### Arayüzler ve Devretme

Source: https://academia.sh/tr/kurslar/kotlin/arayuzler-ve-devretme

Arayüzü, varsayılan gerçekleştirimi ve `by` ile devretmeyi iki değeri eşit yapan ölçütle ölçer; devretmenin arayüzün üyelerini ilettiğini, `equals`'ü iletmediğini gösterir.

Önceki derste bir nesnenin tekil mi yoksa her seferinde yeni mi kurulduğuna baktık. Bu derste nesnenin davranışını sınıfın dışına çıkarıyoruz: arayüz bir sözleşmeyi yazar, bir sınıf o sözleşmeyi ya kendisi gerçekleştirir ya da başka bir nesneye devreder. Soru devretmeye geliyor: bir nesne davranışını başkasına bıraktığında, iki devreden nesneyi eşit yapan ne?

**Arayüz (interface)**, bir tipin uyması gereken üyeleri tanımlar. Kotlin'de bir arayüz yalnız imza değil, gövdeli fonksiyon ve gövdeli okunur özellik de taşıyabilir; bunlara varsayılan gerçekleştirim denir. Bir sınıf arayüzü gerçekleştirdiğinde gövdesiz üyeleri yazmak zorundadır, gövdelileri isterse yeniden tanımlar (override).

Bu derste ölçüt eksenini bir kez daha hatırlatalım. Java müfredatındaki, kararı hangi tipin verdiğini soran ölçü (M08/K02) burada yok: biz tipe değil, iki değerin eşit sayılmasının nedenine bakıyoruz. Aynı nesne olmaları tekil, eşitliğin kimliğe düşmesi kimlik, içeriklerin eşit olması içerik, yalnız bir kısmının eşit olması kısmi içerik.

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

Dersin kendi modeli ikinci bir yardımcı dosyada durur. `Printer` arayüzü bir okunur özellik ve bir varsayılan fonksiyon taşır; iki sınıf onu farklı biçimde gerçekleştirir.

```kotlin
// Printers.kt — arayuzde ozellik ve varsayilan gerceklestirim
interface Printer {
    val prefix: String
    val width: Int
        get() = prefix.length

    fun render(text: String): String = prefix + text
}

class PlainPrinter : Printer {
    override val prefix: String = "> "
}

class LoudPrinter : Printer {
    override val prefix: String = "# "
    override fun render(text: String): String = prefix + text.uppercase()
}
```

**TN26.** `Printer`'ın yalnız bir gövdesiz özelliği ve bir gövdeli okunur özelliği var; arayüzde saklanan (alan taşıyan) bir özellik yazılamaz ve bu burada ölçülmedi. Aşağıdaki bloklar iki `Printer` gerçekleştirimini ve `by` ile yazılan sarmalayıcıları bu yardımcıyla birlikte derler.

#### Arayüz Üyesi Gövde Taşıyabilir

Aşağıdaki blok iki sınıfı yan yana kullanır. `PlainPrinter` yalnız gövdesiz özelliği yazdı; `render` ile `width` arayüzdeki gövdeleri aldı. `LoudPrinter` ise `render`'ı yeniden tanımladı. Son satır iki `PlainPrinter`'ı ölçer.

```kotlin
// Main.kt — arayuz varsayilani ve yeniden tanimlama
fun main() {
    val plain = PlainPrinter()
    val loud = LoudPrinter()
    println(plain.render("ada"))
    println(loud.render("ada"))
    println(plain.width)
    val printers: List<Printer> = listOf(plain, loud)
    println(printers.map { it.render("x") })
    Criterion("iki-varsayilan", PlainPrinter(), PlainPrinter(), loud).report()
}
```

```
> ada
# ADA
2
[> x, # X]
iki-varsayilan: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
```

`plain` arayüzün `render` gövdesini kullandı, `loud` kendi gövdesini; `printers` listesinden ikisi de aynı `Printer` adıyla çağrıldı ve her biri kendi sonucunu verdi. `width` yalnız arayüzde tanımlıydı ve `prefix`'in uzunluğunu verdi. Son satırda iki ayrı `PlainPrinter` `kimlik` çıktı: sınıf `equals`'ü yeniden tanımlamadığı için eşitlik kimliğe düştü ve iki örnek aynı arayüz gövdesini kullansa da eşit olmadı. Varsayılan gerçekleştirim davranışı paylaştırır, eşitliği paylaştırmaz.

#### Çakışan Varsayılanlar

Bir sınıf iki arayüzü gerçekleştirir ve ikisi de aynı imzalı gövdeli fonksiyon taşırsa, derleyici hangisinin seçileceğini bilemez. Sınıfın fonksiyonu kendisinin yeniden tanımlaması gerekir ve gövde içinde `super<Arayüz>` ile her birine ayrı ayrı ulaşılabilir.

```kotlin
// Main.kt — iki arayuzun ayni varsayilani tek sinifta birlesir
interface Reader {
    fun describe(): String = "okur"
}

interface Writer {
    fun describe(): String = "yazar"
}

class Both : Reader, Writer {
    override fun describe(): String = super<Reader>.describe() + "+" + super<Writer>.describe()
}

fun main() {
    println(Both().describe())
    println(Both().let { it as Reader }.describe())
}
```

```
okur+yazar
okur+yazar
```

`Both` iki kaynağı birleştirdi. İkinci satır önemli: `Both` nesnesi `Reader` olarak görüldüğünde de aynı birleşik gövde koştu, çünkü seçimi nesnenin kendi yeniden tanımladığı fonksiyon yaptı, değişkenin tipi değil.

**TN27.** Bu blokta çakışmayı çözmeyen bir sınıfın derlenmediğini ayrıca doğrulamadık; yalnız çözülmüş hâli ölçüldü.

#### by ile Devretme

**Devretme (delegation)**, bir arayüzün gerçekleştirimini başka bir nesneye bırakmaktır. Kotlin'de bu `: Printer by inner` biçiminde yazılır: derleyici arayüzün her üyesi için, çağrıyı `inner` üzerine ileten bir fonksiyon üretir. İleten nesneye devreden, çağrının gittiği nesneye **temsilci (delegate)** denir.

```kotlin
// Main.kt — by ile devretme arayuzun uyelerini iletir
class Labeled(private val inner: Printer) : Printer by inner

class Quiet(private val inner: Printer) : Printer by inner {
    override fun render(text: String): String = inner.render(text).lowercase()
}

fun main() {
    val loud = LoudPrinter()
    val labeled = Labeled(loud)
    println(labeled.render("ada"))
    println(labeled.prefix)
    println(labeled.width)
    println(Quiet(loud).render("ADA"))
    val checks = listOf(
        Criterion("ayni-temsilci", Labeled(loud), Labeled(loud), Labeled(PlainPrinter())),
        Criterion("yeniden-tanimli", Quiet(loud), Quiet(loud), Quiet(PlainPrinter())),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
# ADA
#
2
# ada
ayni-temsilci: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
yeniden-tanimli: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
tekil: 0
kimlik: 2
icerik: 0
kismi-icerik: 0
```

`Labeled` hiçbir üye yazmadı; `render`, `prefix`, `width` yine de çalıştı: üçü de `loud`'a gitti. `render` sonucunun büyük harfli olması, çağrının `Printer`'ın varsayılan gövdesine değil `LoudPrinter`'ın yeniden tanımladığı gövdeye gittiğini gösteriyor. `Quiet` ise bir üyeyi kendisi yeniden tanımladı ve geri kalanını devretti.

Aynı temsilciyi saran iki `Labeled` ve aynı temsilciyi saran iki `Quiet` ikisi de `kimlik` çıktı. İki devreden nesne aynı `loud`'a bağlı olsa da eşit sayılmadı.

**TN28.** `by`'dan sonra gelen ifadenin bir kurucu parametresi olması şart değildir; burada yalnız parametreyi gösteren biçimi ölçtük. Devretmenin, arayüzde olmayan bir üyeyi de iletip iletmediği ölçülmedi.

#### Fonksiyonel Arayüz, Nesne İfadesi ve Devir İfadesi

Arayüz yalnız sınıflarla gerçekleştirilmez. Tek bir soyut fonksiyonu olan bir arayüz, `fun interface` ile işaretlenirse bir lambda ile kurulabilir. Bir arayüz ayrıca adsız bir nesne ifadesiyle de gerçekleştirilebilir. Üçüncü olarak, `by`'dan sonra gelen ifade bir kurucu parametresi olmak zorunda değildir; herhangi bir ifade olabilir. Bu üçünü tek bir blokta ölçüyoruz.

`Shout` iki lambda ile kurulur: ikisinin gövdesi aynıdır. `anon` bir nesne ifadesiyle `Printer` gerçekleştirir. `Cached` sınıfı ise temsilciyi `makePrinter()` çağrısıyla kurar ve `made` sayacı, bu çağrının kaç kez koştuğunu sayar.

```kotlin
// Main.kt — fun interface, nesne ifadesi ve devir ifadesinin bir kez degerlendirilmesi
fun interface Shout {
    fun apply(text: String): String
}

var made = 0

fun makePrinter(): Printer {
    made += 1
    return PlainPrinter()
}

class Cached : Printer by makePrinter()

fun main() {
    val first = Shout { it.uppercase() }
    val second = Shout { it.uppercase() }
    println(first.apply("ada"))
    val anon = object : Printer {
        override val prefix: String = "~ "
    }
    println(anon.render("x"))
    val one = Cached()
    println(one.render("a"))
    println(one.render("b"))
    println(made)
    Cached()
    println(made)
    val checks = listOf(
        Criterion("fonksiyonel-arayuz", first, second, Shout { it }),
        Criterion("nesne-ifadesi", anon, anon, PlainPrinter()),
    )
    for (check in checks) check.report()
}
```

```
ADA
~ x
> a
> b
1
2
fonksiyonel-arayuz: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
nesne-ifadesi: ayni-nesne=true, ikiz=true, farkli=false -> tekil
```

Bloğun ilk sonucu, gövdesi aynı olan iki lambdanın eşit sayılmadığıdır: `fonksiyonel-arayuz` satırı `kimlik` çıktı. Gövdelerin metin olarak aynı olması eşitlik üretmedi; her lambda ifadesi kendi nesnesini kurdu ve fonksiyonel arayüz `equals`'ü yeniden tanımlamıyor. Aynı arayüzü sınıfla gerçekleştirmek ile lambda ile gerçekleştirmek eşitlik bakımından ayrılmaz.

İkinci sonuç nesne ifadesi içindir. `nesne-ifadesi` satırı `tekil` çıktı, çünkü `anon` tek bir değişkendir ve aynı değer iki kez ölçüye verildi. Önceki derste gördüğümüz ayrım burada da geçerli: ifade her değerlendirildiğinde yeni bir nesne kurar, ama bir kez kurulup bir değişkene konan nesne tek kalır. `anon.render("x")` çıktısındaki `~ ` öneki, arayüzdeki varsayılan `render` gövdesinin nesne ifadesinde de kullanıldığını gösterdi.

Üçüncü sonuç `by` ifadesi içindir. `made` ilk `Cached()` çağrısından sonra `1`, ikinciden sonra `2` yazdı. Temsilci ifadesi her `Cached` örneği kurulurken bir kez değerlendirilir: iki örnek, iki ayrı `PlainPrinter` taşır. Aynı örneğin iki `render` çağrısı ise temsilciyi yeniden kurmadı; `made` yalnız kurucuda arttı. Bu, devretmenin bir yöntem çağrısı değil, kurucuda saklanan bir alan olduğunu söyler: temsilci örnek başına bir kez kurulur ve sonra iletilir.

**TN29.** Bir lambdanın dış değişkeni yakalayıp yakalamamasının eşitliği değiştirip değiştirmediği ölçülmedi; iki lambda da hiçbir şey yakalamadı.



#### Ölçümlerden Çıkan Eşitlik Kuralı

Bu derste ölçtüğümüz her satır, bir davranışın nereden geldiğini sorduktan sonra eşitliğin nereden geleceğini sordu. Arayüz davranış paylaşır ama durum taşımaz ve `equals` yazmaz; `by` davranışı bir temsilciye iletir ama `equals`'ü iletmez; `fun interface` bir lambdayı arayüz değerine çevirir ama iki özdeş lambdayı eşit saymaz. Üç düzeneğin ortak sonucu, eşitliğin davranışın paylaşılmasıyla değil, sınıfın kendi `equals` tanımıyla belirlendiğidir.

Sık yapılan hata, devretmeyi kalıtım gibi okumaktır. Kalıtımda alt sınıf üst sınıfın `equals`'ünü miras alır; `by` ile devredilen bir sınıf ise arayüzün üyelerini iletir, ama `equals` arayüzde tanımlı bir üye olmadığı için iletilmez. Bu yüzden aynı temsilciyi saran iki `Labeled` ya da iki `Quiet` örneği, davranışları birebir aynı olsa da eşit çıkmadı. Eşitlik gerekiyorsa sınıf bunu kendisi yazar ya da `data class` olarak bildirir; devretme bu işi yapmaz.

Devir ifadesinin örnek başına bir kez değerlendirilmesi ikinci bir pratik sonuç verir. `made` sayacı iki örnek için `2` yazdı; yani temsilci bir alandır, örneğin ömrü boyunca aynı kalır. Bir temsilcinin içinde durum tutuluyorsa, örneğin bir sayaç ya da bir önbellek, o durum devreden örneğe özgüdür ve iki örnek arasında paylaşılmaz.

Lambda ile kurulan arayüz değerlerinde de aynı ders geçerlidir: iki gövde aynı metni taşısa bile iki ayrı değerdir ve sonuç `kimlik` çıkar. Eşitliği davranıştan beklemek, bu derste ölçtüğümüz her düzenekte yanlış çıktı. Davranış ile eşitliği ayrı düşünmek, bir koleksiyonda ya da bir `==` karşılaştırmasında beklenmedik bir sonuçla karşılaşmamayı sağlar.

#### Ölçmeden Önce Tahmin Etmek

Bu dersin ölçütü `kimlik` olduğu için tahmin kolaydır, ama hangi satırın neden `kimlik` verdiğini söylemek daha öğreticidir. Sınıf ile gerçekleştirilen bir arayüzde eşitlik sınıfın `equals`'üne bağlıdır; sınıf onu yazmadıysa `Object`'in kimlik karşılaştırması geçerlidir. `by` bu tabloya bir şey eklemez, çünkü iletilen üyeler arayüzde tanımlı olanlardır. Veri sınıfı olarak bildirilen bir sınıfta `equals` derleyici tarafından üretilir ve içeriğe bakar; bu ders onu bir karşılaştırma olarak gösterdi.

Fonksiyonel arayüz satırında tahmin tuzağı vardır. İki lambda aynı metni taşıyor ve aynı arayüze çevriliyor; "aynı iş yapıyorlar, demek ki eşitler" diye düşünmek doğal görünür. Ölçüm `kimlik` verdi. Nesne ifadesi satırında ise aynı değişken iki kez verildi ve sonuç `tekil` oldu; ifade değerlendirilirken yeni nesne kurulur, ama bir değişkene konan nesne bundan sonra tek kalır. Bu iki satırın farkı, "iki ifade" ile "bir ifade, iki okuma" arasındaki farktır.

Devir ifadesinde tahmin edilecek şey bir sayıdır. İki `Cached` kuran bir program için `made` değeri `2` olacaktır, çünkü temsilci kurucuda bir kez değerlendirilir. Bu sayıyı `1` ya da çağrı sayısı kadar bekleyen kişi, devretmenin bir alan mı, yoksa her çağrıda yeniden hesaplanan bir ifade mi olduğunu yanlış anlamıştır.

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

Neden iki devreden nesne eşit değil? Çünkü `by` yalnız arayüzün üyelerini iletir; `equals`, `hashCode` ve `toString` arayüzün üyesi değil, `Any`'nin üyesidir. Önce bunu doğruladık: aşağıdaki parça Kotlin 2.4.21'de derlenmedi, bu yüzden çıktısız kalıyor. Bir arayüzün `Any`'nin `equals`'ü için varsayılan gerçekleştirim vermesi de yasak.

```kotlin
interface Same {
    override fun equals(other: Any?): Boolean = true
}
```

Devretme `equals`'ü iletmediğine göre, eşitliği değiştiren tek yol sınıfın kendisinin onu yeniden tanımlamasıdır. Veri sınıfı bunu otomatik yapar. Aşağıdaki blok, aynı temsilciyi saran bir sıradan sınıfı ve bir veri sınıfını yan yana ölçer; üçüncü satır devreden nesne ile temsilcisini karşılaştırır.

```kotlin
// Main.kt — devretme equals'u iletmez, veri sinifi kendi uretir
class Wrapped(val inner: Printer) : Printer by inner

data class Tagged(val inner: Printer) : Printer by inner

fun main() {
    val loud = LoudPrinter()
    val checks = listOf(
        Criterion("sinif-devretme", Wrapped(loud), Wrapped(loud), Wrapped(PlainPrinter())),
        Criterion("veri-sinifi-devretme", Tagged(loud), Tagged(loud), Tagged(PlainPrinter())),
        Criterion("devreden-ve-temsilci", Wrapped(loud), loud, PlainPrinter()),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
sinif-devretme: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
veri-sinifi-devretme: ayni-nesne=false, ikiz=true, farkli=false -> icerik
devreden-ve-temsilci: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
tekil: 0
kimlik: 2
icerik: 1
kismi-icerik: 0
```

Sıradan sınıfta ölçüt `kimlik`, veri sınıfında `icerik` çıktı. Veri sınıfı kendi `equals`'ünü üretip birincil yapıcıdaki `inner`'ı karşılaştırdığı için, aynı temsilciyi saran iki örnek eşit oldu; farklı temsilciyi saran ise eşit olmadı. Üçüncü satır devretmenin sınırını açık gösteriyor: `Wrapped(loud)` ile `loud` birbirine eşit değil. Devreden nesne temsilcisi gibi davranır ama temsilcisi değildir.

#### Özet

- Arayüz, bir tipin uyması gereken üyeleri yazar; gövdeli fonksiyon ve gövdeli okunur özellik taşıyabilir.
- İki arayüz aynı varsayılan fonksiyonu getirirse sınıf onu yeniden tanımlar ve `super<Arayüz>` ile ikisine ayrı ulaşır.
- `by`, arayüzün üyelerini temsilciye iletir; temsilcinin kendi yeniden tanımladığı gövdeler korunur.
- Devretme `equals`'ü iletmez ve bir arayüz `equals` için varsayılan gerçekleştirim veremez; aynı temsilciyi saran iki sıradan nesne eşit değildir, iki veri sınıfı ise eşittir.
- Bu dersin ölçütü: **kimlik**. Davranış paylaşılsa bile eşitlik, sınıfın `equals`'ünü yeniden tanımlamadıkça kimliğe düşer.

#### Sonraki Adım

Devretme, davranışı sınıfın içinde başka bir nesneye taşıdı. Sıradaki ders davranışı sınıfın dışından ekliyor: uzantı fonksiyonları, kaynağına dokunmadığınız bir tipe yeni fonksiyon yazdırır. Bir uzantının, tipin kendi üyesinin yerine geçip geçemeyeceğini, özellikle `equals` üzerinde ölçeceğiz.

### Uzantı Fonksiyonları

Source: https://academia.sh/tr/kurslar/kotlin/uzanti-fonksiyonlari

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.

### Genellikler ve Varyans

Source: https://academia.sh/tr/kurslar/kotlin/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.

## Koleksiyonlar ve Eşzamanlılık Temelleri

Salt okunur ve değiştirilebilir koleksiyonlar, eşleme, süzme ve indirgeme ile dönüşüm işlemleri, tembel diziler, eş yordamlar, akışlar ve denetlenmeyen istisna modeli.

### Koleksiyon Türleri

Source: https://academia.sh/tr/kurslar/kotlin/koleksiyon-turleri

Salt okunur arayüzün değiştirilemezlik anlamına gelmediğinin, listenin içeriğiyle, kümenin ve eşlemin sıradan bağımsız eşitliğinin ölçümü.

Önceki ders "Genellikler ve Varyans" tip parametresinin çalışma anında silindiğini gösterdi. Bu derste o tip parametresini en çok taşıyan yapıların, yani koleksiyonların (collection) üzerinden gideceğiz. Bir program hemen her zaman birden çok değer üzerinde çalışır: sipariş satırları, kullanıcı adları, ayar anahtarları. Kotlin bunlar için üç temel koleksiyon sunar: sıralı ve tekrara izin veren **liste (`List`)**, tekrara izin vermeyen **küme (`Set`)** ve anahtardan değere giden **eşlem (`Map`)**.

Üçünün de iki yüzü vardır. `List`, `Set` ve `Map` arayüzleri yalnız okuma işlemlerini bildirir: eleman sayısı, elemana erişim, içerme sorusu. Eleman ekleyip çıkaran işlemler `MutableList`, `MutableSet` ve `MutableMap` arayüzlerindedir. Bu ayrımın dilde iki adı vardır ve karıştırılmaması gerekir. Koleksiyon **salt okunur** ise elimizdeki arayüz yazma işlemi sunmaz. Koleksiyon **değiştirilemez** ise nesnenin kendisi hiçbir yolla değişmez. İlki bir arayüz özelliğidir, ikincisi bir nesne özelliği. Ders bu farkı ölçer, sonra iki koleksiyonun eşit sayılmasını neyin belirlediğini sorar.

Kursun bu konusundaki ortak ölçü sorusu şudur: **bu iki değeri eşit yapan ne?** Dört ölçüt vardır. Eşit çıkan iki değer aynı nesneyse ölçüt **tekil**dir. İkiz ayrı bir nesneyse ve eşit değilse **kimlik**tir. İkiz eşitse ve yalnız tek bakımdan farklı olan değer de eşit değilse **içerik**tir. Farklı olan değer de eşit çıkıyorsa **kısmi içerik**tir. Her ölçümde üç değer kurulur: `first`, aynı tarifle ayrı kurulmuş ikizi `twin` ve yalnız bir bakımdan farklı olan `other`. Aşağıdaki `Criterion` sınıfı bu üç değeri `===` ve `==` ile karşılaştırır, ölçütü adlandırır ve satır olarak basar. Sınıf bir kez tanımlanır, her ölçüm onu kullanır; 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 }}")
    }
}
```

Bu bloğun çıktısı yoktur: yalnızca ölçüm aracını tanımlar ve sonraki her blokla birlikte derlenir.

#### Salt Okunur Arayüz, Aynı Nesne

`val view: List<Int> = editable` satırı yeni bir liste kurmaz. Soldaki tip yalnız hangi işlemlerin görüneceğini söyler; sağdaki nesne `mutableListOf` ile kurulmuş değiştirilebilir listedir. Dolayısıyla `view` üzerinden eleman eklenemese de `editable` üzerinden eklenen eleman `view`'da görünür. Gerçek bir kopya için `toList()` kullanılır: çağrı o anın elemanlarıyla yeni bir liste kurar ve sonraki değişiklikler ona ulaşmaz. Aşağıdaki blok bunu üç sorudan okur: `view` eklemeden sonra ne gösteriyor, `snapshot` ne gösteriyor ve ikisi `editable` ile aynı nesne mi?

```kotlin
// Main.kt — salt okunur arayuz ayni nesnenin gorunumudur
fun main() {
    val editable = mutableListOf(1, 2)
    val view: List<Int> = editable
    val snapshot = editable.toList()
    editable.add(3)
    println(view)
    println(snapshot)
    println(view === editable)
    println(snapshot === editable)
    println((view as MutableList<Int>).size)
}
```

```
[1, 2, 3]
[1, 2]
true
false
3
```

İlk satır eklemenin `view`'da göründüğünü, ikinci satır `snapshot`'ın eklemeden önceki iki elemanda kaldığını gösterir. Üçüncü satır `view`'ın `editable` ile aynı nesne olduğunu, dördüncü satır kopyanın ayrı bir nesne olduğunu söyler. Son satır daha rahatsız edici bir sonuç verir: salt okunur tipli `view`, `MutableList`'e dönüştürülebilir ve boyutu `3` olarak okunur. Yani **salt okunur bir tip, değiştirilemezliğin garantisi değildir**; yalnız o referans üzerinden yazma işleminin yapılamayacağını belirtir. Bir fonksiyon `List` döndürüyorsa ve içeride değiştirilebilir bir listeyi olduğu gibi veriyorsa, çağıran kendi liste sanıp onu tutarken içeriğin altından kayabilir. Güvenli yol, sınırı aşarken `toList()` ile kopya vermektir.

**KE1.** Bu ölçüm `mutableListOf` ile kurulan liste için. `listOf(...)` ile kurulan listenin `MutableList`'e dönüştürülüp değiştirilmeye çalışılması ve diğer koleksiyon kurucularının bu bakımdan davranışı ayrıca ölçülmedi.

#### Ölçüt: İçerik

İki listeyi neyin eşit yaptığını soralım. Aklımıza gelen ilk cevap türdür: biri `MutableList`, öbürü `List` ise farklıdır. Ölçüm tersini gösterir. Aşağıdaki blok önce aynı elemanlı değiştirilebilir ve salt okunur bir listeyi, sonra `listOf` ile `arrayListOf` ile kurulanı `==` ile karşılaştırır. Ardından `Criterion` ile aynı ölçüyü sınıflandırır: `first` ve `twin` aynı tarifle (`mutableListOf(1, 2)`) ayrı kurulur, `other` ise yalnız son elemanı farklı bir listedir.

```kotlin
// Main.kt — liste turu degil icerik karsilastirilir
fun main() {
    println(mutableListOf(1, 2) == listOf(1, 2))
    println(listOf(1, 2) == arrayListOf(1, 2))
    val check = Criterion("liste", mutableListOf(1, 2), mutableListOf(1, 2), listOf(1, 3))
    check.report()
}
```

```
true
true
liste: ayni-nesne=false, ikiz=true, farkli=false -> icerik
```

İlk iki satır, türü ve gerçeklemesi farklı listelerin aynı elemanlarla eşit çıktığını gösterir. Üçüncü satır ölçütü adlandırır: ikiz ayrı bir nesne olduğu için `tekil` değil, ikiz eşit olduğu için `kimlik` değil, farklı eleman eşit çıkmadığı için `kısmi içerik` değil. Kalan ölçüt **içerik**tir. Bu dersin ölçütü budur: iki liste, aynı sırada aynı elemanları taşıyorsa eşittir; hangi arayüzle görüldükleri ya da hangi sınıftan geldikleri sonucu değiştirmez. Bu, yapısal eşitliğin (`==`) koleksiyonlarda `equals` çağrısını elemanlara kadar götürmesinin sonucudur. Kimlik eşitliği (`===`) ise iki ayrı listede her zaman yanlış çıkar ve burada hiç belirleyici olmaz.

**KE2.** Bu ölçüm iki elemanlı `Int` listeleri için. Elemanları kendi `equals`'ını tanımlamayan sınıflardan oluşan listelerin eşitliği ayrıca ölçülmedi; o durum bir sonraki dersin konusudur.

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

İçerik ölçütü listede sıra bilgisini de içerir: `listOf(1, 2)` ile `listOf(2, 1)` aynı elemanlara sahip olsa da eşit değildir. Küme ve eşlem için aynı soruyu sorarsak ölçütün sınırını buluruz. Aşağıdaki blok beş ölçümü tek seferde basar. Listede `other` ters sıradadır. Kümede ve eşlemde de `other` yalnız sırası farklı bir koleksiyondur; `eslem-deger` ölçümünde ise `other` bir değeri farklıdır. Son ölçümde boş iki listenin tip parametresi bilerek farklı tutulur: `emptyList<Int>()` ve `emptyList<String>()`.

```kotlin
// Main.kt — kumede ve eslemde sira fark degildir
fun main() {
    val checks = listOf(
        Criterion("liste", listOf(1, 2), listOf(1, 2), listOf(2, 1)),
        Criterion("kume", setOf(1, 2), setOf(1, 2), setOf(2, 1)),
        Criterion("eslem", mapOf("a" to 1, "b" to 2), mapOf("a" to 1, "b" to 2), mapOf("b" to 2, "a" to 1)),
        Criterion("eslem-deger", mapOf("a" to 1, "b" to 2), mapOf("a" to 1, "b" to 2), mapOf("a" to 1, "b" to 3)),
        Criterion("bos-liste", emptyList<Int>(), emptyList<Int>(), emptyList<String>()),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
liste: ayni-nesne=false, ikiz=true, farkli=false -> icerik
kume: ayni-nesne=false, ikiz=true, farkli=true -> kismi-icerik
eslem: ayni-nesne=false, ikiz=true, farkli=true -> kismi-icerik
eslem-deger: ayni-nesne=false, ikiz=true, farkli=false -> icerik
bos-liste: ayni-nesne=true, ikiz=true, farkli=true -> tekil
tekil: 1
kimlik: 0
icerik: 2
kismi-icerik: 2
```

Sınırlayıcı ölçüm şudur: **kümede ve eşlemde sıra fark değildir.** Listede sıra değişince eşitlik bozulur (`icerik`), kümede ve eşlemde aynı değişiklik hiçbir fark yaratmaz (`kismi-icerik`): farklı olan değer de eşit çıkar. Küme eşitliği aynı eleman topluluğuna bakar, sıra bunun parçası değildir; eşlem ise anahtar kümesi ve her anahtarın değeriyle karşılaştırılır, anahtarların yazılış sırası bunun dışındadır. `eslem-deger` satırı bu ikincisini doğrular: sıra aynı kalıp bir değer değişince eşlem eşit çıkmaz. Dört ölçümün dağılımı 2/2 olarak iki ölçüte bölünür; beşinci ölçüm başka bir şeyi gösterir.

Beşinci satırdaki sonuç iki yönden dikkat çeker. `emptyList<Int>()` ile `emptyList<String>()` aynı nesnedir, dolayısıyla ölçüt `tekil`dir ve hiç karşılaştırma gerektirmez. Dahası, bu boş liste tek ve ortak bir nesne olduğu için tip parametresi farklı iki çağrı da onu verir; tip parametresi çalışma anında silindiğinden `other` ile `first` eşit çıkar. Önceki dersteki tip silme burada bir koleksiyon eşitliğinin ayrıntısı olarak karşımıza çıkar.

**KE3.** Bu ölçümler iki elemanlı koleksiyonlar ve `emptyList` için. Daha büyük kümelerde karma kodunun çakışması durumundaki davranış ve kendi `hashCode`'unu tanımlamayan elemanlı kümeler ayrıca ölçülmedi.

#### Eşlem ve Küme İşlemleri

Eşlem, günlük işte listeden de sık kullanılır, ama okuma kuralı iki yerde şaşırtır. Bir anahtar yoksa `[]` ile okumak `null` döndürür; bu yüzden sonuç tipi boş değer alabilen bir tiptir (`Int?`). `getOrDefault` aynı durumda verdiğiniz değeri döndürür. `getValue` ise eksik anahtarda bir istisna (`NoSuchElementException`) fırlatır. Aşağıdaki blok bu üç okumayı, yazma işlemlerini ve `+` işleciyle yeni bir eşlem kurmayı bir arada ölçer. `stock + ("muz" to 2)` mevcut eşlemi değiştirmez, içinde yeni anahtar bulunan ayrı bir eşlem döndürür. Son iki satır `hashSetOf` ile kurulan kümeyi gösterir: bu kümenin yineleme sırası bir garanti değildir, bu yüzden elemanlar sıralanarak basılır.

```kotlin
// Main.kt — eslem okumalari
fun main() {
    val stock = mutableMapOf("elma" to 3, "armut" to 5)
    println(stock["muz"])
    println(stock.getOrDefault("muz", 0))
    stock["elma"] = 4
    stock.put("kiraz", 1)
    println(stock.keys)
    println(stock.values.sum())
    val copy = stock + ("muz" to 2)
    println(stock.size)
    println(copy.size)
    try {
        stock.getValue("muz")
    } catch (e: NoSuchElementException) {
        println(e::class.simpleName)
    }
    val tags = hashSetOf("b", "a", "b")
    println(tags.size)
    println(tags.sorted())
}
```

```
null
0
[elma, armut, kiraz]
10
3
4
NoSuchElementException
2
[a, b]
```

Sırayla okuyalım. Eksik anahtar `null` ve verilen varsayılan olarak `0` döndü. `stock["elma"] = 4` var olan anahtarın değerini değiştirdi, `put` yeni bir anahtar ekledi; anahtarlar `elma`, `armut`, `kiraz` sırasında kaldı, değerler `4 + 5 + 1` olarak `10` toplandı. `+` ile kurulan `copy` dört girdi taşırken `stock` üç girdide kaldı: yeni eşlem ayrıdır. `getValue` eksik anahtarda istisnayı fırlattı ve yakalandı. Son iki satır, `"b"` iki kez eklendiği hâlde kümenin iki elemanlı olduğunu ve sıralanmış hâlinin `[a, b]` olduğunu gösterir.

Buradan çıkan kural pratiktir: eksik olması beklenen bir anahtar için `[]` kullanılır ve sonuç `?:` (Elvis işleci) ile bir varsayılana bağlanır; eksik olmaması gereken bir anahtar için `getValue` kullanılır ve eksiklik bir hata olarak yüzeye çıkar. Bu iki yol arasındaki seçim aynı zamanda eşlemin sözleşmesidir: okunan değerin var olduğuna program mı karar veriyor, veri mi?

**KE4.** Bu ölçümler `mutableMapOf` ile kurulan eşlem ve `hashSetOf` ile kurulan küme için. `mutableMapOf` ile kurulan eşlemin yineleme sırası bu ölçümde eklenme sırasıyla örtüştü; bu bir dil garantisi olarak ölçülmedi ve `hashMapOf` için sıra hiç iddia edilmedi.

#### Dolaşırken Değiştirmek

En sık yapılan koleksiyon hatalarından biri, bir listeyi dolaşırken aynı listeden eleman silmektir. `for (x in list)` döngüsü arkada bir yineleyici (iterator) kullanır ve yineleyici, kendisi dışında bir yolla değişen koleksiyonu fark edince çalışmayı reddeder. Aşağıdaki blok dört elemanlı bir listede çift sayıları döngü içinde silmeye çalışır; fırlatılan istisnanın adını ve listenin o andaki durumunu basar. Sonra aynı iş `removeAll` ile yapılır. Son iki satır, liste ile kümenin aynı elemanlarla bile eşit sayılmadığını ve bir listeden kurulan kümenin sıradan bağımsız eşit çıktığını gösterir.

```kotlin
// Main.kt — dolasirken silmek
fun main() {
    val numbers = mutableListOf(1, 2, 3, 4)
    try {
        for (n in numbers) {
            if (n % 2 == 0) numbers.remove(n)
        }
    } catch (e: ConcurrentModificationException) {
        println(e::class.simpleName)
    }
    println(numbers)
    numbers.removeAll { it % 2 == 0 }
    println(numbers)
    println(listOf(1, 2) == setOf(1, 2))
    println(listOf(2, 1).toSet() == setOf(1, 2))
}
```

```
ConcurrentModificationException
[1, 3, 4]
[1, 3]
false
true
```

`2` döngünün ikinci adımında silindi ve liste değişti; istisna bir sonraki `next()` çağrısında fırlatıldı. Liste bu yüzden `[1, 3, 4]` durumunda kaldı; `4` hiç denetlenmedi. Bir çift sayının hâlâ listede kalması, hatanın yalnızca istisnadan ibaret olmadığını, yarım kalmış bir temizlik bıraktığını gösterir. `removeAll` ise koşulu bütün elemanlara tek geçişte uygular ve `[1, 3]` bırakır. Kural şudur: bir koleksiyonu silerek temizlemek gerekiyorsa `removeAll` ya da `retainAll` gibi koleksiyonun kendi toplu işlemi kullanılır; dolaşma sırasında koleksiyonun kendisinden silme yapılmaz (yineleyicinin kendi `remove` yöntemi bunun dışındadır).

Son iki satır eşitliğin türe de bağlı olduğunu gösterir. Aynı elemanlara sahip `List` ile `Set` eşit değildir: liste eşitliği yalnız başka bir listeyle, küme eşitliği yalnız başka bir kümeyle tanımlıdır. Bu nedenle liste kümeye çevrildikten sonra karşılaştırma yapılır; çevrilmiş `toSet()` sıradan bağımsız eşit çıkar.

**KE5.** Bu ölçüm `ArrayList` tabanlı `mutableListOf` için. Silmenin listenin sondan ikinci elemanında yapıldığı durumda istisnanın fırlatılıp fırlatılmadığı ölçülmedi ve güvence olarak yazılmaz.

#### Değişen Elemanın Bedeli

Küme ve eşlem, elemanı ya da anahtarı bir sayı (karma kodu) ile yerleştirir. Eleman değiştirilebilir bir listeyse ve kümeye girdikten sonra değiştirilirse, sayı değişir ama küme eleman yerini güncellemez. Sonuç, kümede duran ama aranınca bulunamayan bir elemandır. Aşağıdaki blok bir listeyi kümeye ekler, listeye eleman ekler ve kümede iki biçimde arar: değiştirilmiş listenin kendisiyle ve aynı içerikte yeni kurulmuş bir listeyle. Boyut her durumda okunur.

```kotlin
// Main.kt — kumeye girdikten sonra degisen eleman
fun main() {
    val key = mutableListOf(1)
    val bag = hashSetOf(key)
    println(bag.contains(key))
    key.add(2)
    println(bag.size)
    println(bag.contains(key))
    println(bag.contains(mutableListOf(1, 2)))
    println(bag.any { it === key })
}
```

```
true
1
false
false
true
```

Eklemeden önce arama başarılıdır. Eklemeden sonra küme boyutu `1` kalır, yani eleman silinmedi; ama değiştirilmiş listeyle de, aynı içerikli yeni listeyle de arama başarısız olur. Son satır elemanın hâlâ orada olduğunu kanıtlar: kümeyi doğrudan dolaşınca aynı nesne bulunur. Eleman kümede durur, ama küme ona artık ulaşamaz. Bu hata sessizdir ve istisna fırlatmaz.

Çıkarılacak ders açık: küme elemanı ya da eşlem anahtarı olacak değerler girdikten sonra değişmeyen türlerden seçilir. `String`, sayılar ve alanlarının hepsi `val` olup kendileri de değiştirilemeyen veri sınıfları bu bakımdan güvenlidir; `MutableList` değildir. Değişen bir değer anahtar yapılacaksa önce `toList()` ile kopyası alınır ve kopya anahtar olur.

**KE6.** Bu ölçüm bir `HashSet` içindeki `ArrayList` elemanı için. `LinkedHashSet` ve ağaç tabanlı kümelerin aynı durumdaki davranışı ve eşlem anahtarı olarak değişen eleman ölçülmedi.

#### Özet

- Salt okunur `List`, `Set` ve `Map` arayüzleri değiştirilemezlik garantisi vermez; aynı nesneyi gösteren `MutableList` değişirse salt okunur görünüm de değişir.
- Gerçek kopya için `toList()` kullanılır; sınırı aşan bir fonksiyon değiştirilebilir bir koleksiyonu olduğu gibi `List` diye döndürmemelidir.
- Liste eşitliği türe değil içeriğe ve sıraya bakar. Bu dersin ölçütü: **içerik**.
- Sınırlayıcı ölçüm: küme ve eşlemde sıra fark değildir (`kismi-icerik`); boş liste tekil bir nesnedir ve tip parametresi eşitliği etkilemez.
- Eşlemde eksik anahtar `[]` ile `null`, `getValue` ile istisna verir.

#### Sonraki Adım

Bu ders koleksiyonu bir veri kabı olarak ölçtü. Bir sonraki ders, o kabın içeriğinden yeni koleksiyonlar üreten işlemlere geçer: `map`, `filter`, `fold`, `groupBy`. Orada da aynı soru geri döner: aynı listeye aynı dönüşüm iki kez uygulanırsa sonuçlar neyle eşit sayılır?

### Dönüşüm İşlemleri

Source: https://academia.sh/tr/kurslar/kotlin/donusum-islemleri

Eşleme, süzme, katlama ve gruplamanın yeni koleksiyon ürettiğinin, aynı dönüşümün iki kez uygulanınca eşit ama ayrı sonuç verdiğinin ve distinct ile toSet'in elemanın eşitliğine dayandığının ölçümü.

Önceki ders koleksiyonları bir veri kabı olarak ölçtü ve içeriğin eşitliği belirlediğini gösterdi. Gerçek programlarda koleksiyon nadiren olduğu gibi kullanılır. Sipariş listesinden tutarları toplamak, bir şehre ait siparişleri ayırmak, iki listeyi eşleştirmek gerekir. Bunların hepsi **dönüşüm işlemleridir**: bir koleksiyonu alır, ondan yeni bir koleksiyon ya da tek bir değer üretirler. Dönüşüm işlemlerinin çoğu **yüksek mertebeden fonksiyon**dur, yani parametre olarak bir lambda alır; `sorted()` ya da `distinct()` gibi lambda almayanlar da vardır. Lambda, her elemana uygulanacak kuralı taşır; işlemin adı bu kuralın ne için kullanılacağını söyler.

Dört temel işlemin Türkçe adları şunlardır. `map` **eşleme**dir: her elemandan bir yenisini üretir ve sonuç aynı uzunlukta bir listedir. `filter` **süzme**dir: lambdanın doğru dediği elemanları tutar. `fold` **katlama**dır: bir başlangıç değerinden yola çıkıp elemanları tek bir değere birleştirir. `reduce` **indirgeme**dir: katlamanın başlangıç değeri vermeyen biçimidir, ilk elemanı başlangıç sayar. Kursun ortak ölçü sorusu bu derste de geri döner: **bu iki değeri eşit yapan ne?** Dört ölçüt (tekil, kimlik, içerik, kısmi içerik) önceki derste tanıtıldığı biçimde kalır. `Criterion` sınıfı olduğu gibi tekrar verilir.

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

Bu dersin ölçümleri dört ayrı veri tipi kullanır. `Order` bir sipariş satırıdır: kimlik, şehir ve tutar. `Ticket` yalnız bir kimlik taşıyan **veri sınıfı**dır; `Plain` aynı alanı taşıyan ama veri sınıfı olmayan sıradan bir sınıftır. `Session` ise kimliği kurucuda, ziyaret sayısını gövdede taşıyan bir veri sınıfıdır. Veri sınıfı `equals`'ı kurucudaki özelliklerden üretir; gövdede tanımlanan özellik bu üretime girmez. `session` fonksiyonu iki alanı birlikte kuran küçük bir yardımcıdır. Bu bloğun da çıktısı yoktur.

```kotlin
// Models.kt — donusum olcumlerinin kullandigi tipler
data class Order(val id: Int, val city: String, val amount: Int)

data class Ticket(val id: Int)

class Plain(val id: Int)

data class Session(val id: Int) {
    var visits = 0
}

fun session(id: Int, visits: Int): Session {
    val result = Session(id)
    result.visits = visits
    return result
}
```

#### Dört Temel İşlem

İlk ölçüm bir `Int` listesine dört işlemi uygular. `map { it * 2 }` her tutarı ikiyle çarpar, `filter { it > 6 }` altı ve altındakileri eler. `fold(0)` toplamı verir; başlangıç değeri `0` olduğu için sonuç tipi başlangıç değerinin tipidir. `reduce` ile en büyük eleman bulunur. Son iki ölçüm iki işlemin boş koleksiyonda ayrıldığı yeri gösterir: `fold` başlangıç değerini geri verir, `reduce`'un ise başlayacak ilk elemanı olmadığı için bir istisna fırlatması beklenir. Ölçümde istisna `try` ile yakalanır ve yalnız sınıf adı basılır.

```kotlin
// Main.kt — eslem, suzme, katlama, indirgeme
fun main() {
    val amounts = listOf(5, 12, 7, 20)
    println(amounts.map { it * 2 })
    println(amounts.filter { it > 6 })
    println(amounts.fold(0) { acc, n -> acc + n })
    println(amounts.reduce { acc, n -> maxOf(acc, n) })
    println(emptyList<Int>().fold(0) { acc, n -> acc + n })
    try {
        emptyList<Int>().reduce { acc, n -> acc + n }
    } catch (e: UnsupportedOperationException) {
        println(e::class.simpleName)
    }
}
```

```
[10, 24, 14, 40]
[12, 7, 20]
44
20
0
UnsupportedOperationException
```

İlk satırda dört eleman dört elemana eşlendi. İkinci satırda yalnız üç eleman süzmeden geçti: `5` elendi. Üçüncü satır `5 + 12 + 7 + 20` toplamıdır; dördüncü satır `maxOf` ile `20`'yi bulur. Beşinci satır `fold`'un boş listede başlangıç değeri olan `0`'ı döndürdüğünü, altıncı satır `reduce`'un aynı durumda `UnsupportedOperationException` fırlattığını gösterir. Kural açıktır: eleman olup olmadığı bilinmiyorsa `fold` seçilir; boş koleksiyon hata sayılacaksa `reduce` seçilir.

**KE7.** Bu ölçümler `Int` listeleri için. Elemanları başka tipte olan bir `reduce`'un sonuç tipinin elemanların ortak üst tipine nasıl daraldığı ve `reduceOrNull` gibi kardeş işlemler ayrıca ölçülmedi.

#### Gruplama, Eşlem Kurma ve Eşleştirme

Üç işlem daha koleksiyondan koleksiyon üretir, ama sonuç liste olmak zorunda değildir. `groupBy` her elemanı bir anahtara göre toplar ve anahtardan listeye giden bir eşlem kurar. `associate` her elemandan bir anahtar-değer çifti üretir ve bunlardan bir eşlem kurar; `associateBy` anahtarı elemandan alır, değer olarak elemanın kendisini saklar. İki elemanın anahtarı aynı çıkarsa **sonradan gelen öncekinin yerini alır**. `zip` iki listeyi sırayla çiftler ve çift sayısı kısa olan listeyle sınırlıdır. Ölçümde `associateBy` sonucunun `ankara` anahtarındaki siparişin kimliği okunur; iki ankara siparişinden hangisinin kaldığı böylece görünür.

```kotlin
// Main.kt — gruplama, eslem kurma, zip
fun main() {
    val orders = listOf(Order(1, "ankara", 30), Order(2, "izmir", 10), Order(3, "ankara", 20))
    println(orders.groupBy { it.city }.mapValues { entry -> entry.value.map { it.id } })
    println(orders.associate { it.id to it.amount })
    println(orders.associateBy { it.city }["ankara"]?.id)
    println(orders.map { it.id }.zip(listOf("a", "b", "c")))
    println(listOf(1, 2, 3).zip(listOf("a", "b")))
}
```

```
{ankara=[1, 3], izmir=[2]}
{1=30, 2=10, 3=20}
3
[(1, a), (2, b), (3, c)]
[(1, a), (2, b)]
```

İlk satırda `groupBy` iki ankara siparişini tek listede topladı ve `izmir` için tek elemanlı bir liste bıraktı. İkinci satır kimlikten tutara giden bir eşlemdir. Üçüncü satırda `ankara` anahtarı için `3` okunur: kimliği `1` olan sipariş, kimliği `3` olanın gelmesiyle yerini kaybetti. Dördüncü satır üç çiftli bir listedir. Beşinci satırda ikinci liste kısa olduğu için `3` elemanı çiftlenmeden kaldı ve sonuç iki çifttir. `zip` hiçbir zaman istisna fırlatmaz; uzunlukların uyuşmaması sessizce en kısa uzunluğa indirgenir. Bu davranış çoğu zaman istenendir ama iki listenin aynı uzunlukta olması gerekiyorsa bunu `zip`'ten önce kontrol etmek gerekir.

**KE8.** Bu ölçümler üç elemanlı sipariş listesi için. `groupBy` ile `groupingBy` arasındaki farklar, `unzip` ve üç listeli `zip` ayrıca ölçülmedi.

#### Ölçüt: İçerik

Dönüşüm işleminin sonucu yeni bir koleksiyondur. Aynı işlemi aynı koleksiyona iki kez uyguladığımızda iki sonuç elde ederiz. Bu iki sonucun aynı nesne olması gerekmez, ama içeriklerinin eşit olması gerekir. Aşağıdaki blok `map` ve `filter` için iki kez dönüşüm uygular. `first` ve `twin` aynı tarifle ayrı kurulur; `other` ise bir bakımdan farklıdır: `map` için son eleman `41`'dir, `filter` için bir eleman eksiktir. Son satır, her elemanı olduğu gibi döndüren `map { it }` sonucunun kaynak listeyle aynı nesne olup olmadığını sorar.

```kotlin
// Main.kt — ayni donusum iki kez
fun main() {
    val amounts = listOf(5, 12, 7, 20)
    val checks = listOf(
        Criterion("map", amounts.map { it * 2 }, amounts.map { it * 2 }, listOf(10, 24, 14, 41)),
        Criterion("filter", amounts.filter { it > 6 }, amounts.filter { it > 6 }, listOf(12, 7)),
    )
    for (check in checks) check.report()
    summarize(checks)
    println(amounts.map { it } === amounts)
}
```

```
map: ayni-nesne=false, ikiz=true, farkli=false -> icerik
filter: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 0
kimlik: 0
icerik: 2
kismi-icerik: 0
false
```

Her iki ölçüm de **içerik** çıktı. İkizler ayrı nesnelerdir (`ayni-nesne=false`), ama eşittir; farklı olan liste ise eşit değildir. Dönüşüm işleminin sonucunu tanımlayan şey, hangi çağrıdan çıktığı değil, taşıdığı elemanlardır. Bu dersin ölçütü budur: **içerik**. Son satır önemli bir ayrıntıyı ayırır: elemanları değiştirmeyen `map { it }` bile kaynak listeyi geri vermez, yeni bir liste kurar. Dönüşüm işlemleri kaynak koleksiyona dokunmaz; bu dersin ölçümlerinde sonuç her seferinde ayrı bir nesneydi ve kaynağın sonradan değişmesi sonucu etkilemedi. Boş sonuçlarda standart kitaplık ortak bir boş liste döndürebilir; bu durum burada ölçülmedi.

**KE9.** Bu ölçümler `Int` elemanlı listeler için. Boş koleksiyona uygulanan dönüşümün, önceki derste ölçülen tekil boş listeyi döndürüp döndürmediği ayrıca ölçülmedi.

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

İçerik ölçütü, elemanlar `Int` iken kolay işler: iki `5` birbirine eşittir. Elemanlar kendi eşitliğini taşıyan nesneler olunca durum değişir. `distinct()` ve `toSet()` tekrar eden elemanı elemek için **elemanın kendi eşitliğini** kullanır; dönüşüm işlemi ölçütü kendisi üretmez, elemandan alır. Aşağıdaki blok aynı kuralı üç tipe uygular. `Ticket` veri sınıfıdır, iki `Ticket(1)` eşittir. `Plain` sıradan bir sınıftır, iki `Plain(1)` aynı nesne olmadıkça eşit değildir. `Session` veri sınıfıdır ama `visits` özelliği gövdede tanımlıdır ve eşitliğe girmez. `distinctBy` ise elemanı değil seçicinin döndürdüğü değeri karşılaştırır.

```kotlin
// Main.kt — distinct ve toSet ogenin olcutunu kullanir
fun main() {
    val tickets = listOf(Ticket(1), Ticket(1), Ticket(2))
    val plains = listOf(Plain(1), Plain(1), Plain(2))
    println(tickets.distinct().size)
    println(plains.distinct().size)
    println(tickets.toSet().size)
    println(plains.toSet().size)
    println(plains.distinctBy { it.id }.size)
    val sessions = listOf(session(1, 0), session(1, 5))
    println(sessions.distinct().size)
    val checks = listOf(
        Criterion("veri-sinifi", Ticket(1), Ticket(1), Ticket(2)),
        Criterion("siradan-sinif", Plain(1), Plain(1), Plain(2)),
        Criterion("govde-ozelligi", session(1, 0), session(1, 0), session(1, 5)),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
2
3
2
3
2
1
veri-sinifi: ayni-nesne=false, ikiz=true, farkli=false -> icerik
siradan-sinif: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
govde-ozelligi: ayni-nesne=false, ikiz=true, farkli=true -> kismi-icerik
tekil: 0
kimlik: 1
icerik: 1
kismi-icerik: 1
```

Sınırlayıcı ölçüm şudur: **`distinct` ve `toSet` ölçütü elemandan alır.** İlk dört satırda aynı kimlik dizisi (`1`, `1`, `2`) iki tipe uygulanmıştır. Veri sınıfı olan `Ticket` için `distinct` ve `toSet` iki elemana indirir: iki `Ticket(1)` eşit sayılır ve biri elenir. Sıradan sınıf olan `Plain` için ikisi de üç eleman bırakır: `Plain(1)` ile `Plain(1)` ayrı nesnelerdir ve eşit değildir, bu yüzden hiçbiri elenmez. Beşinci satırda `distinctBy { it.id }` aynı `Plain` listesini ikiye indirir. Burada ölçütü elemanın kendisi değil, seçicinin döndürdüğü `Int` belirler; elemanın sıradan sınıf olması fark etmez. Altıncı satır `Session` için bir şaşırtma gösterir: ziyaret sayıları `0` ve `5` olmasına rağmen iki oturum tek elemana iner, çünkü `visits` kurucuda olmadığı için veri sınıfının eşitliğine girmez.

Sonraki üç satır aynı üç durumu `Criterion` ile adlandırır. `Ticket` için ölçüt **içerik**tir. `Plain` için ölçüt **kimlik**tir: ikiz bile eşit değildir. `Session` için ölçüt **kısmi içerik**tir: `other` yalnız `visits` bakımından farklıdır ve yine de eşit çıkar. Dağılım 0/1/1/1 olarak üç ölçüte bölünür. Dersin ölçütü yine içeriktir, ama sınır şuradadır: dönüşüm işlemleri bir eşitlik tanımı getirmez, elemanın eşitliğini kullanır. Eleman eşitliği yanlış kurulduysa, sonuçtaki tekrar eleme de yanlış kurulmuş olur.

**KE10.** Bu ölçümler birer alanlı `Ticket` ve `Plain` ile iki alanlı `Session` için. `distinct`'in hangi tekrar eden elemanı tuttuğu (ilk mi, son mu) bu ölçümlerde sorulmadı; `distinctBy` için de aynı.

#### Sıralama ve Yerinde Değişiklik

Dönüşüm işlemlerinde sık yapılan bir hata, `sorted` ile `sort` arasındaki farkı gözden kaçırmaktır. `sorted()` ve `sortedBy` yeni bir sıralı liste döndürür, kaynağa dokunmaz. `sort()` ise yalnız değiştirilebilir listede vardır, listeyi yerinde değiştirir ve bir şey döndürmez. Birincisini yazıp sonucu kullanmamak, sıralanmış bir liste beklerken eski listeyle kalmak demektir. Ayrıca `sortedBy` kararlıdır: anahtarı eşit çıkan elemanlar kaynaktaki sıralarını korur. Aşağıdaki blok bunları sırayla ölçer. Sipariş listesi şehre göre sıralanır ve yalnız kimlikler basılır; iki ankara siparişi arasındaki sıra kararlılığı buradan okunur.

```kotlin
// Main.kt — sorted yeni liste, sort yerinde
fun main() {
    val numbers = mutableListOf(3, 1, 2)
    val ordered = numbers.sorted()
    println(numbers)
    println(ordered)
    println(ordered === numbers)
    numbers.sort()
    println(numbers)
    val orders = listOf(Order(1, "izmir", 10), Order(2, "ankara", 30), Order(3, "ankara", 20), Order(4, "bursa", 5))
    println(orders.sortedBy { it.city }.map { it.id })
    println(orders.sortedByDescending { it.amount }.map { it.id })
}
```

```
[3, 1, 2]
[1, 2, 3]
false
[1, 2, 3]
[2, 3, 4, 1]
[2, 3, 1, 4]
```

Birinci satır `sorted()` çağrısından sonra `numbers`'ın dokunulmadan kaldığını, ikinci satır `ordered`'ın sıralı yeni liste olduğunu, üçüncü satır ikisinin ayrı nesneler olduğunu gösterir. `sort()` çağrısından sonra `numbers`'ın kendisi sıralanır. Beşinci satırda şehre göre sıralama `ankara`, `bursa`, `izmir` düzenini kurar ve iki ankara siparişi kaynaktaki sıralarıyla (`2`, sonra `3`) kalır; kararlılık budur. Altıncı satır tutara göre azalan sıralamadır.

**KE11.** Bu ölçümler `Int` ve `Order` listeleri için. `sortedWith` ile bileşik karşılaştırıcı, `Comparable` olmayan elemanlarda sıralama ve çok büyük listelerde sıralamanın bedeli ölçülmedi.

#### Boş Koleksiyon ve Sınır Durumları

Dönüşüm işlemlerinin ikinci hata kaynağı sınırdır: koleksiyon boşsa ya da istenen parça koleksiyondan büyükse ne olur? `first()` boş listede istisna fırlatır, `firstOrNull()` ise `null` döndürür. `maxOrNull()` boş listede `null` verir. `average()` boş listede `NaN` verir, istisna fırlatmaz, bu yüzden sonuç sessizce sayı olmayan bir değer olur ve sonraki hesapları bozar. `chunked(n)` listeyi en çok `n` elemanlı parçalara böler ve son parça kısa kalabilir. `windowed(n)` ise her konumda `n` elemanlı kayan bir pencere kurar; koleksiyon `n`'den kısaysa hiç pencere üretmez. Ölçümde istisna `try` ile yakalanır ve yalnız sınıf adı basılır.

```kotlin
// Main.kt — bos koleksiyon ve parcalama
fun main() {
    val empty = emptyList<Int>()
    println(empty.firstOrNull())
    println(empty.maxOrNull())
    println(empty.average())
    try {
        empty.first()
    } catch (e: NoSuchElementException) {
        println(e::class.simpleName)
    }
    val five = listOf(1, 2, 3, 4, 5)
    println(five.chunked(2))
    println(five.windowed(3))
    println(listOf(1, 2).windowed(3))
    println(listOf("1", "x", "3").mapNotNull { it.toIntOrNull() })
}
```

```
null
null
NaN
NoSuchElementException
[[1, 2], [3, 4], [5]]
[[1, 2, 3], [2, 3, 4], [3, 4, 5]]
[]
[1, 3]
```

İlk iki satır `null` verir. Üçüncü satırda boş listenin ortalaması `NaN` çıktı: istisna yok, ama sonucu sayı sanıp kullanan kod yanlış çalışır. Bu yüzden ortalama almadan önce listenin boş olup olmadığını denetlemek ya da `isEmpty()` sonucuna göre dallanmak gerekir. Dördüncü satır `first()`'in boş listede `NoSuchElementException` fırlattığını gösterir. Beşinci satırda `chunked(2)` beş elemanı `[1, 2]`, `[3, 4]`, `[5]` olarak üç parçaya böler; son parça kısadır. Altıncı satırda `windowed(3)` üç pencere üretir: `[1, 2, 3]`, `[2, 3, 4]`, `[3, 4, 5]`. Yedinci satırda iki elemanlı liste için pencere kurulamaz ve sonuç boş listedir. Son satırda `mapNotNull`, dönüştürme sırasında `null` veren elemanı atar: `"x"` sayıya çevrilemediği için yalnız `1` ve `3` kalır. `map` ve `filterNotNull` ikilisinin tek adımda yazılmış biçimidir.

**KE12.** Bu ölçümler `Int` ve `String` listeleri için. `windowed`'un `step` ve `partialWindows` parametreleri ve `Double` listelerinde `average` dışındaki toplayıcı işlemlerin boş durumları ölçülmedi.

#### Özet

- `map`, `filter`, `fold` ve `reduce` kaynak koleksiyona dokunmaz; bu dersin ölçümlerinde sonuç ayrı bir nesneydi. `fold` boş koleksiyonda başlangıç değerini, `reduce` bir istisna verir.
- `groupBy` anahtardan listeye giden, `associate` ve `associateBy` anahtardan değere giden bir eşlem kurar; anahtar çakışınca sonraki girdi öncekinin yerini alır. `zip` kısa olan listeye göre keser.
- Aynı dönüşümü aynı koleksiyona iki kez uygulamak eşit ama ayrı iki koleksiyon verir. Bu dersin ölçütü: **içerik**.
- Sınırlayıcı ölçüm: `distinct` ve `toSet` elemanın kendi eşitliğine dayanır. Veri sınıfı öğelerde tekrarlar elenir, sıradan sınıf öğelerinde elenmez, gövde özelliği eşitliğe girmez; `distinctBy` ölçütü seçicinin değerinden alır.

#### Sonraki Adım

Bu ders her dönüşüm işleminin hemen çalıştığını ve her adımda yeni bir koleksiyon kurduğunu varsaydı. Zincirdeki ara listeler gereksizse ya da kaynak sonsuzsa bu varsayım gereksiz iş yaptırır ya da hiç bitmez. Bir sonraki ders aynı işlemleri tembel dizilerle (`Sequence`) çalıştırır ve eşitlik sorusunu orada yeniden sorar: iki tembel dizi, aynı elemanları üretiyor olsa da eşit midir?

### Tembel Diziler

Source: https://academia.sh/tr/kurslar/kotlin/tembel-diziler

Tembel dizinin ara işlemleri uç işlem gelene kadar bekletip elemanı parça parça işlediğinin, sonsuz kaynaktan bitirilebildiğinin ve iki tembel dizinin eşit sayılmadığının ölçümü.

Önceki ders her dönüşümün bir koleksiyon aldığını ve yeni bir koleksiyon verdiğini gösterdi. `map` dört elemanlı listeden dört elemanlı liste kurar, `filter` bu listeden bir üçüncüsünü kurar; zincirde her adım kendi ara listesini bırakır. Çoğu zaman bu sorun olmaz. Ama kaynak çok büyükse, sonsuzsa ya da yalnızca ilk eşleşen eleman isteniyorsa, her adımın tüm elemanları işlemesi gereksiz iş demektir.

Kotlin bu durum için **tembel dizi (`Sequence`)** sunar. Tembel dizi elemanları saklayan bir kap değil, elemanları sırayla üretme tarifidir. Üzerine eklenen `map`, `filter` gibi **ara işlemler** bir şey hesaplamaz; yalnızca tarife bir adım ekleyip yeni bir tembel dizi döndürür. Hesabı başlatan, sonucu çıkaran işlemlere **uç işlem** denir: `toList`, `first`, `count` ve benzerleri. Uç işlem gelince elemanlar zincirden bir bir geçer: ilk eleman bütün ara işlemlerden geçip sonuca ulaşır, ancak ondan sonra ikinci eleman başlar. Tembellik burada bir ölçü ekseni değildir, yalnız davranışı açıklayan bir özelliktir: değerin ne zaman hesaplandığını soran kategori bu kursta kullanılmaz. Ölçü sorusu önceki derslerdeki gibi kalır: **bu iki değeri eşit yapan ne?**

Dikkat edilecek bir ad çakışması vardır. Kotlin'de **dizi** (`Array`, `IntArray`) elemanları ardışık bellekte tutan sabit boyutlu bir yapıdır. Tembel dizi ise `Sequence` arayüzüdür ve aynı adı taşımaz; bu derste "dizi" dendiğinde her zaman `Sequence` kastedilir ve ayrım gereken yerde "tembel dizi" yazılır. Dört ölçüt, `Criterion` ve `summarize` önceki derslerdeki gibidir.

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

#### Liste Hemen, Tembel Dizi Parça Parça

Fark en açık yan etkiyle görünür. Aşağıdaki blok aynı iki işlemi aynı dört elemana önce tembel diziyle, sonra listeyle uygular. Her lambda bir kaydı `log`'a ekler, böylece hangi adımın ne zaman koştuğu sonradan okunur. Tembel dizi için sırayla üç şey sorulur: zincir kurulduktan hemen sonra kaç kayıt var, `first()` ne döndürüyor ve kayıtlar hangi sırada. Liste için ise yalnız kayıt sayısı ve ilk eleman sorulur.

```kotlin
// Main.kt — liste hemen, tembel dizi parca parca isler
fun main() {
    val log = mutableListOf<String>()
    val lazy = sequenceOf(1, 2, 3, 4)
        .map { log.add("map$it"); it * 2 }
        .filter { log.add("filter$it"); it > 2 }
    println(log.size)
    println(lazy.first())
    println(log)

    val eagerLog = mutableListOf<String>()
    val eager = listOf(1, 2, 3, 4)
        .map { eagerLog.add("map$it"); it * 2 }
        .filter { eagerLog.add("filter$it"); it > 2 }
    println(eagerLog.size)
    println(eager.first())
}
```

```
0
4
[map1, filter2, map2, filter4]
8
4
```

Birinci satır, zincir kurulduğunda tembel dizide hiçbir lambdanın koşmadığını gösterir: ara işlemler tarife eklendi, hesap başlamadı. İkinci satırda `first()` uç işlem olarak çalıştı ve `4` döndürdü. Üçüncü satır neyin çalıştığını söyler. İlk eleman `1`, `map1` ile `2` oldu, `filter2` onu eledi (`2 > 2` doğru değil). İkinci eleman `2`, `map2` ile `4` oldu, `filter4` onu geçirdi ve `first()` ilk eşleşmeyi bulunca durdu. `3` ve `4` elemanları hiç işlenmedi. Toplam dört kayıt tutuldu.

Listede aynı zincir sekiz kayıt bıraktı: dört `map`, dört `filter`. Beşinci satırdaki `4` aynı sonucu verir, ama önce dört eleman da eşlendi, sonra dört eleman da süzüldü ve yalnız ardından `first()` çağrıldı. Aradaki fark sonucun değil, **işin** farkıdır. Sonuç aynı çıkar; tembel dizi gereksiz elemanı işlemez, ara liste kurmaz. Bu, yan etkisi olan lambdalarda kayıt sırasını da değiştirir: listede önce bütün `map`'ler, sonra bütün `filter`'lar koşar; tembel dizide her eleman kendi zincirini tamamlar.

**KE13.** Bu ölçümler dört elemanlı bir `Int` kaynağı ve yan etkili lambdalar için. Yan etkisiz saf lambdalarla zincirin kaç elemanı işlediği ve kaynağın büyüklüğüyle iş miktarının nasıl değiştiği ölçülmedi.

#### Sonsuz Kaynak ve Kaynak Türleri

Tembel dizi elemanları baştan saklamadığı için kaynağı sonsuz olabilir. `generateSequence(1) { it * 2 }` ilk eleman olarak `1` verir, sonrakini bir önceki elemandan üretir ve hiç bitmez. Sonlu bir sonuç almak için zincire `take` ya da `takeWhile` gibi sınırlayıcı bir işlem eklenir. `sequence { ... }` kurucusu ise içinde `yield` çağıran bir blok alır: her `yield` bir eleman üretir ve blok bir sonraki elemanın istenmesine kadar durur. Bir listeden tembel diziye geçmek için `asSequence()` kullanılır. Son iki ölçüm, aynı tembel dizinin iki kez `toList()` ile toplanabildiğini gösterir.

```kotlin
// Main.kt — sonsuz kaynak ve kaynak turleri
fun main() {
    println(generateSequence(1) { it * 2 }.take(6).toList())
    println(generateSequence(1) { it + 1 }.map { it * it }.takeWhile { it < 50 }.toList())
    println(listOf(3, 8, 11).asSequence().filter { it > 5 }.count())
    val squares = sequence {
        var n = 1
        while (true) {
            yield(n * n)
            n++
        }
    }
    println(squares.drop(2).take(3).toList())
    val twice = sequenceOf(1, 2, 3).map { it + 1 }
    println(twice.toList())
    println(twice.toList())
}
```

```
[1, 2, 4, 8, 16, 32]
[1, 4, 9, 16, 25, 36, 49]
2
[9, 16, 25]
[2, 3, 4]
[2, 3, 4]
```

İlk satır ikinin kuvvetlerinden altı eleman alır. İkinci satır doğal sayıların karelerini üretir ve elli ya da daha büyük karede durur; son eleman `49`'dur. Üçüncü satırda liste tembel diziye çevrilir, süzülür ve sayılır: `8` ve `11` beşten büyüktür, sonuç `2`. Dördüncü satır `sequence { }` ile yazılmış sonsuz bir kare üreticisinden ilk iki elemanı atar (`1` ve `4`) ve sonraki üçünü alır. Son iki satır aynı tembel dizinin iki kez toplandığını ve iki kez aynı sonucu verdiğini gösterir. `sequenceOf` ile kurulmuş bir dizi birden çok kez dolaşılabilir; her uç işlem tarifi baştan işletir. Bu her tembel diziye genellenemez, ama bu dersin kaynakları için doğrudur.

Burada bir sınır da var: sonsuz kaynakta sınırlayıcı işlem unutulursa program bitmez. `take` ya da `takeWhile` gibi bir işlem olmadan `toList()` çağırmak, sonsuz bir koleksiyonu belleğe sığdırmaya çalışır. Tembel dizi sonsuzluğu mümkün kılar, ama sonlu bir sonucu kurma sorumluluğunu çağırana bırakır.

**KE14.** Bu ölçümler `generateSequence`, `sequence`, `sequenceOf` ve `asSequence` kaynakları için. `generateSequence`'in tohumsuz biçimi gibi yalnız bir kez dolaşılabilen kaynakların ikinci dolaşımda ne yaptığı ve sınırlayıcı işlemsiz sonsuz kaynağın sonucu ayrıca ölçülmedi.

#### Ölçüt: Kimlik

Tembel diziler kendi içeriklerini saklamaz, tarif taşır. Bu yüzden iki tembel dizinin eşitliği sorusu, iki listeninkinden farklı bir cevap verir. Aşağıdaki blok iki ayrı `sequenceOf(1, 2)` kurar, ikisini `==` ile karşılaştırır ve `Criterion` ile adlandırır. `other` yalnız ikinci elemanı farklı bir tembel dizidir. İkinci ölçümde tembel diziler aynı listeden `asSequence()` ile iki kez kurulur: kaynak aynı liste olsa bile her çağrı yeni bir tembel dizi döndürür.

```kotlin
// Main.kt — iki tembel dizi
fun main() {
    val list = listOf(1, 2)
    val checks = listOf(
        Criterion("sequenceOf", sequenceOf(1, 2), sequenceOf(1, 2), sequenceOf(1, 3)),
        Criterion("asSequence", list.asSequence(), list.asSequence(), sequenceOf(1, 3)),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
sequenceOf: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
asSequence: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
tekil: 0
kimlik: 2
icerik: 0
kismi-icerik: 0
```

İki ölçüm de **kimlik** çıktı. İkizler ayrı nesnelerdir ve eşit değildir; yani iki `sequenceOf(1, 2)` aynı elemanları üretecek olsa da `==` ile karşılaştırınca `false` verir. Bunun nedeni `Sequence`'in `equals`'ı tanımlamamasıdır: eşitlik varsayılan davranışa, yani kimliğe düşer. Bu dersin ölçütü budur: **kimlik**. `other` ölçümü burada bir şey anlatmaz: ikiz zaten eşit çıkmadığı için ölçüt `farkli` değerine bakmadan `kimlik` olur. İkinci ölçüm daha öğreticidir: aynı `list` değişkeninden iki kez kurulan tembel diziler bile eşit çıkmaz, çünkü eşitlik kaynağın aynı olmasını aramaz.

**KE15.** Bu ölçümler `sequenceOf` ve `asSequence` ile kurulan tembel diziler için. Ara işlemlerle türetilmiş tembel dizilerin (`map`, `filter` sonuçları) kendi aralarındaki eşitliği ve `generateSequence` sonucunun eşitliği ayrıca ölçülmedi.

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

Peki iki tembel diziyi karşılaştırmanın bir yolu var mı? Var, ama karşılaştırma tembel dizi üzerinde değil, toplanmış hâl üzerinde yapılır. `toList()` tembel diziyi tüketir ve bir liste kurar; liste tipi `equals`'ı içeriğe göre tanımladığı için sonuç değişir. Aşağıdaki blok aynı iki tembel diziyi üç biçimde ölçer: olduğu gibi, `toList()` ile ve `toSet()` ile. Kümeli ölçümde `other` aynı elemanları ters sırada veren bir tembel dizidir.

```kotlin
// Main.kt — toplaninca icerik
fun main() {
    val checks = listOf(
        Criterion("tembel", sequenceOf(1, 2), sequenceOf(1, 2), sequenceOf(1, 3)),
        Criterion("toplanmis", sequenceOf(1, 2).toList(), sequenceOf(1, 2).toList(), sequenceOf(1, 3).toList()),
        Criterion("kume", sequenceOf(1, 2).toSet(), sequenceOf(1, 2).toSet(), sequenceOf(2, 1).toSet()),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
tembel: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
toplanmis: ayni-nesne=false, ikiz=true, farkli=false -> icerik
kume: ayni-nesne=false, ikiz=true, farkli=true -> kismi-icerik
tekil: 0
kimlik: 1
icerik: 1
kismi-icerik: 1
```

Sınırlayıcı ölçüm şudur: **eşitliği değer değil, değerin tutulduğu tip verir.** Aynı elemanlar tembel dizide `kimlik`, listede `icerik`, kümede `kismi-icerik` ölçütünü verir. Üçünde de elemanlar `1` ve `2` iken ölçütü değiştiren tek şey kabın tipidir. İlk satır önceki ölçümün tekrarıdır. İkinci satır `toList()` sonrasında ikizin eşit, farklı olanın eşit olmadığını gösterir. Üçüncü satır kümede sıranın fark yaratmadığını gösterir: `other` ters sırada olsa da eşit çıkar. Böylece ilk iki dersin ölçütleri bu derste yan yana görünür.

Pratik çıkarım şudur: iki tembel diziyi karşılaştırmak gerekiyorsa karşılaştırma öncesinde uygun bir koleksiyona toplanır. Hangi koleksiyona toplanacağı, hangi eşitliğin istendiğine bağlıdır: sıra önemliyse `toList()`, önemli değilse `toSet()`. Ayrıca toplama tembel diziyi tüketir; sonsuz bir tembel diziyi toplamadan önce sınırlamak gerekir.

**KE16.** Bu ölçümler `Int` elemanlı iki elemanlı tembel diziler için. Elemanları kendi eşitliğini tanımlamayan sınıflar olan tembel dizilerin toplanmış hâllerinin eşitliği ayrıca ölçülmedi.

#### İşlem Sırası Sonucu Değiştirir

Tembel dizide ara işlemlerin sırası yalnız işi değil, bazen sonucu da değiştirir. `filter` ve `take` ikilisi bunun en açık örneğidir. `filter { it % 2 == 0 }.take(2)` önce çift elemanları süzer, sonra ilk ikisini alır. `take(2).filter { it % 2 == 0 }` ise önce ilk iki elemanı alır, sonra yalnız onları süzer. Aşağıdaki blok altı elemanlı bir kaynağa iki sırayı da uygular ve her `filter` çağrısını kaydeder. Kayıtlar `filter`'a giren elemanları sırayla gösterir.

```kotlin
// Main.kt — filter ve take sirasi
fun main() {
    val firstLog = mutableListOf<Int>()
    val filterFirst = sequenceOf(1, 2, 3, 4, 5, 6)
        .filter { firstLog.add(it); it % 2 == 0 }
        .take(2)
        .toList()
    println(filterFirst)
    println(firstLog)

    val secondLog = mutableListOf<Int>()
    val takeFirst = sequenceOf(1, 2, 3, 4, 5, 6)
        .take(2)
        .filter { secondLog.add(it); it % 2 == 0 }
        .toList()
    println(takeFirst)
    println(secondLog)
}
```

```
[2, 4]
[1, 2, 3, 4]
[2]
[1, 2]
```

Önce süzüp sonra alan zincir iki çift sayı bulana kadar dolaştı: `filter` dört elemanı gördü ve `[2, 4]` verdi. Önce alıp sonra süzen zincirde `filter` yalnız ilk iki elemanı gördü ve tek eleman, `[2]`, kaldı. İki zincir aynı işlemlerden kuruldu; sonuçları farklıdır. Bu, ara işlemlerin birbirinin yerine geçebilir sayılmaması gerektiğini söyler.

**KE17.** Bu ölçümler `filter` ve `take` ikilisi için. `map` ile `take` sırasının, `drop` ve `takeWhile` gibi diğer sınırlayıcıların kayıt sıraları ayrıca ölçülmedi.

#### Sıralama Tembelliği Bozar

Her ara işlem elemanı tek tek geçirmez. `sorted()` bir tembel diziyi sıralamak için bütün elemanları görmek zorundadır: ilk sonuç elemanı ancak en küçüğü bulduktan sonra bilinir. Bu yüzden zincire `sorted()` girince, kalan işlemler tembel olsa bile, kaynak baştan sona dolaşılır. Ölçüm bunu `onEach` ile görünür kılar; `onEach` her elemanı geçirirken bir kayıt bırakır. İlk zincirde `sorted()` vardır, ikincisinde yoktur ve ikisi de `first()` ile biter.

```kotlin
// Main.kt — sorted tum elemanlari ister
fun main() {
    val sortedLog = mutableListOf<Int>()
    val smallest = sequenceOf(3, 1, 2, 5, 4)
        .onEach { sortedLog.add(it) }
        .sorted()
        .first()
    println(smallest)
    println(sortedLog)

    val plainLog = mutableListOf<Int>()
    val head = sequenceOf(3, 1, 2, 5, 4)
        .onEach { plainLog.add(it) }
        .first()
    println(head)
    println(plainLog)
}
```

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

`sorted()` olan zincir en küçük sayıyı, `1`'i buldu, ama bunun için beş elemanın hepsini gördü. Kayıt kaynağın tam sırasıdır. `sorted()` olmayan zincir ilk elemanı, `3`'ü aldı ve durdu; kayıtta yalnız o var. Kural budur: tembel dizinin kazancı, zincirdeki hiçbir adımın bütün elemanları beklememesine bağlıdır. `sorted`, `sortedBy` ve `sortedDescending` gibi işlemler bu zinciri keser. Sınırlayıcı bir işlem varsa onu sıralamadan önce koymak, sıralanacak elemanları azaltır.

**KE18.** Bu ölçümler `sorted()` ve `first()` için. `sortedBy`, `sortedDescending` ve `distinct` gibi diğer işlemlerin kaç elemanı beklediği ayrıca ölçülmedi.

#### Tek Dolaşımlık Dizi

Önceki bölümde `sequenceOf` ile kurulan dizinin iki kez toplanabildiği görüldü. Bu her tembel dizi için geçerli değildir. Bir `Iterator`'dan `asSequence()` ile kurulan dizi, yineleyicinin elemanlarını yalnız bir kez verebilir; ikinci dolaşımda aynı yineleyici zaten tükenmiştir. Kotlin bu dizileri tek dolaşımlık sayar ve ikinci uç işlemde `IllegalStateException` fırlatır. Hata, dizi iki yerde kullanıldığında ortaya çıkar; zinciri kuran kodda görünmez.

```kotlin
// Main.kt — tek dolasimlik dizi
fun main() {
    val once = listOf(1, 2, 3).iterator().asSequence()
    println(once.toList())
    try {
        println(once.toList())
    } catch (e: IllegalStateException) {
        println(e::class.simpleName)
    }
    val again = listOf(1, 2, 3).asSequence()
    println(again.toList())
    println(again.toList())
}
```

```
[1, 2, 3]
IllegalStateException
[1, 2, 3]
[1, 2, 3]
```

İlk toplama `[1, 2, 3]` verdi. İkinci toplama `IllegalStateException` fırlattı ve yakalandı. Aynı elemanlar bu kez `listOf(1, 2, 3).asSequence()` ile kurulduğunda iki toplama da aynı sonucu verdi: dizi kaynak listeyi her seferinde baştan dolaşır. Fark, kaynağın yeniden dolaşılabilir olup olmamasıdır. Bir tembel diziyi birden çok kez kullanacak kod, dizinin hangi kaynaktan kurulduğunu bilmeli; emin değilse ara sonucu `toList()` ile bir koleksiyona alıp onu kullanmalıdır.

**KE19.** Bu ölçümler `Iterator.asSequence()` ve `List.asSequence()` için. `constrainOnce()` ile bir diziyi bilerek tek dolaşımlık yapmak ve hata mesajının metni ölçülmedi.

#### Özet

- Tembel dizide ara işlemler uç işlem gelene kadar çalışmaz; uç işlem gelince her eleman bütün zincirden geçer. Liste her adımda bütün elemanları işler, tembel dizi gereksiz elemanı hiç işlemez.
- `generateSequence` ve `sequence { }` sonsuz kaynak kurar; sonlu sonuç `take` ya da `takeWhile` ile alınır. Sınırlayıcı unutulursa program bitmez.
- İki ayrı kurulmuş tembel dizi eşit değildir; `Sequence` `equals` tanımlamaz. Bu dersin ölçütü: **kimlik**.
- Sınırlayıcı ölçüm: aynı elemanlar `toList()` ile toplanınca `icerik`, `toSet()` ile toplanınca `kismi-icerik` verir; eşitliği değerin tuttuğu tip belirler.

#### Sonraki Adım

Bu ders tembel dizinin tarif olduğunu ve tarifin tek başına bir değer olmadığını gösterdi. Elemanların zamanla, başka bir iş beklenirken gelmesi gerektiğinde ise tarifin kendisi de beklemek zorundadır. Bir sonraki ders bu bekleyen hesabı eş yordamlarla (coroutine) kurar ve yine eşitlik sorusunu sorar: aynı sonucu verecek iki bekleyen iş, birbirine eşit midir?

### Eş Yordamlar

Source: https://academia.sh/tr/kurslar/kotlin/es-yordamlar

Eş yordamın askıya alındığı noktada sıra verdiğinin, sonucun await ile alındığının ve iki ayrı başlatılmış işin eşit sayılmadığının ölçümü.

Önceki derste tembel dizi, bir hesabın tarifini taşıyordu ve hesap uç işlem gelince çalışıyordu. Bazı hesaplar ise başka bir işin bitmesini bekler: bir dosyanın okunması, bir ağ yanıtı, bir zamanlayıcı. Bu bekleme sırasında çağıranı tutmak istenmez. Kotlin bu durum için **eş yordam (coroutine)** sunar: bir iş yarıda durabilir, kontrolü bırakabilir ve sonra kaldığı yerden devam edebilir.

Eş yordam kullanan kod `kotlinx.coroutines` eş yordam kitaplığını içe alır. Bu kitaplık dilin kendisinde değil, ayrı bir pakettedir; bu derste ve bir sonrakinde `import kotlinx.coroutines.*` satırı gerekir, önceki derslerde gerekmiyordu. Bu derste üç yapı yeter. `suspend` ile işaretlenmiş **askıya alınan fonksiyon**, gövdesinde yarıda durabilen fonksiyondur ve yalnız bir eş yordamdan ya da başka bir askıya alınan fonksiyondan çağrılır. `launch` yeni bir eş yordam başlatır ve bir `Job` döndürür; sonuç beklenmez, yalnız bitişi `join()` ile beklenir. `async` yeni bir eş yordam başlatır ve sonucu taşıyan bir `Deferred` döndürür; sonuç `await()` ile alınır. Üçünü de bir eş yordam kapsamı içinde çalıştırmak gerekir, bu derste kapsamı `runBlocking` kurar: `main` fonksiyonunu çağıran iş parçacığını, içindeki bütün eş yordamlar bitene kadar bekletir.

Eş yordam bir iş parçacığı değildir. Bir iş parçacığı işletim sisteminin sıraladığı bir yürütme birimidir; eş yordam ise kitaplığın bir **dağıtıcı (`Dispatchers`)** üzerinde yürüttüğü iştir ve aynı iş parçacığında sırayla yürüyebilir. Bu derste hiçbir ölçüm süreye ya da iş parçacığı adına dayanmaz: zamanlama çıktıdan okunmaz, yalnız işlerin sırası ve sonuçları okunur. Ölçü sorusu önceki derslerdeki gibi kalır: **bu iki değeri eşit yapan ne?**

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

#### Sıra: launch, join, async, await

Aşağıdaki blok `doubled` adında bir askıya alınan fonksiyon tanımlar; içinde `yield()` çağrısı eş yordamın sırayı başkasına verebileceği bir nokta kurar. `main` üç şeyi ölçer. İlk olarak `launch` bloğunun, `launch` çağrısını izleyen satırdan önce mi sonra mı çalıştığı: ikisi de aynı listeye kayıt ekler. İkinci olarak iki `async` işinin sonuçlarının `await()` ile toplanması. Üçüncü olarak `coroutineScope` içinde başlatılan üç işin sonuçlarının sırayla alınması.

```kotlin
// Main.kt — launch, async ve await
import kotlinx.coroutines.*

suspend fun doubled(n: Int): Int {
    yield()
    return n * 2
}

fun main() = runBlocking {
    val log = mutableListOf<String>()
    val job = launch { log.add("launch") }
    log.add("main")
    job.join()
    println(log)
    val first = async { doubled(21) }
    val second = async { doubled(4) }
    println(first.await() + second.await())
    val total = coroutineScope {
        val parts = listOf(1, 2, 3).map { async { doubled(it) } }
        parts.map { it.await() }
    }
    println(total)
}
```

```
[main, launch]
50
[2, 4, 6]
```

İlk satır önemli bir sonucu gösterir: `launch` bloğu, `launch` çağrısı döner dönmez çalışmadı. Önce `main` kaydı eklendi, `launch` kaydı ondan sonra geldi. `runBlocking` tek bir iş parçacığında çalıştığı için yeni eş yordam, başlatan eş yordam bir askıya alma noktasına gelene kadar sırada bekler; burada bu nokta `job.join()` çağrısıdır. Sıranın böyle çıkması dağıtıcıya bağlıdır; bu yüzden ders bu sırayı değil, `join()` çağrısından sonra her iki kaydın da listede olduğunu güvence sayar.

İkinci satırda iki `async` işi `21 * 2` ve `4 * 2` sonuçlarını üretir; `await()` bunları alır ve toplam `50` çıkar. Üçüncü satırda `coroutineScope`, içinde başlatılan bütün işler bitmeden dönmez; üç `async`'in sonuçları `await()` ile listede başlatılış sırasında alınır: `[2, 4, 6]`. Sonuçların sırası başlatma sırasını izler, çünkü `parts` listesi `await()` çağrılarını o sırayla yapar. İşlerin hangisinin önce bittiği ise bu çıktıdan okunamaz ve okunmaya çalışılmaz.

**KE20.** Bu ölçümler `runBlocking` kapsamı ve tek bir `Int` sonuç tipi için. Başka bir dağıtıcıda `launch` ile `main` kaydının sırası, askıya alma noktası olmayan bir fonksiyonda sıra ve işler arasındaki gerçek bitiş sırası ölçülmedi.

#### Ölçüt: Kimlik

`async` bir sonuç değil, sonucun sözünü döndürür. Bu söz bir `Deferred` nesnesidir ve her `async` çağrısı yeni bir tane kurar. Aşağıdaki blok iki ayrı `async { 1000 }` çağrısını ve değeri farklı üçüncüsünü karşılaştırır. İkinci ölçüm sabit küçük bir değerle aynı soruyu sorar. İkisinde de bloklar, sorunun bir sonuç değil bir `Deferred` olduğunu görmek için `await()` çağırmaz.

```kotlin
// Main.kt — iki Deferred
import kotlinx.coroutines.*

fun main() = runBlocking {
    val checks = listOf(
        Criterion("async-1000", async { 1000 }, async { 1000 }, async { 2000 }),
        Criterion("async-1", async { 1 }, async { 1 }, async { 2 }),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
async-1000: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
async-1: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
tekil: 0
kimlik: 2
icerik: 0
kismi-icerik: 0
```

**KE21.** Bu ölçümler `async` ile kurulan `Deferred` nesneleri için. `launch` ile kurulan `Job` nesnelerinin ve `CompletableDeferred` gibi doğrudan kurulan sözlerin eşitliği ölçülmedi.

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

Sonucu almak eşitliği değiştirir. Aşağıdaki blok üç şeyi yan yana ölçer: `Deferred` nesnelerinin kendisini, onların `await()` ile alınmış sonuçlarını ve aynı eş yordamın bağlamına konabilen iki `CoroutineName("a")` öğesini. Sonuçlar için `1000` seçildi; küçük tam sayılar kutulama sırasında paylaşılabilir ve `tekil` çıkabilir, `1000` için bu ayrıntıya bağlı kalınmaz, sonuç sandbox'ın bastığı olur.

```kotlin
// Main.kt — sonuc ve baglam ogesi
import kotlinx.coroutines.*

fun main() = runBlocking {
    val a = async { 1000 }
    val b = async { 1000 }
    val c = async { 2000 }
    val checks = listOf(
        Criterion("deferred", a, b, c),
        Criterion("sonuc", a.await(), b.await(), c.await()),
        Criterion("ad", CoroutineName("a"), CoroutineName("a"), CoroutineName("b")),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
deferred: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
sonuc: ayni-nesne=false, ikiz=true, farkli=false -> icerik
ad: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 0
kimlik: 1
icerik: 2
kismi-icerik: 0
```

Sınırlayıcı ölçüm şudur: **eşitliği iş değil, işin sonucunu taşıyan tip belirler.** `Deferred` kimlikle karşılaştırılır; `await()` ile alınan `Int` ise içerikle. Aynı `a` ve `b` işleri, bir kez nesne olarak, bir kez sonuç olarak ölçüldü ve ölçüt `kimlik`ten `icerik`e döndü. `CoroutineName` de içerikle karşılaştırılır: aynı adı taşıyan iki öğe eşittir, farklı adlı olan eşit değildir. Bu, eş yordam bağlamındaki bir öğenin kimlik değil değer taşıdığını gösterir.

Pratik çıkarım: iki işi karşılaştırmak gerekiyorsa `Deferred`'ları değil sonuçları karşılaştırmak gerekir; bunun için önce `await()` çağırmak gerekir. Bu çağrı da yalnız bir eş yordam içinde yapılabilir; `await()` bir askıya alınan fonksiyondur.

**KE22.** Bu ölçümler `Int` sonuçları ve `CoroutineName` öğeleri için. Başka sonuç tiplerinin eşitliği, `Dispatchers` öğelerinin eşitliği ve birden çok bağlam öğesinin birleşiminin eşitliği ölçülmedi.

#### Tembel Başlatma ve İptal

`async` ve `launch` varsayılan olarak hemen sıraya girer. `start = CoroutineStart.LAZY` verilirse eş yordam kurulur ama başlatılmaz: gövdesi, `await()`, `join()` ya da `start()` çağrılana kadar hiç çalışmaz. Bu, önceki dersteki tembel dizinin fikrine benzer, ama burada tembel olan bir hesabın sonucu değil, bir işin başlangıcıdır. İptal ise başlamamış bir işi hiç çalışmadan sonlandırabilir. Aşağıdaki blok önce tembel bir `async` kurar ve `await()` öncesinde, sonrasında ona ve listeye bakar. Sonra tembel bir `launch` kurar, iptal eder ve ancak ondan sonra bekler. Sıralar yalnız `await()` ve `join()` çağrılarının güvence verdiği noktalardan okunur.

```kotlin
// Main.kt — tembel baslatma ve iptal
import kotlinx.coroutines.*

fun main() = runBlocking {
    val log = mutableListOf<String>()
    val lazy = async(start = CoroutineStart.LAZY) { log.add("govde"); 7 }
    println(log.size)
    println(lazy.isActive)
    println(lazy.await())
    println(log)
    println(lazy.isCompleted)
    val cancelled = launch(start = CoroutineStart.LAZY) { log.add("kosmaz") }
    cancelled.cancel()
    cancelled.join()
    println(cancelled.isCancelled)
    println(log)
}
```

```
0
false
7
[govde]
true
true
[govde]
```

Tembel `async` kurulduğunda liste boştu ve iş etkin değildi: gövde `await()` çağrısına kadar hiç çalışmadı. `await()` gövdeyi başlattı, sonucu `7` olarak verdi ve listeye `govde` kaydı düştü. Ardından iş tamamlanmış sayıldı. Tembel `launch` ise iptal edildiği için gövdesine hiç girmedi: son satırda `kosmaz` kaydı yoktur, ama iş `isCancelled` olarak işaretlendi. Sonuç şudur: bir eş yordamın başlatılmış olması ile bitirilmiş olması farklı şeylerdir ve `Job` bu durumu sorgulatır. Sonucu hiç kullanılmayacak bir işi iptal etmek, onun gövdesinin yan etkilerini de engelleyebilir.

**KE23.** Bu ölçümler `CoroutineStart.LAZY` ile kurulmuş, `runBlocking` kapsamındaki işler için. Çalışmakta olan bir işin ortasında iptalin etkisi ve `start()` ile başlatmanın sırası ölçülmedi.

#### Bütün Çocukları Beklemek

Birden çok `launch` başlatıldığında hepsinin bitişini ayrı ayrı beklemek zorunlu değildir. `coroutineScope { }` bloğu, içinde başlatılan bütün işler bitmeden dönmez; `joinAll()` ise verilen `Job` listesindeki hepsini bekler. İki yolun da güvencesi aynıdır: dönüşten sonra işlerin yan etkileri görünürdür. Ama işlerin kendi arasındaki sırası bu güvencenin parçası değildir. Bu yüzden aşağıdaki blok sıralamayı değil, sonucu ölçer: dönüşten sonra listeyi sıralar ve kaç eleman bulunduğunu sayar.

```kotlin
// Main.kt — coroutineScope ve joinAll
import kotlinx.coroutines.*

fun main() = runBlocking {
    val log = mutableListOf<Int>()
    coroutineScope {
        for (i in 1..3) {
            launch {
                yield()
                log.add(i)
            }
        }
    }
    println(log.sorted())
    val jobs = List(4) { i ->
        launch {
            yield()
            log.add(i * 10)
        }
    }
    jobs.joinAll()
    println(log.size)
    println(jobs.all { it.isCompleted })
}
```

```
[1, 2, 3]
7
true
```

`coroutineScope` bloğu döndüğünde üç kayıt da listedeydi. Ölçüm sıralanmış listeyi bastı, çünkü işlerin hangi sırayla bittiği güvenceli değildir. İkinci kümede dört iş `joinAll()` ile beklendi ve listede toplam yedi eleman oldu; hepsi tamamlanmış göründü. Güvence ve sıra ayrımı pratikte önemlidir: bir sonucu beklemek için kapsam ya da `join` kullanılır, bitiş sırasına dayanan bir mantık ise yanlış kurulmuş olur.

**KE24.** Bu ölçümler `launch` işleri ve `Int` listesi için. Bir çocuk işin istisna fırlatması durumunda kapsamın ve kardeş işlerin davranışı bu derste ölçülmedi.

#### Bağlam ve Ad

Her eş yordam bir bağlam taşır ve bağlam öğeleri anahtarla okunur. `coroutineContext[CoroutineName]` varsa adı verir, yoksa `null` döner. `withContext(CoroutineName("worker")) { }` bloğu içinde bağlam yeni adı taşır ve blok bitince eski bağlam geri gelir. `async` ya da `launch` çağrısına bir bağlam öğesi verilirse, çocuk iş onu taşır. Aşağıdaki blok adı okuyan küçük bir askıya alınan fonksiyon tanımlar ve onu dört yerden çağırır. Ad bir iş parçacığı adı değildir; yalnız bağlama konmuş bir etikettir, bu yüzden ölçülebilir.

```kotlin
// Main.kt — baglamdan ad okumak
import kotlinx.coroutines.*
import kotlin.coroutines.coroutineContext

suspend fun currentName(): String? = coroutineContext[CoroutineName]?.name

fun main() = runBlocking {
    println(currentName())
    println(withContext(CoroutineName("worker")) { currentName() })
    println(currentName())
    val named = async(CoroutineName("child")) { currentName() }
    println(named.await())
}
```

```
null
worker
null
child
```

İlk çağrıda `runBlocking` bağlamında ad yoktu ve sonuç `null`. `withContext` bloğunda ad `worker` oldu. Blok bittikten sonra ad yine `null`: bağlam değişikliği bloğa özeldi. Son satırda çocuk `async` kendi adı olan `child` değerini okudu. Bağlam bu yüzden eş yordamlar arasında bilgi taşımanın temiz bir yoludur: parametre geçirmeden, ama kapsamı belli bir biçimde.

**KE25.** Bu ölçümler `CoroutineName` öğesi ve `runBlocking` kapsamı için. `Job` öğesinin ve `Dispatchers` öğesinin bağlamdan okunması, çocuğun üst bağlamdan öğe devralışı ve iki öğenin `+` ile birleşimi ölçülmedi.

#### İş Durumları ve Tekrarlı await

Her `launch` ve `async` bir `Job` taşır ve `Job` işin hangi aşamada olduğunu üç özellikle söyler: `isActive` işin sürdüğünü, `isCompleted` bittiğini, `isCancelled` iptal edildiğini gösterir. Bir işin bittiğini `join()` ya da `await()` döndükten sonra söylemek güvencelidir; başlatma ile ilk askıya alma noktası arasındaki ara durumlar ise dağıtıcıya bağlıdır ve bu derste ölçülmez. `await()` ayrıca birden çok kez çağrılabilir: sonuç bir kez hesaplanır ve sonraki çağrılar aynı değeri verir. Aşağıdaki blok önce `join()` sonrasındaki durumu, sonra aynı `Deferred` üzerinde iki `await()` çağrısını ve gövdenin kaç kez koştuğunu ölçer.

```kotlin
// Main.kt — is durumlari ve tekrarli await
import kotlinx.coroutines.*

fun main() = runBlocking {
    val job = launch { yield() }
    job.join()
    println(job.isActive)
    println(job.isCompleted)
    println(job.isCancelled)
    var runs = 0
    val deferred = async { runs++; "sonuc" }
    println(deferred.await())
    println(deferred.await())
    println(runs)
}
```

```
false
true
false
sonuc
sonuc
1
```

`join()` döndükten sonra iş artık etkin değildi, tamamlanmıştı ve iptal edilmemişti. `Deferred` iki kez beklendi ve iki seferde de aynı değeri verdi; sayaç ise bir kez arttı, yani gövde yalnız bir kez koştu. `await()` işi yeniden başlatmaz, sonucu okur. Bunun bir anlamı da şudur: `Deferred` sonucu bir kez hesaplandıktan sonra saklar ve her `await()` o saklanan değeri okur. Bu, akıştaki durumun tersidir: soğuk akış her toplamada yeniden çalışır, `Deferred` ise tek hesabın sonucunu paylaşır.

**KE26.** Bu ölçümler `join()` ve `await()` sonrası durumlar için. Başlatma ile ilk askıya alma arasındaki `isActive` değeri ve hatayla biten işin durumu ölçülmedi.

#### Özet

- `suspend` ile işaretlenen fonksiyon yalnız bir eş yordamdan ya da başka bir askıya alınan fonksiyondan çağrılır; `launch` sonuçsuz iş, `async` sonuçlu iş başlatır.
- `launch` çağrısı hemen çalışmaz; `runBlocking` içinde başlatan eş yordam askıya alma noktasına gelene kadar bekler. `join()` ve `await()` işin bittiğini güvence eder.
- İki ayrı `async` çağrısının `Deferred` nesneleri eşit değildir. Bu dersin ölçütü: **kimlik**.
- Sınırlayıcı ölçüm: aynı işlerin `await()` sonuçları `icerik` ölçütünü verir; `CoroutineName` öğeleri de içerikle karşılaştırılır.

#### Sonraki Adım

Bu ders tek bir değer üreten işleri ele aldı. Bir iş tek değer yerine zamanla değer akıtabilir. Bir sonraki ders bu durumu akışlarla (flow) kurar ve eşitlik sorusunu orada da sorar: aynı değerleri akıtan iki akış eşit midir ve art arda gelen eşit değerler akışta nasıl ele alınır?

### Akışlar

Source: https://academia.sh/tr/kurslar/kotlin/akislar

Soğuk akışın her toplamada yeniden çalıştığının, art arda gelen eşit değerlerin içerikle elendiğinin ve iki akışın eşit sayılmadığının ölçümü.

Önceki ders tek bir değer üreten işleri ele aldı. Çoğu iş ise tek değer değil, zamanla gelen bir değer dizisi üretir: bir sensörden okumalar, bir listenin sayfaları, bir sayacın her adımı. Tembel dizi bu dizileri bekleyemez; elemanı üretirken çağıranı bloke eder. **Akış (`Flow`)** aynı fikri eş yordamlarla kurar: değerleri bir bir üretir ve üretici ile toplayıcı arada askıya alınabilir.

Akışın iki yüzü vardır. **Soğuk akış (cold flow)** `flow { }` ya da `flowOf` ile kurulur ve kendi başına hiçbir şey yapmaz; biri toplayana kadar bekler. Her toplayıcı için üretici blok baştan çalışır. **Sıcak akış (hot flow)** ise toplayıcıdan bağımsız bir durumu taşır; bu derste yalnız `MutableStateFlow` görülür, elde tutulan son değeri olan bir sıcak akıştır. Toplama işlemi için `collect` kullanılır; akışın bütün değerlerini bir listeye indirmek için `toList()` yeterlidir. Her ikisi de askıya alınan fonksiyondur ve bir eş yordam içinde çağrılır; bu derste yine `runBlocking` kapsamı kurulur ve `import kotlinx.coroutines.*` ile birlikte akışlar için `import kotlinx.coroutines.flow.*` gerekir. Zamanlama ölçülmez, yalnız değerler ve sayılar okunur. Ölçü sorusu önceki derslerdeki gibidir: **bu iki değeri eşit yapan ne?**

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

#### Soğuk Akış, Dönüşüm ve Sıcak Akış

Aşağıdaki blok önce `flow { }` ile bir akış kurar; üretici blok her çalıştığında bir sayacı artırır. Akış iki kez toplanır ve sayaç okunur. Sonra `map` ve `filter` ile dönüşüm ve `toList()` ile toplama gösterilir. Son olarak bir `MutableStateFlow` kurulur, değeri değiştirilir ve `value` ile okunur.

```kotlin
// Main.kt — soguk akis ve sicak akis
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    var runs = 0
    val numbers = flow {
        runs++
        emit(1)
        emit(2)
        emit(3)
    }
    println(runs)
    println(numbers.toList())
    println(numbers.toList())
    println(runs)
    println(flowOf(1, 2, 3, 4).map { it * 10 }.filter { it > 10 }.toList())
    val state = MutableStateFlow(5)
    state.value = 6
    println(state.value)
}
```

```
0
[1, 2, 3]
[1, 2, 3]
2
[20, 30, 40]
6
```

İlk satır, akış kurulduğunda üretici bloğun hiç çalışmadığını gösterir: `runs` sıfırdır. İki `toList()` çağrısı her seferinde `[1, 2, 3]` verir ve dördüncü satır üretici bloğun **iki kez** çalıştığını söyler. Bu soğuk akışın tanımıdır: her toplayıcı akışı baştan çalıştırır, akış önceki toplamadan bir şey hatırlamaz. Beşinci satırda `map` her değeri on ile çarpar, `filter` on'u eler ve `[20, 30, 40]` kalır; tembel dizideki gibi, ara işlemler akışı yalnız tarif eder ve toplama anında çalışır. Son satırda `MutableStateFlow` bir değer tutar ve `value` ile atanan son değer okunur: `6`.

**KE27.** Bu ölçümler tek toplayıcılı akışlar ve tek iş parçacıklı `runBlocking` kapsamı için. Birden çok toplayıcının aynı anda toplaması, `flowOn` ile dağıtıcı değiştirme ve akışın dışarıdan `cancel` ile iptali ölçülmedi; `take` ile erken kesme ilerideki bir bölümde ölçülüyor.

#### Ölçüt: İçerik

`MutableStateFlow` ve `distinctUntilChanged` aynı soruyu sorar: yeni gelen değer öncekine eşit mi? Eşitse atlarlar. Bu soru bu kursun ölçü sorusudur, yani akışta eşitliği bir ölçüt belirler. Aşağıda önce iki yardımcı sınıf tanımlanır: `Reading` bir veri sınıfıdır, `Raw` ise aynı alana sahip sıradan sınıftır.

```kotlin
// Reading.kt — veri sinifi
data class Reading(val id: Int)
```

```kotlin
// Raw.kt — siradan sinif
class Raw(val id: Int)
```

Aşağıdaki blok `Reading` öğeli `flowOf(A, A, B, B, A)` akışına `distinctUntilChanged()` uygular ve kaç değerin kaldığını sayar. Hemen yanında `Reading` için `Criterion` ölçümü yapılır.

```kotlin
// Main.kt — art arda eslik
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val a = Reading(1)
    val b = Reading(2)
    val kept = flowOf(a, Reading(1), b, Reading(2), Reading(1)).distinctUntilChanged().toList()
    println(kept.size)
    Criterion("veri-sinifi", Reading(1), Reading(1), Reading(2)).report()
}
```

```
3
veri-sinifi: ayni-nesne=false, ikiz=true, farkli=false -> icerik
```

Beş değerden üçü kaldı: `A, B, A`. Art arda gelen ikinci `Reading(1)` ve ikinci `Reading(2)` elendi, çünkü `distinctUntilChanged` öncekiyle `==` karşılaştırır ve veri sınıfı `equals`'ı alanlara göre tanımlar. Ayrı bir nesne olan `Reading(1)`, öncekine eşit sayıldı. İkinci satır aynı sonucu `Criterion` ile söyler: ikiz eşittir, farklı olan eşit değildir, ölçüt **içerik**. Bu dersin ölçütü budur: **içerik**.

**KE28.** Bu ölçümler `Int` alanlı bir veri sınıfı ve beş öğeli bir `flowOf` için. Anahtar seçen `distinctUntilChangedBy`, karşılaştırıcı alan `distinctUntilChanged { a, b -> ... }` biçimi ve uzun akışlarda kaç değerin elenebileceği ölçülmedi.

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

Aynı akışı sıradan sınıf öğeleriyle, `MutableStateFlow`'a eşit değer atamayı ve iki ayrı akışın kendisini ölçen blok:

```kotlin
// Main.kt — siradan sinif, durum ve akis
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val raws = flowOf(Raw(1), Raw(1), Raw(2), Raw(2), Raw(1)).distinctUntilChanged().toList()
    println(raws.size)
    val original = Reading(1)
    val state = MutableStateFlow(original)
    state.value = Reading(1)
    println(state.value === original)
    val checks = listOf(
        Criterion("akis", flowOf(1, 2), flowOf(1, 2), flowOf(1, 3)),
        Criterion("veri-sinifi", Reading(1), Reading(1), Reading(2)),
        Criterion("siradan-sinif", Raw(1), Raw(1), Raw(2)),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
5
true
akis: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
veri-sinifi: ayni-nesne=false, ikiz=true, farkli=false -> icerik
siradan-sinif: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
tekil: 0
kimlik: 2
icerik: 1
kismi-icerik: 0
```

Birinci satır, aynı `distinctUntilChanged` çağrısının sıradan sınıf öğelerde hiçbir değeri elemediğini gösterir: beş öğenin beşi de kaldı. `Raw(1)` ile `Raw(1)` içerik olarak aynı alanı taşısa da `equals` varsayılan kimliğe düşer ve ardışık değerler hiç eşit sayılmaz. İkinci satırda `MutableStateFlow`'a `Reading(1)` ile eşit ama ayrı bir nesne atandı ve `value` hâlâ ilk nesneye işaret ediyor (`true`): akış eşit değeri yeni değer saymadı ve eskisini yerinde bıraktı. Sıcak akış da soğuk akış gibi eşitliği `==` ile ölçer.

Kalan satırlar üç ölçümü yan yana koyar. İki ayrı `flowOf(1, 2)` akışı **kimlik** ölçütünü verir: akışın kendisi eşitliği içerikle tanımlamaz, tıpkı tembel dizi ve `Deferred` gibi. Veri sınıfı `icerik`, sıradan sınıf `kimlik` çıkar. Sınırlayıcı ölçüm şudur: **akışın içindeki değerler içerik ölçütünü yalnız öğenin tipi taşıyorsa uygular.** Akışı kuran tarif kimlikle, akışın içinden geçen değer ise tipinin eşitliğiyle karşılaştırılır. Özet tablosu da bunu söyler: iki `kimlik` (akış ve sıradan sınıf), bir `icerik` (veri sınıfı).

**KE29.** Bu ölçümler `MutableStateFlow` için ve eşit değerin `value` ile atanmasını kapsar. `StateFlow`'un toplayıcıya ilettiği değerler ve `SharedFlow` ölçülmedi; `update` ve `compareAndSet` ile atamalar ilerideki bir bölümde ölçülüyor.

**KE30.** Bu derste hiçbir zamanlama, iş parçacığı adı ya da dağıtıcı sıralaması basılmadı. Akışın değerleri `toList()` ile toplandı, çıktıdaki sıra üreticinin sırasıdır.

#### Akışı Erken Kesmek

Bir akışın üreticisi çok değer üretebilir, ama toplayıcı hepsini istemeyebilir. `take(n)` akıştan ilk `n` değeri alır ve sonra akışı iptal eder; üretici blok o noktadan sonra devam etmez. Tembel dizideki `take` ile aynı fikirdir, ama burada kesilen şey bir eş yordamdır. Aşağıdaki blok beş değer üretmeye hazır bir akış kurar. Üretici her değerden önce bir kayıt bırakır, böylece `take(2)` sonrasında üreticinin ne kadar ilerlediği listeden okunur. Ayrıca `onStart` ve `onCompletion` ile akışın başlangıcı ve bitişi kaydedilir.

```kotlin
// Main.kt — take akisi erken keser
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val log = mutableListOf<String>()
    val source = flow {
        for (i in 1..5) {
            log.add("uret$i")
            emit(i)
        }
    }
    val firstTwo = source
        .onStart { log.add("basla") }
        .onCompletion { log.add("bitti") }
        .take(2)
        .toList()
    println(firstTwo)
    println(log)
}
```

```
[1, 2]
[basla, uret1, uret2, bitti]
```

Toplanan liste `[1, 2]` oldu, yani yalnız iki değer içerir. Kayıt listesi `basla` ile açılır, `uret1` ve `uret2` ile sürer, `bitti` ile kapanır; `uret3` kaydı hiç yoktur. Kayıtlar üreticinin üçüncü değere hiç uzanmadığını gösterir: `take(2)` ikinci değeri aldıktan sonra akışı kesti. `onStart` ve `onCompletion` kayıtları da listede yer alır ve bitiş kaydı kesilmiş akışta da düşer. Bu sıra güvencelidir, çünkü `toList()` döndüğünde akış tamamlanmıştır; kayıtlar bu noktadan sonra okunur. Pratik sonuç şudur: bir akışın ilk birkaç değeri yetiyorsa `take` ya da `first()` kullanmak, üretici bloğun geri kalanını çalıştırmaz. Üreticinin yan etkisi olan bir iş yaptığı yerde bu fark, çalışan ya da çalışmayan işin farkıdır.

**KE31.** Bu ölçümler `take`, `onStart` ve `onCompletion` için, tek toplayıcıyla. Akışın kesilmesi sırasında üretici bloktaki `try/finally` temizliği ve `takeWhile` ile kesme ölçülmedi.

#### Uç İşlemler ve Boş Akış

Akışı bir listeye çevirmek tek yol değildir. `fold` ve `reduce` bütün değerleri tek bir değere indirger, `count` değer sayısını verir, `first()` ilk değeri döndürür. Boş akış sınır durumudur: `first()` istisna fırlatır, `firstOrNull()` ise `null` verir. Aşağıdaki blok dört değerli bir akışta bu işlemleri çağırır; sonra boş bir akışta ikisini dener. `fold` bir başlangıç değeri ister; `reduce` ister istemez ilk değeri başlangıç sayar ve boş akışta çalışamaz.

```kotlin
// Main.kt — uc islemler ve bos akis
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val numbers = flowOf(1, 2, 3, 4)
    println(numbers.fold(100) { sum, n -> sum + n })
    println(numbers.reduce { sum, n -> sum + n })
    println(numbers.count { it % 2 == 0 })
    println(numbers.first())
    val empty = emptyFlow<Int>()
    try {
        empty.first()
    } catch (e: NoSuchElementException) {
        println(e::class.simpleName)
    }
    println(empty.firstOrNull())
}
```

```
110
10
2
1
NoSuchElementException
null
```

`fold` başlangıç değeri olan `100`'e dört değeri ekledi. `reduce` başlangıç değeri olmadan ilk değerden başladı ve yalnız dört değerin toplamını verdi. `count` yalnız çift olanları saydı. `first()` akışın ilk değeridir. Boş akışta `first()` `NoSuchElementException` fırlattı, `firstOrNull()` ise `null` döndürdü. Koleksiyonlardaki `first` ve `firstOrNull` ikilisinin akıştaki karşılığı budur: hata görünür olsun isteniyorsa ilki, boşluk kabul edilebilirse ikincisi seçilir.

**KE32.** Bu ölçümler sonlu, `Int` değerli akışlar için. Hiç bitmeyen bir akışta `fold` ile `count` çağrısı ve `reduce`'un boş akıştaki istisnasının sınıf adı ölçülmedi.

#### Dönüştürmek ve Birleştirmek

`map` her değerden bir değer üretir. Bir değerden birden çok değer ya da hiç değer üretmek için `transform` kullanılır: blok gelen değeri alır ve istediği kadar `emit` çağırır. İki akışı yan yana okumak için `zip` iki akıştan sırayla birer değer alıp birleştirir; kısa olan akış bitince sonuç da biter. Aşağıdaki blok her ikisini de ölçer. İkinci akış üç yerine iki değer taşır.

```kotlin
// Main.kt — transform ve zip
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val doubled = flowOf(1, 2, 3).transform { n ->
        emit(n)
        if (n % 2 == 1) emit(n * 10)
    }.toList()
    println(doubled)
    val pairs = flowOf(1, 2, 3).zip(flowOf("a", "b")) { n, s -> "$n$s" }.toList()
    println(pairs)
}
```

```
[1, 10, 2, 3, 30]
[1a, 2b]
```

`transform` tek sayılar için iki değer üretti, çift sayı için tek değer üretti. Sonuç listesi giriş değerlerini ve türeyen değerleri art arda taşır. `zip` iki akıştan birer değer çiftleyip `"1a"` ve `"2b"` üretti; birinci akışın üçüncü değeri eşsiz kaldığı için hiçbir sonuç üretmedi. Bu iki işlemin sonucu zamanlamaya değil, yalnız giriş değerlerine ve sırasına bağlıdır, bu yüzden güvenle ölçülebilir. Kural açık: `map` bire bir, `transform` bire çok ya da bire sıfır dönüşüm yapar; `zip` ise kısa olanın uzunluğunda durur.

**KE33.** Bu ölçümler `transform` ve `zip` için, iki sonlu akışla. İki akışın zaman içinde değişen son değerlerini birleştiren `combine` ve `flatMapConcat` ölçülmedi; bunların sonucu zamanlamaya dayanabilir.

#### Durumu Güncellemek

`MutableStateFlow`'a değer atamak için `value` yeterlidir, ama okuma ile atama arasındaki boşluk güvenli değildir. `update { }` bloğu yeni değeri eskisinden hesaplar ve atamayı tek adımda yapar. `compareAndSet(beklenen, yeni)` ise yalnız mevcut değer beklenene eşitse atar ve atayıp atamadığını döndürür. Karşılaştırma `==` iledir, bu yüzden bu iki işlem de önceki bölümlerdeki eşitlik kuralına bağlıdır. Aşağıdaki blok bir sayıyı güncelleyip denetler.

```kotlin
// Main.kt — update ve compareAndSet
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val state = MutableStateFlow(0)
    state.update { it + 1 }
    println(state.value)
    println(state.compareAndSet(1, 5))
    println(state.value)
    println(state.compareAndSet(1, 9))
    println(state.value)
    println(state.first())
}
```

```
1
true
5
false
5
5
```

`update` değeri `0`'dan `1`'e çıkardı. İlk `compareAndSet(1, 5)` mevcut değer `1` olduğu için atadı ve `true` döndürdü. İkinci `compareAndSet(1, 9)` değer artık `5` olduğundan atamadı ve `false` döndürdü, değer `5` olarak kaldı. `first()` sıcak akışın o anki değerini verdi: durum akışı toplanınca hep mevcut değerle başlar. Bu, `update` ve `compareAndSet` işlemlerini sıradan bir `value` atamasından ayırır: ikisi de mevcut değeri okuyup karar verir ve bunu tek işlemde yapar. Soğuk akıştan farkı budur: sıcak akışın elinde bir değer vardır ve yeni toplayıcıya önce onu verir.

**KE34.** Bu ölçümler `Int` değerli bir `MutableStateFlow` ve tek eş yordam için. Birden çok eş yordamın aynı anda `update` çağırması ve `StateFlow` toplayıcısının ara değerleri görüp görmemesi ölçülmedi.

#### Özet

- Soğuk akış her toplamada üretici bloğu baştan çalıştırır; kurulduğunda hiçbir şey çalışmaz. `map` ve `filter` akışı dönüştürür, `toList()` toplar.
- `MutableStateFlow` son değeri tutan bir sıcak akıştır; eşit değer atanınca eski nesne yerinde kalır.
- `distinctUntilChanged` art arda gelen eşit değerleri `==` ile eler. Bu dersin ölçütü: **içerik**. Veri sınıfında 5 değerden 3'ü, sıradan sınıfta 5'i kalır.
- Sınırlayıcı ölçüm: akışın kendisi kimlikle, içindeki değerler ise tipinin eşitliğiyle karşılaştırılır.

#### Sonraki Adım

Şimdiye kadar her ders değerin eşit olup olmadığını sordu. Son ders, değer yerine bir hatanın eşit olup olmadığını sorar: aynı mesajı taşıyan iki istisna eşit midir? İstisna Yönetimi dersi bunu try, catch ve finally ile ölçer.

### İstisna Yönetimi

Source: https://academia.sh/tr/kurslar/kotlin/istisna-yonetimi

Denetlenmeyen istisna modelinin, try ifadesinin, Result'ın ve eş yordamdaki yayılmanın ölçümü; aynı mesajlı iki istisnanın eşit sayılmadığının gösterilmesi ve kursun kapanışı.

Önceki ders iki akışın eşit sayılmadığını ölçtü. Bu son ders değer yerine bir hatanın eşit olup olmadığını sorar: aynı mesajı taşıyan iki istisna eşit midir? Önce Kotlin'in hata modeli okunur. Java'da bir fonksiyon hangi istisnaları fırlatabileceğini `throws` ile bildirir ve çağıran onları yakalamak zorundadır. Kotlin'de böyle bir bildirim yoktur; bütün istisnalar **denetlenmeyen istisna** sayılır, yakalamak çağıranın isteğine kalır. **Fırlatma** `throw` ile, **yakalama** `try` ve `catch` ile yapılır. Ölçü sorusu yine aynıdır: **bu iki değeri eşit yapan ne?**


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

#### Denetlenmeyen İstisna ve try İfadesi

Aşağıdaki blok iki şeyi ölçer. `readDisk` Java'nın `IOException` istisnasını fırlatır, ama imzasında hiçbir bildirim yoktur ve derleyici bir şey istemez. `try` ise ifadedir: blok son ifadesinin değerini verir, `catch` dalı kendi değerini verir ve `finally` her durumda koşar ama değer üretmez. Günlük listesi sırayı gösterir.

```kotlin
// Main.kt — denetlenmeyen istisna, try ifadesi ve finally
import java.io.IOException

fun readDisk(): String {
    throw IOException("disk")
}

fun withdraw(balance: Int, amount: Int): Int {
    if (amount > balance) throw IllegalStateException("yetersiz")
    return balance - amount
}

fun main() {
    val log = mutableListOf<String>()
    val left = try {
        log.add("dene")
        withdraw(100, 500)
    } catch (e: IllegalStateException) {
        log.add("yakala")
        -1
    } finally {
        log.add("son")
    }
    println(left)
    println(log)
    try {
        readDisk()
    } catch (e: IOException) {
        println(e::class.simpleName)
    }
    println(withdraw(100, 30))
}
```

```
-1
[dene, yakala, son]
IOException
70
```

Birinci satır `-1`: `withdraw` fırlattığı için `try` bloğunun değeri atılıp `catch` dalının `-1` değeri `left`'e atandı. Günlük `[dene, yakala, son]` sırasını söyler; `finally` en sonda, değer belli olduktan sonra koştu. Üçüncü satır `IOException` adını verir; `catch` yazıldığı için yakalandı, yazılmasaydı da derleme geçerdi. Son satır fırlatmayan yolun `70` döndüğünü gösterir.

**KE35.** Bu ölçümler tek iş parçacıklı, `return` içermeyen `try` ifadeleri için. `finally` içinden `return` ya da yeni istisna fırlatmanın sonucu, `use` ile kaynak kapatma ve `Error` sınıfları ölçülmedi.

#### runCatching ve Result

Hatayı yakalamak yerine değer olarak taşımak için `runCatching { }` kullanılır. Blok döndüyse `Result` başarılı, fırlattıysa başarısızdır. `getOrElse` başarısızlıkta yedek değeri verir, `exceptionOrNull` başarılıysa `null`, değilse istisnanın kendisini verir. Önce yardımcı dosyada **kendi istisna sınıfı** tanımlanır; ana blok onu fırlatan bir fonksiyon kurar.

```kotlin
// Errors.kt — kendi istisna sinifi
class LimitException(val limit: Int) : Exception("limit asildi")
```

```kotlin
// Main.kt — runCatching ve Result
fun parse(text: String): Int {
    if (text.length > 3) throw LimitException(3)
    return text.toInt()
}

fun main() {
    val ok = runCatching { parse("42") }
    val badFormat = runCatching { parse("x") }
    val tooLong = runCatching { parse("12345") }
    println(ok.getOrElse { -1 })
    println(badFormat.getOrElse { -1 })
    println(ok.exceptionOrNull())
    println(badFormat.exceptionOrNull()?.let { it::class.simpleName })
    val error = tooLong.exceptionOrNull()
    if (error is LimitException) {
        println(error.limit)
    }
}
```

```
42
-1
null
NumberFormatException
3
```

`ok` başarılı olduğu için `getOrElse` `42` verdi; `"x".toInt()` `NumberFormatException` fırlattığı için ikinci satır yedek değer olan `-1`. Üçüncü satır başarılı sonuçta `exceptionOrNull`'un `null` olduğunu, dördüncü satır başarısız sonuçta istisnanın tipini gösterir. Son satır kendi istisna sınıfının taşıdığı `limit` değerini okur: `is` denetimi sonrası `error` akıllı tip dönüşümüyle `LimitException` olur.

**KE36.** Bu ölçümler `Int` sonuçlu `runCatching` çağrıları ve `Exception` altındaki bir sınıf için. `map`, `recover` ve `onFailure` zincirleri, `getOrThrow` ve `Result`'ın fonksiyon dönüş tipi olarak kullanımı ölçülmedi.

#### Eş Yordamda Yayılma

Yapılandırılmış eşzamanlılıkta bir çocuk eş yordam başarısız olursa kapsam iptal edilir: kardeşler iptal alır ve istisna `coroutineScope`'tan dışarı çıkar. Aşağıdaki blokta ilk çocuk bin milisaniye bekleyecek, ikincisi hemen fırlatacak. Dışarıda yakalanan istisnanın yalnız tipi basılır, mesajı basılmaz.

```kotlin
// Main.kt — eş yordamda istisnanın yayılması
import kotlinx.coroutines.*

fun main() = runBlocking {
    val log = mutableListOf<String>()
    val caught = try {
        coroutineScope {
            launch {
                try {
                    delay(1000)
                    log.add("bitti")
                } finally {
                    log.add("iptal")
                }
            }
            launch {
                throw IllegalStateException("kardes")
            }
        }
        "yok"
    } catch (e: Exception) {
        e::class.simpleName
    }
    println(caught)
    println(log)
}
```

```
IllegalStateException
[iptal]
```

Dışarıda yakalanan istisna çocuğun fırlattığı `IllegalStateException`'dır. Günlükte yalnız `iptal` vardır: bekleyen çocuğa iptal geldi, `finally` bloğu koştu ve `bitti` hiç yazılmadı. `coroutineScope` çağrısından sonraki `"yok"` satırına ise hiç gelinmedi. İstisna, eş yordamın kapsamını aşıp sıradan bir `try` ile yakalanabilir.

**KE37.** Bu ölçümler tek `coroutineScope`, tek `runBlocking` ve bir başarısız çocuk için. `supervisorScope`, `CoroutineExceptionHandler` ve birden çok başarısız çocuğun istisnalarının nasıl toplandığı ölçülmedi.

#### Ölçüt: Kimlik

Şimdi asıl soru. Aynı mesajla kurulan iki istisna eşit midir? `Throwable` sınıfı `equals` tanımlamaz, kimliğe düşer. Sınırlayıcı ölçümler `Result`'ı ve veri sınıfı olarak yazılan bir istisnayı ekler. `Result.failure` içindeki istisnayı taşır: iki ayrı istisna iki ayrı `Result` yapar, aynı istisna iki `Result`'ı eşit yapar.

```kotlin
// Main.kt — istisnanın ölçütü ve sınırlayıcı ölçümler
data class CodedError(val code: Int) : Exception("kod")

fun main() {
    val shared = IllegalStateException("m")
    val checks = listOf(
        Criterion("istisna", IllegalStateException("m"), IllegalStateException("m"), IllegalStateException("x")),
        Criterion("sonuc-iki-istisna", Result.failure<Int>(IllegalStateException("m")), Result.failure<Int>(IllegalStateException("m")), Result.failure<Int>(IllegalStateException("x"))),
        Criterion("sonuc-ayni-istisna", Result.failure<Int>(shared), Result.failure<Int>(shared), Result.failure<Int>(IllegalStateException("x"))),
        Criterion("sonuc-basari", Result.success(1), Result.success(1), Result.success(2)),
        Criterion("veri-sinifi-istisna", CodedError(7), CodedError(7), CodedError(8)),
    )
    for (check in checks) check.report()
    summarize(checks)
}
```

```
istisna: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
sonuc-iki-istisna: ayni-nesne=false, ikiz=false, farkli=false -> kimlik
sonuc-ayni-istisna: ayni-nesne=false, ikiz=true, farkli=false -> icerik
sonuc-basari: ayni-nesne=false, ikiz=true, farkli=false -> icerik
veri-sinifi-istisna: ayni-nesne=false, ikiz=true, farkli=false -> icerik
tekil: 0
kimlik: 2
icerik: 3
kismi-icerik: 0
```

İlk satır bu dersin ölçütüdür: aynı mesajlı iki istisna eşit değildir, ölçüt **kimlik**. İkinci satırda iki ayrı istisnayı saran `Result`'lar da eşit değildir. Üçüncü satırda aynı istisnayı saran iki ayrı `Result` nesnesi eşittir: `Result` istisnanın ölçütünü devralır. `Result.success(1)` iki kez içerik verir. Veri sınıfı olarak yazılan `CodedError` ise aynı koduyla eşittir, çünkü üretilen `equals`, `Exception`'dan devralınan kimlik eşitliğini yeniden tanımlar. Sınırlayıcı ölçüm şudur: **istisnanın ölçütünü tipi belirler, mesaj belirlemez.**

**KE38.** Bu ölçümler `IllegalStateException` ve `Result` için. İstisnanın `cause` zinciri, `stackTrace` içeriği ve `equals`'i yazılmış başka istisna sınıfları ölçülmedi.

#### Özet

- Kotlin'de `throws` yoktur; bütün istisnalar denetlenmeyen istisnadır. `try` değer üreten bir ifadedir ve `finally` en sonda koşar.
- `runCatching` hatayı `Result` olarak taşır; `getOrElse` yedek değer, `exceptionOrNull` istisnayı verir.
- `coroutineScope` içinde bir çocuk başarısız olunca kardeşler iptal edilir ve istisna dışarıya yayılır.
- Aynı mesajlı iki istisna eşit değildir. Bu dersin ölçütü: **kimlik**.
- Sınırlayıcı ölçüm: `Result.failure` istisnanın ölçütünü taşır; veri sınıfı olarak yazılan istisna aynı alanla içeriktir.

#### Kurs Kapanışı

Kurs yirmi üç dersle tek soruyu sordu ve her dersin ölçütü kaynak dersin özetinden alındı:

| Ders | Ölçülen değer | Ölçüt | Sınırlayıcı ölçüm |
|---|---|---|---|
| Kotlin'in Konumu | veri sınıfı, JVM listesi | içerik | kurucudaki `name` farklıysa eşit değil |
| Değişkenler | `const val` sabiti | tekil | çalışma anında kurulan aynı metin içerik |
| Temel Tipler | kutulanan `1000` | içerik | 127 tekil; NaN statik tipe göre değişir |
| Dizgi Şablonları | iki ayrı şablon | içerik | tek değişkenli şablon tekil |
| Denetim Akışı | `when` dalı, sıradan sınıf | kimlik | `is Box` tutar; iki `1..3` içerik |
| Fonksiyonlar | iki `::plain` | içerik | `listOf()` varsayılanı tekil |
| Lambdalar | iki ayrı lambda | kimlik | yakalamayan lambda tekil; `::inc` içerik |
| Kapsam Fonksiyonları | `let` ile `copy` | içerik | `also` ikizi tekil, ölçümü bozar |
| Null Güvenliği | `null` ile `null` | tekil | dolu iki dizgi içerik; dolu ile `null` kimlik |
| Güvenli Çağrı ve Elvis | iki güvenli çağrı | içerik | boş alıcıda iki `null` tekil |
| Sınıflar ve Yapıcılar | `equals`'siz sınıf | kimlik | aynı kalıp `equals`'e göre içerik, kısmi içerik |
| Veri Sınıfları | `copy` ile kurulan nesne | içerik | `IntArray` alanı kimlik, `List` alanı içerik |
| Mühürlü Tipler ve Numaralandırmalar | numaralandırma girdisi | tekil | alt tipin türü ölçütü belirler |
| Nesne Bildirimleri | iki okuma | tekil | nesne ifadesi her seferinde yeni, kimlik |
| Arayüzler ve Devretme | `by` ile devreden iki nesne | kimlik | aynı temsilcili iki veri sınıfı eşit |
| Uzantı Fonksiyonları | uzantı `equals` | kimlik | üye uzantıyı yener |
| Genellikler ve Varyans | iki boş liste, iki tip parametresi | tekil | genel veri sınıfı alan değerine bakar |
| Koleksiyon Türleri | liste eşitliği | içerik | küme sırası kısmi içerik; boş liste tekil |
| Dönüşüm İşlemleri | iki `map` | içerik | `distinct` öğenin ölçütünü kullanır |
| Tembel Diziler | iki `sequenceOf` | kimlik | `toList()` ile içerik |
| Eş Yordamlar | iki `Deferred` | kimlik | `await` sonuçları içerik |
| Akışlar | `distinctUntilChanged` | içerik | sıradan sınıfta 5 değer kalır; akış kimlik |
| İstisna Yönetimi | aynı mesajlı iki istisna | kimlik | `Result` istisnanın ölçütünü taşır; veri sınıfı istisna içerik |

Yirmi üç dersin dağılımı: tekil 5, kimlik 8, içerik 10, kısmi içerik 0. Kısmi içerik hiçbir dersin ölçütü olmadı; yalnız sınırlayıcı ölçümlerde göründü. Açılış dersinin çekirdeği ayrı bir sayımdır: sekiz değerin dağılımı 2/2/3/1'dir (tekil/kimlik/içerik/kısmi içerik). İkisi aynı şey değildir, çünkü çekirdek sekiz değer, tablo yirmi üç ders sayar.

Aynı mesajla kurulan iki istisnanın eşit çıkmaması, kursun bütün cevabını toplar: Kotlin'de eşitliği değerin görünüşü değil tipin kuralı verir. Sıradaki kurs Android Platformu ve Araçlar'dır; uygulamanın parçalarını ve bu parçaların yaşam döngülerini konu alır.

