---
title: 'Veri Sınıfları'
source: 'https://academia.sh/tr/kurslar/kotlin/veri-siniflari'
course: 'Kotlin Dili'
language: tr
updated: '2026-10-21T05:02:40+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Veri sınıfının equals, hashCode, toString ve copy yöntemlerini birincil yapıcıdan ürettiğini ölçer'
    - 'copy ile kurulan ayrı nesnenin içerik ölçütüne düştüğünü gösterir'
    - 'Yapısöküm bildiriminin componentN yöntemlerine karşılık geldiğini ölçer'
    - 'Alanın tipinin, yani dizi ya da liste olmasının ölçütü belirlediğini sınırlayıcı ölçümle doğrular'
---

# Veri Sınıfları

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.
