---
title: 'Koleksiyon Türleri'
source: 'https://academia.sh/tr/kurslar/kotlin/koleksiyon-turleri'
course: 'Kotlin Dili'
language: tr
updated: '2026-10-21T05:02:39+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Salt okunur List arayüzünün aynı nesnenin değiştirilebilir bir görünümü olabildiğini ölçer'
    - 'Listenin eşitliğini türüne değil içeriğine dayandırdığını çıktıdan okur'
    - 'Kümenin ve eşlemin eşitliğinin eleman sırasından bağımsız olduğunu gösterir'
    - 'Eşlemde eksik anahtarın null döndürdüğünü, getValue çağrısının ise istisna fırlattığını ayırır'
---

# Koleksiyon Türleri

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?
