Kotlin Bootcamp for Programmers 5.1: Extensions

Bu codelab, Kotlin Bootcamp for Programmers kursunun bir parçasıdır. Bu kurstan en iyi şekilde yararlanmak için codelab'leri sırayla tamamlamanızı öneririz. Bilgi düzeyinize bağlı olarak bazı bölümleri gözden geçirebilirsiniz. Bu kurs, nesne yönelimli bir dil bilen ve Kotlin'i öğrenmek isteyen programcılara yöneliktir.

Giriş

Bu kod laboratuvarında, Kotlin'deki çiftler, koleksiyonlar ve uzantı işlevleri gibi çeşitli faydalı özelliklerle tanışacaksınız.

Bu kurstaki dersler, tek bir örnek uygulama oluşturmak yerine bilginizi artırmak için tasarlanmıştır ancak birbirlerinden yarı bağımsız oldukları için aşina olduğunuz bölümleri gözden geçirebilirsiniz. Örneklerin çoğu, bunları bir araya getirmek için akvaryum temasını kullanır. Akvaryumun hikayesinin tamamını görmek isterseniz Kotlin Bootcamp for Programmers (Programcılar için Kotlin Temel Eğitim Programı) adlı Udacity kursuna göz atın.

Bilmeniz gerekenler

  • Kotlin işlevlerinin, sınıflarının ve yöntemlerinin söz dizimi
  • IntelliJ IDEA'da Kotlin'in REPL'si (Read-Eval-Print Loop) ile çalışma
  • IntelliJ IDEA'da yeni bir sınıf oluşturma ve program çalıştırma

Neler öğreneceksiniz?

  • İkili ve üçlü öğelerle çalışma
  • Koleksiyonlar hakkında daha fazla bilgi
  • Sabitleri tanımlama ve kullanma
  • Uzantı işlevleri yazma

Yapacaklarınız

  • REPL'de çiftler, üçlüler ve karma haritalar hakkında bilgi
  • Sabitleri düzenlemenin farklı yollarını öğrenin
  • Uzantı işlevi ve uzantı özelliği yazma

Bu görevde, çiftler ve üçlüler hakkında bilgi edinecek ve bunları yapılarını bozmayı öğreneceksiniz. Çiftler ve üçlüler, 2 veya 3 genel öğe için önceden hazırlanmış veri sınıflarıdır. Bu, örneğin bir işlevin birden fazla değer döndürmesi için yararlı olabilir.

List balığınız olduğunu ve balığın tatlı su balığı mı yoksa tuzlu su balığı mı olduğunu kontrol eden bir isFreshWater() işleviniz olduğunu varsayalım. List.partition(), biri durumun true olduğu öğeleri, diğeri ise durumun false olduğu öğeleri içeren iki liste döndürür.

val twoLists = fish.partition { isFreshWater(it) }
println("freshwater: ${twoLists.first}")
println("saltwater: ${twoLists.second}")

1. adım: İkili ve üçlü gruplar oluşturun

  1. REPL'yi açın (Araçlar > Kotlin > Kotlin REPL).
  2. Bir ekipmanı ne için kullanıldığıyla ilişkilendirerek bir çift oluşturun ve değerleri yazdırın. İki değeri (ör. iki dize) to anahtar kelimesiyle bağlayan bir ifade oluşturarak çift oluşturabilir, ardından her bir değere başvurmak için .first veya .second kullanabilirsiniz.
val equipment = "fish net" to "catching fish"
println("${equipment.first} used for ${equipment.second}")
⇒ fish net used for catching fish
  1. toString() ile üçlü oluşturup yazdırın, ardından toList() ile listeye dönüştürün. Triple() kullanarak 3 değer içeren bir üçlü oluşturursunuz. Her bir değere atıfta bulunmak için .first, .second ve .third özelliklerini kullanın.
val numbers = Triple(6, 9, 42)
println(numbers.toString())
println(numbers.toList())
⇒ (6, 9, 42)
[6, 9, 42]

Yukarıdaki örneklerde, çiftin veya üçlünün tüm parçaları için aynı tür kullanılmaktadır ancak bu zorunlu değildir. Parçalar; dize, sayı veya liste olabilir. Örneğin, başka bir çift ya da üçlü.

  1. Çiftin ilk bölümünün kendisi de çift olan bir çift oluşturun.
val equipment2 = ("fish net" to "catching fish") to "equipment"
println("${equipment2.first} is ${equipment2.second}\n")
println("${equipment2.first.second}")
⇒ (fish net, catching fish) is equipment
⇒ catching fish

2. adım: Bazı ikili ve üçlü grupları ayrıştırın

Çiftleri ve üçlüleri bileşenlerine ayırmaya yapı bozma denir. Çifti veya üçlüyü uygun sayıda değişkene atayın. Kotlin, her bölümün değerini sırayla atar.

  1. Bir çifti yapıdan çıkarma ve değerleri yazdırma.
val equipment = "fish net" to "catching fish"
val (tool, use) = equipment
println("$tool is used for $use")
⇒ fish net is used for catching fish
  1. Üçlü bir yapıyı bozup değerleri yazdırın.
val numbers = Triple(6, 9, 42)
val (n1, n2, n3) = numbers
println("$n1 $n2 $n3")
⇒ 6 9 42

Çiftlerin ve üçlülerin yapılarını bozmanın, önceki bir codelab'de ele alınan veri sınıflarıyla aynı şekilde çalıştığını unutmayın.

Bu görevde, listeler de dahil olmak üzere koleksiyonlar ve yeni bir koleksiyon türü olan karma eşlemler hakkında daha fazla bilgi edineceksiniz.

1. adım: Listeler hakkında daha fazla bilgi edinin

  1. Listeler ve değiştirilebilir listeler önceki bir derste tanıtılmıştı. Çok kullanışlı bir veri yapısı olduklarından Kotlin, listeler için bir dizi yerleşik işlev sağlar. Listeler için bu kısmi işlev listesini inceleyin. Tam listeleri List ve MutableList ile ilgili Kotlin belgelerinde bulabilirsiniz.

İşlev

Purpose

add(element: E)

Değiştirilebilir listeye öğe ekleyin.

remove(element: E)

Değiştirilebilir bir listeden öğe kaldırma.

reversed()

Öğelerin ters sırada olduğu listenin bir kopyasını döndürür.

contains(element: E)

Liste öğeyi içeriyorsa true değerini döndürür.

subList(fromIndex: Int, toIndex: Int)

Listenin bir bölümünü döndürür. Bu bölüm, ilk dizinden başlayıp ikinci dizine kadar (ikinci dizin hariç) olan kısmı kapsar.

  1. REPL'de çalışmaya devam ederek bir sayı listesi oluşturun ve bu listede sum() işlevini çağırın. Bu, tüm öğelerin toplamıdır.
val list = listOf(1, 5, 3, 4)
println(list.sum())
⇒ 13
  1. Dizelerden oluşan bir liste oluşturun ve listeyi toplayın.
val list2 = listOf("a", "bbb", "cc")
println(list2.sum())
⇒ error: none of the following functions can be called with the arguments supplied:
  1. Öğe, List öğesinin doğrudan nasıl toplayacağını bilmediği bir şeyse (ör. dize), .sumBy() ile bir lambda işlevi kullanarak nasıl toplanacağını belirtebilirsiniz. Örneğin, her dizenin uzunluğuna göre toplama işlemi yapabilirsiniz. Lambda bağımsız değişkeninin varsayılan adı it'dır. Burada it, liste üzerinde gezinilirken listenin her bir öğesini ifade eder.
val list2 = listOf("a", "bbb", "cc")
println(list2.sumBy { it.length })
⇒ 6
  1. Listelerle yapabileceğiniz daha birçok şey var. Kullanılabilir işlevleri görmenin bir yolu, IntelliJ IDEA'da bir liste oluşturup noktayı ekledikten sonra ipucundaki otomatik tamamlama listesine bakmaktır. Bu yöntem, tüm nesneler için geçerlidir. Bu özelliği bir listede deneyin.

  1. Listeden listIterator() simgesini seçin, ardından for ifadesiyle listeyi inceleyin ve boşluklarla ayrılmış tüm öğeleri yazdırın.
val list2 = listOf("a", "bbb", "cc")
for (s in list2.listIterator()) {
    println("$s ")
}
⇒ a bbb cc

2. adım: Karma haritaları deneyin

Kotlin'de hashMapOf() kullanarak neredeyse her şeyi başka bir şeye eşleyebilirsiniz. Karma haritalar, ilk değerin anahtar olarak kullanıldığı bir çift listesine benzer.

  1. Balıkların semptomlarını (anahtarlar) ve hastalıklarını (değerler) eşleştiren bir karma eşlem oluşturun.
val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
  1. Ardından, get() veya daha kısa olan köşeli parantezleri [] kullanarak hastalık değerini belirti anahtarına göre alabilirsiniz.
println(cures.get("white spots"))
⇒ Ich
println(cures["red sores"])
⇒ hole disease
  1. Haritada bulunmayan bir belirti belirtmeyi deneyin.
println(cures["scale loss"])
⇒ null

Bir anahtar haritada yoksa eşleşen hastalığı döndürmeye çalışmak null değerini döndürür. Harita verilerine bağlı olarak, olası bir anahtar için eşleşme olmaması yaygın bir durumdur. Bu gibi durumlarda Kotlin, getOrDefault() işlevini sağlar.

  1. getOrDefault() kullanarak eşleşmesi olmayan bir anahtarı aramayı deneyin.
println(cures.getOrDefault("bloating", "sorry, I don't know"))
⇒ sorry, I don't know

Yalnızca değer döndürmekten daha fazlasını yapmanız gerekiyorsa Kotlin, getOrElse() işlevini sağlar.

  1. Kodunuzu getOrDefault() yerine getOrElse() kullanacak şekilde değiştirin.
println(cures.getOrElse("bloating") {"No cure for this"})
⇒ No cure for this

Basit bir varsayılan değer döndürmek yerine, küme parantezleri {} arasındaki kod yürütülür. Örnekte else yalnızca bir dize döndürür ancak bu, tedavi yöntemi içeren bir web sayfası bulup döndürmek kadar karmaşık da olabilir.

mutableListOf gibi, mutableMapOf da oluşturabilirsiniz. Değiştirilebilir bir harita, öğe yerleştirmenize ve kaldırmanıza olanak tanır. Değişebilir (mutable) yalnızca değişebilir anlamına gelirken değişmez (immutable) değişemez anlamına gelir.

  1. Bir ekipman dizesini öğe sayısıyla eşleyerek değiştirilebilen bir envanter haritası oluşturun. Akvaryumun içine balık ağı koyarak oluşturun, ardından put() ile envantere 3 akvaryum temizleyici ekleyin ve remove() ile balık ağını kaldırın.
val inventory = mutableMapOf("fish net" to 1)
inventory.put("tank scrubber", 3)
println(inventory.toString())
inventory.remove("fish net")
println(inventory.toString())
⇒ {fish net=1, tank scrubber=3}{tank scrubber=3}

Bu görevde, Kotlin'deki sabitler ve bunları düzenlemenin farklı yolları hakkında bilgi edineceksiniz.

1. adım: const ve val hakkında bilgi edinin

  1. REPL'de sayısal bir sabit oluşturmayı deneyin. Kotlin'de, const val kullanarak üst düzey sabitler oluşturabilir ve derleme zamanında bunlara değer atayabilirsiniz.
const val rocks = 3

Değer atanır ve değiştirilemez. Bu, normal bir val bildirmeye çok benzer. Peki const val ile val arasındaki fark nedir? const val değeri derleme zamanında belirlenirken val değeri program yürütme sırasında belirlenir. Bu nedenle, val çalışma zamanında bir işlev tarafından atanabilir.

Bu, val'ya bir işlevden değer atanabileceği ancak const val'ye değer atanamayacağı anlamına gelir.

val value1 = complexFunctionCall() // OK
const val CONSTANT1 = complexFunctionCall() // NOT ok

Ayrıca, const val yalnızca üst düzeyde ve object ile tanımlanan tekil sınıflarda çalışır, normal sınıflarda çalışmaz. Bu özelliği kullanarak yalnızca sabitleri içeren bir dosya veya tekil nesne oluşturabilir ve bunları gerektiğinde içe aktarabilirsiniz.

object Constants {
    const val CONSTANT2 = "object constant"
}
val foo = Constants.CONSTANT2

2. adım: Yardımcı nesne oluşturun

Kotlin'de sınıf düzeyinde sabitler kavramı yoktur.

Bir sınıfın içinde sabitler tanımlamak için bunları companion anahtar kelimesiyle tanımlanan tamamlayıcı nesnelere sarmalamanız gerekir. Yardımcı nesne, temelde sınıf içindeki tekil bir nesnedir.

  1. Dize sabiti içeren bir yardımcı nesneyle sınıf oluşturun.
class MyClass {
    companion object {
        const val CONSTANT3 = "constant in companion"
    }
}

Yardımcı nesneler ile normal nesneler arasındaki temel fark şudur:

  • Companion nesneler, kapsayan sınıfın statik oluşturucusundan başlatılır. Yani nesne oluşturulduğunda oluşturulurlar.
  • Normal nesneler, bu nesneye ilk erişimde (yani ilk kullanıldıklarında) geç başlatılır.

Daha fazla bilgi var ancak şu an için bilmeniz gereken tek şey, sabitleri bir yardımcı nesnedeki sınıflara sarmalamaktır.

Bu görevde, sınıfların davranışını genişletme hakkında bilgi edineceksiniz. Bir sınıfın davranışını genişletmek için yardımcı işlevler yazmak çok yaygındır. Kotlin, bu yardımcı işlevleri (uzantı işlevleri) bildirmek için uygun bir söz dizimi sağlar.

Uzantı işlevleri, kaynak koduna erişmek zorunda kalmadan mevcut bir sınıfa işlev eklemenize olanak tanır. Örneğin, bunları paketinizin bir parçası olan Extensions.kt dosyasında bildirebilirsiniz. Bu, sınıfı gerçekten değiştirmez ancak bu sınıfın nesnelerinde işlevi çağırırken nokta gösterimini kullanmanıza olanak tanır.

1. adım: Uzantı işlevi yazın

  1. REPL'de çalışmaya devam ederek bir dizenin boşluk içerip içermediğini kontrol etmek için basit bir uzantı işlevi hasSpaces() yazın. İşlev adının önüne üzerinde çalıştığı sınıf eklenir. İşlevin içinde this, üzerinde çağrıldığı nesneyi, it ise find() çağrısındaki yineleyiciyi ifade eder.
fun String.hasSpaces(): Boolean {
    val found = this.find { it == ' ' }
    return found != null
}
println("Does it have spaces?".hasSpaces())
⇒ true
  1. hasSpaces() işlevini basitleştirebilirsiniz. this açıkça gerekli değildir ve işlev tek bir ifadeye indirgenip döndürülebilir. Bu nedenle, etrafındaki küme parantezleri {} de gerekli değildir.
fun String.hasSpaces() = find { it == ' ' } != null

2. adım: Uzantıların sınırlamalarını öğrenin

Uzantı işlevleri yalnızca genişlettikleri sınıfın herkese açık API'sine erişebilir. private olan değişkenlere erişilemez.

  1. private olarak işaretlenmiş bir özelliğe uzantı işlevleri eklemeyi deneyin.
class AquariumPlant(val color: String, private val size: Int)

fun AquariumPlant.isRed() = color == "red"    // OK
fun AquariumPlant.isBig() = size > 50         // gives error
⇒ error: cannot access 'size': it is private in 'AquariumPlant'
  1. Aşağıdaki kodu inceleyin ve ne yazdıracağını bulun.
open class AquariumPlant(val color: String, private val size: Int)

class GreenLeafyPlant(size: Int) : AquariumPlant("green", size)

fun AquariumPlant.print() = println("AquariumPlant")
fun GreenLeafyPlant.print() = println("GreenLeafyPlant")

val plant = GreenLeafyPlant(size = 10)
plant.print()
println("\n")
val aquariumPlant: AquariumPlant = plant
aquariumPlant.print()  // what will it print?
⇒ GreenLeafyPlant
AquariumPlant

plant.print() baskı GreenLeafyPlant. aquariumPlant.print() değişkenine plant değeri atandığı için aquariumPlant.print() değişkeninin de GreenLeafyPlant değerini yazdırmasını bekleyebilirsiniz. Ancak tür, derleme zamanında çözümlendiği için AquariumPlant yazdırılır.

3. adım: Uzantı özelliği ekleyin

Kotlin, uzantı işlevlerine ek olarak uzantı özellikleri de eklemenize olanak tanır. Uzantı işlevlerinde olduğu gibi, genişlettiğiniz sınıfı, ardından bir nokta ve ardından özellik adını belirtirsiniz.

  1. REPL'de çalışmaya devam ederek isGreen uzantı özelliğini AquariumPlant öğesine ekleyin. Bu özellik, renk yeşilse true olur.
val AquariumPlant.isGreen: Boolean
   get() = color == "green"

isGreen özelliğine normal bir özellik gibi erişilebilir. Erişildiğinde, değeri almak için isGreen getter'ı çağrılır.

  1. aquariumPlant değişkeni için isGreen özelliğini yazdırın ve sonucu inceleyin.
aquariumPlant.isGreen
⇒ res4: kotlin.Boolean = true

4. adım: Nullable alıcılar hakkında bilgi edinin

Genişlettiğiniz sınıfa alıcı adı verilir ve bu sınıfı null yapılabilir hale getirmek mümkündür. Bu durumda, gövdede kullanılan this değişkeni null olabilir. Bu nedenle, bu durumu test ettiğinizden emin olun. Arayanların, uzantı yönteminizi null değer atanabilir değişkenlerde çağırmak isteyeceğini düşünüyorsanız veya işleviniz null için uygulandığında varsayılan bir davranış sağlamak istiyorsanız null değer atanabilir bir alıcı kullanmak isteyebilirsiniz.

  1. REPL'de çalışmaya devam ederken, null değer atanabilir bir alıcı alan pull() yöntemi tanımlayın. Bu, türden sonra ve noktadan önce soru işareti ? ile belirtilir. Gövde içinde, soru işareti-nokta-uygula ?.apply. kullanarak this öğesinin null olmadığını test edebilirsiniz.
fun AquariumPlant?.pull() {
   this?.apply {
       println("removing $this")
   }
}

val plant: AquariumPlant? = null
plant.pull()
  1. Bu durumda, programı çalıştırdığınızda çıkış olmaz. plant, null olduğu için içteki println() çağrılmaz.

Uzantı işlevleri çok güçlüdür ve Kotlin standart kitaplığının çoğu uzantı işlevi olarak uygulanır.

Bu derste koleksiyonlar ve sabitler hakkında daha fazla bilgi edindiniz. Ayrıca, uzantı işlevlerinin ve özelliklerinin gücünü keşfettiniz.

  • Çiftler ve üçlüler, bir işlevden birden fazla değer döndürmek için kullanılabilir. Örneğin:
    val twoLists = fish.partition { isFreshWater(it) }
  • Kotlin, List için reversed(), contains() ve subList() gibi birçok yararlı işleve sahiptir.
  • Anahtarları değerlerle eşlemek için HashMap kullanılabilir. Örneğin:
    val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
  • const anahtar kelimesini kullanarak derleme zamanı sabitlerini bildirin. Bunları en üst düzeye yerleştirebilir, tekil bir nesnede düzenleyebilir veya yardımcı bir nesneye yerleştirebilirsiniz.
  • Companion object, companion anahtar kelimesiyle tanımlanan bir sınıf tanımı içindeki tekil bir nesnedir.
  • Uzantı işlevleri ve özellikleri, bir sınıfa işlevsellik ekleyebilir. Örneğin:
    fun String.hasSpaces() = find { it == ' ' } != null
  • Null olabilen alıcı, null olabilen bir sınıfta uzantılar oluşturmanıza olanak tanır. ?. operatörü, kodu yürütmeden önce null olup olmadığını kontrol etmek için apply ile birlikte kullanılabilir. Örneğin:
    this?.apply { println("removing $this") }

Kotlin belgeleri

Bu kurstaki herhangi bir konu hakkında daha fazla bilgi edinmek veya takıldığınız noktaları aşmak için https://kotlinlang.org adresini ziyaret edebilirsiniz.

Kotlin eğitimleri

https://try.kotlinlang.org web sitesinde Kotlin Koans adlı zengin eğitimler, web tabanlı bir yorumlayıcı ve örneklerle birlikte eksiksiz bir referans dokümanı seti yer alır.

Udacity kursu

Bu konuyla ilgili Udacity kursunu görüntülemek için Kotlin Bootcamp for Programmers'a (Programcılar için Kotlin Temel Eğitimi) göz atın.

IntelliJ IDEA

IntelliJ IDEA ile ilgili dokümanları JetBrains web sitesinde bulabilirsiniz.

Bu bölümde, bir eğitmenin yönettiği kurs kapsamında bu codelab'i tamamlayan öğrenciler için olası ödevler listelenmektedir. Eğitmen, aşağıdakileri yapmalıdır:

  • Gerekirse ödev atayın.
  • Öğrencilere ev ödevi ödevlerini nasıl göndereceklerini bildirin.
  • Ödevlere not verin.

Eğitmenler bu önerileri istedikleri kadar kullanabilir ve uygun olduğunu düşündükleri diğer ödevleri verebilirler.

Bu codelab'i kendi başınıza tamamlıyorsanız bilginizi test etmek için bu ödevleri kullanabilirsiniz.

Bu soruları yanıtlayın

1. Soru

Aşağıdakilerden hangisi bir listenin kopyasını döndürür?

add()

remove()

reversed()

contains()

2. Soru

class AquariumPlant(val color: String, val size: Int, private val cost: Double, val leafy: Boolean) üzerindeki aşağıdaki uzantı işlevlerinden hangisi derleyici hatası verir?

fun AquariumPlant.isRed() = color == "red"

fun AquariumPlant.isBig() = size > 45

fun AquariumPlant.isExpensive() = cost > 10.00

fun AquariumPlant.isNotLeafy() = leafy == false

3. Soru

Aşağıdakilerden hangisi, const val ile sabitleri tanımlayabileceğiniz bir yer değildir?

▢ Bir dosyanın en üst düzeyinde

▢ normal sınıflarda

▢ tekil nesnelerde

▢ Tamamlayıcı nesnelerde

Sonraki derse geçin: 5.2 Generics

Diğer codelab'lerin bağlantıları da dahil olmak üzere kursa genel bir bakış için "Kotlin Bootcamp for Programmers: Welcome to the course." başlıklı makaleyi inceleyin.