Infix ve Extension Fonksiyonlar

Ömer Sungur
7 min readJul 22, 2023

Kotlin’in kendine has en güzel özelliklerinden olan infix ve extension fonksiyonlarını detaylı bir şekilde inceleyeceğiz. Bunların kullanım alanlarını ve bize sağladığı kolaylıklara değineceğiz.

Infix Fonksiyonlar Nedir ve Nasıl Kullanılır?

Infix fonksiyonlar, sadece daha okunaklı kod yazabilmek için kullandığımız fonksiyonlardır. Performans açısından herhangi bir avantaj sağlamazlar.

Infix fonksiyon yapısını kurabilmek için birkaç şartı sağlamamız gerekiyor. O şartlar ise şu şekildedir:

  • Infix fonksiyonlar, ya üye (bir sınıf veya bir obje içinde) fonksiyon olmalıdır ya da extension fonksiyon olmalıdır. Bu ikisinden birini kesinlikle sağlamalıdır.
  • Infix fonksiyonların sadece bir parametresi olmalıdır ve bu parametre vararg tipinde olamaz.
  • Infix fonksiyonlarındaki parametrenin default değeri olamaz.

Bu 3 şartı sağladıktan sonra infix fonksiyonu yazabiliriz. Örnek kod yazmadan önce Kotlin dili içinde var olan infix fonksiyonlarına bir göz atalım.

fun main() {

val isStudent = true
val isMale = true

if (isMale and isStudent) { // infix kullanımı
println()
}

if (isMale.and(isStudent)) {
println()
}
}

If blokları içinde sık sık or, and gibi boolean ifadelerin kullanımını yaparız. Bu yapının, .and() ve sadece and yazarak ayrı ayrı kullanılabilmesinin sebebi tam olarak infix fonksiyon olmasından kaynaklanır. Infix fonksiyonlarını kullanarak nokta ve parantez yapısından kurtuluyoruz. Kotlin’in boolean dosyası için yazılan kodlara bakacak olursak and fonksiyonunun infix yapısıyla yazıldığını görüyoruz. Kotlin içinde bunun gibi birçok halihazırda infix fonksiyonu vardır.

Şimdi de kendimiz bir infix fonksiyon yazalım.

fun main() {

val picture = Picture()

picture.downloadData("www.example.com")
picture downloadData "www.example.com" // infix kullanımı
}

class Picture {

infix fun downloadData(url: String) {

}
}

Yazdığımız infix fonksiyonu ile sınıf içindeki fonksiyon çağrımını daha farklı bir şekilde yapabildik. Infix fonksiyonunu bir sınıf içerisinde yazdığımıza dikkat çekerim. Sınıf içinde yazmasaydık extension fonksiyon ile yazmak zorundaydık. Üstteki kodun aynısını extension fonksiyon ile yazalım.

fun main() {

val picture = Picture()

picture.downloadData("www.example.com")
picture downloadData "www.example.com" // infix kullanımı
}

class Picture

infix fun Picture.downloadData(url: String) {

}

Extension fonksiyonları gördükten sonra bu kısımlar daha da anlaşılır olacak. Extension fonksiyonları hakkında bilginiz yoksa yazının sonuna geldiğinizde buraya tekrar dönmenizi tavsiye ederim.

Infix fonksiyonları sınıf içerisinde yazılırsa ve sınıfların içerisinden bir erişim yapılacaksa “this” anahtar kelimesinden yararlanmamız gerekiyor.

fun main() {

val picture = Picture()
picture.callDownloadData("www.example.com")
}

class Picture {

private infix fun downloadData(url: String) {
// business logic
}

fun callDownloadData(url: String) {
this downloadData url // this downloadData url = Picture().downloadData(url)
println(url)
downloadData(url) // Bu infix kullanımı değildir. Düz fonksiyon çağrımıdır.
}
}

Buradaki this anahtar kelimesi içinde bulunduğu sınıfı işaret ediyor (Picture) ve o sınıftaki fonksiyonu infix kullanımıyla beraber çağrıyoruz. Bu yapı içinde this yazmadan infix fonksiyonunu kullanamayız. Yani “downloadData url” diye bir kullanım yapılamaz. Normal bir fonksiyon çağırımı gibi downloadData(url) yazabiliriz fakat biz infix fonksiyonunun bize sağladığı özelliklerden yararlanarak parantez ve nokta kullanımlarından kurtulmak istiyoruz. Sınıf dışında bu şekilde this ile bir çağırım yapamadığımıza dikkat edelim. Bunun yapılabilmesi, sınıfın içinde olmamıza bağlıdır.

Son olarak bilmemiz gereken şey, işlem önceliği. Infix fonksiyonları, aritmetik operatörlere, tip dönüşümlerine ve rangeTo operatörüne göre daha düşük önceliğe sahiptir. Fakat boolean operatörlerine (&&, ||) göre de yüksek önceliğe sahiptir. Bu durumlara örnek olarak yine bir infix extension fonksiyon yazalım.

fun main() {

println(3 timesFunc 5 - 4)
}

infix fun Int.timesFunc(number: Int): Int {
return (this * number)
}

timesFunc isimli fonksiyonumuz ile 2 sayıyı çarpıp değerini return ediyoruz. Buradaki this, Int sınıfını işaret ediyor. Bunları extension bölümünde göreceğiz. 10 timesFunc 20 gibi bir infix çağrımıyla bu fonksiyonu kullanabiliyoruz. println fonksiyonunun içeriğinde 3 * 5 - 4 ifadesini görüyoruz. Normalde sonucun 11 olmasını bekleriz fakat konsolda 3 sonucunu alırız. Üstte belirtildiği gibi infix fonksiyonlarının aritmetik operatörlere göre daha az önceliğe sahip olması bu durumu değiştiriyor. O kısmı 3 * (5 - 4) gibi düşünmeliyiz. Son bir örnek olarak boolean tiplerindeki işlem önceliğine bakalım.

fun main() {

val isStudent = true
val isMale = true
val isFemale = false

if(isFemale && isMale or isStudent){ // false && true or true
println("Working...") // Çalışmaz
}

// (isFemale && (isMale or isStudent))
}

Üstteki örnekte de infix fonksiyonunun (or) yüksek önceliğini görüyoruz. Normalde false && true ifadesi bize false değerini verir. Ardından bu false değer ile true değerini or işlemine tabi tutunca true değerinin dönmesi lazım. Fakat buradaki if bloğu bize false olarak dönüş yapar. Bunun sebebi de infix fonksiyonlarının boolean operatörlerine karşı işlem önceliği sağlamasıdır. If koşulu, (isFemale && (isMale or isStudent)) şeklinde işlem görür.

Infix fonksiyonları için bilmemiz gerekenler bunlardır. Şimdi gelelim extension fonksiyonlara.

Extension Fonksiyonlar Nedir ve Nasıl Kullanılır?

Extension fonksiyonlar, Kotlin’de tanımlanan sınıflar ve interfaceler için kalıtım yapılmadan, decorator kullanmadan o sınıfa veya interface yapısına ait fonksiyon yazımını sağlayan fonksiyonlardır. (Decorator = yapısını değiştirmeden ve aynı sınıftaki diğer nesnelerin davranışını etkilemeden mevcut bir nesneye yeni işlevler eklenmesini sağlayan yapı)

Peki neden kullanırız? Kotlin’in string sınıfı için yazılan kodları düşünelim. Bu sınıfın içinde çeşitli fonksiyonlar vardır. Örneğin uppercase(), lowercase(), isBlank() ve daha birçok fonksiyon. Bazen öyle ihtiyaçlarımız olur ki bu yazılan fonksiyonlar tam olarak istediğimiz şeyi karşılayamaz. Diyelim ki şöyle bir fonksiyona ihtiyacımız var: “Metindeki çift indise sahip karakterleri büyük harfle yazacak ve tek indise sahip olan karakterleri de küçük harfle yazacak bir fonksiyon”. Bu işleve sahip bir fonksiyon, string sınıfı içinde tanımlı değildir. Böyle bir fonksiyonu string sınıfının bir fonksiyonuymuş gibi tanımlayabiliriz. İşte buna extension fonksiyon deniyor. Bahsettiğimiz extension fonksiyonu yazmadan önce çok basit bir örnek verelim ve nasıl yazılıyor görelim.

fun main() {

println(10.extPlus(20)) // 30
}

fun Int.extPlus(number: Int): Int {
return (this + number)
}

Bu extension fonksiyon ile 2 sayıyı topluyoruz ve toplamı return ediyoruz. Buradaki this ifadesi receiver’ı işaret eder. Integer yapısı içinde böyle bir fonksiyon default olarak yoktur fakat bu yaptığımız işlemle artık extPlus bir integer fonksiyonu. Ne zaman integer bir ifade kullanırsak bu fonksiyonu çağırabiliriz. Buradaki receiver integera karşılık gelir. Bunları detaylı göreceğiz. Şimdi de üstte bahsettiğimiz diğer extension fonksiyonunu yazalım.


fun main() {

println("HelloWorld".extChangeText()) //
}

fun String.extChangeText(): String{
val result = StringBuilder()
for (i in this.indices) {
if (i % 2 == 0) {
result.append(this[i].uppercase())
} else {
result.append(this[i].lowercase())
}
}
return result.toString()
}

Extension Fonksiyonları Hakkında Birkaç Bilgi

  • Extension fonksiyon yazabilmek için bize gereken şey, fonksiyona ait bir receiver sınıfın olmasıdır. Receiver, extension fonksiyonunu hangi sınıf için yazacağımızı belirtir. Extension fonksiyonunu çağrırıken bu receiver ne ise ona göre bir kullanım yaparak çağrıyoruz. Infix fonksiyonunu anlatırken orada yazdığımız extension fonksiyonuna bakalım.
class Picture

fun Picture.downloadData(url: String) {

}

Buradaki receiver, Picture’dır. Üstteki kod örneklerinde receiver, int ve string sınıflarıydı. Ek olarak Infix olabilme şartlarını sağladığı için bu extension fonksiyonunu infixte yapabiliriz, tamamen bize kalmış bir durum.

  • Extension fonksiyonlarını istediğimiz yerde tanımlayabiliriz. Fakat sınıf içerisinde tanımlarsak sadece o sınıf kapsamında kullanabiliriz. Bu yüzden dolayı extension fonksiyonlar kt. uzantılı dosyalarda top-level (dosyanın içinde olması fakat hiçbir yapının, kod bloğunun içerisinde olmaması) olarak tanımlanmalıdırlar ki her yerden erişilebilsin. Fakat ihtiyaç kapsamında sınıf içerisinde de tanımlama yapılabilir.
  • Extension fonksiyon tanımlarken o fonksiyonu infixte yapabiliriz, eğer ki infix olma şartlarını sağlarsak.
  • Extension fonksiyonlar tanımlandığında bu fonksiyonlar sınıfın kendine has bir fonksiyonu sayılmazlar. Bu yapılarla sadece . notasyonu ile çağrılabilir fonksiyonlar yazmış oluruz, sınıfta bir değişiklik olmaz.
  • Extension fonksiyonlar arka planda statik olarak oluşturulurlar. Bu statik yapı sayesinde da herhangi bir nesne oluşturma gereği duymayız. Sınıf ismi . notasyonu ile direkt çağrım yapabiliriz.
Extension fonksiyonlarının Java koduna gelen karşılığı
  • Sınıf içinde bir fonksiyonumuz olsun. Bir tane de extension fonksiyonumuz olsun (top-level veya sınıf içinde olması farketmez) ve bu extension fonksiyonu sınıf içerisindeki fonksiyon ile birebir aynı olsun (parametre sayısı, parametre tipleri, parametrelerinin sırası ve fonksiyon ismi). Bu durumda extension fonksiyon yok hükmünde sayılır. Hiçbir yerden hiçbir şekilde erişilemez. Her zaman sınıf fonksiyonu çağrılır. Eğer ki bu fonksiyon overload (üstteki yazdığımız fonksiyon özelliklerinden en az biri farklı olacak şekilde) şeklinde yazıldıysa bir problem oluşmaz.
Sınıf içindeki fonksiyon ile bu sınıfa ait extension fonksiyon birerbir aynı olmamalıdır.
  • Receiver, nullable olabilir. Bu durumda, güvenlik açısından dolayı null kontrolü yapılması iyi bir seçenektir.
class Picture 

fun Picture?.downloadData(url: String) {
if(this != null) {
//business logic
}
}
  • Kotlin’de extension fonksiyonlar olduğu gibi extension propertyler de vardır. Bu kısmı anlayabilmek için property - field kavramlarını kesinlikle biliyor olmalıyız. Bu konu hakkında bir bilginiz yoksa property vs field hakkında yazdığım yazıyı okumanızı şiddetle tavsiye ederim.
fun main() {

val picture = Picture()
println(picture.url)
}

class Picture

var Picture?.url
get() = "www.example.com"
set(value) {
}

Burada url isminde bir property oluşturduk. Bu property için get ve set fonksiyonlarını kullanabiliriz fakat bu property backing field’a sahip olmadığı için herhangi bir değer atamasını doğrudan yapamayız. Yine aynı sebepten ötürü set fonksiyonu içinde “field = value” atamasını da yapamıyoruz.

Extension propertylerin backing field’ı yoktur
  • Bir tane A sınıfımız ve bir tane de B sınıfımız olsun. Bu sınıfların içerisinde de birebir aynı fonksiyonlar tanımlamış olalım. B fonksiyonu içinde A fonksiyonuna ait bir extension fonksiyon yazabiliriz ve bu extension fonksiyonun içinden A sınıfında yazılan fonksiyonlara da erişebiliriz.
class Picture {
fun downloadPicture() {

}
}

class AnotherPicture (val picture: Picture) {
fun downloadPicture() {

}
fun Picture.extFunc(){
downloadPicture() // Picture().downloadPicture
this@AnotherPicture.downloadPicture() // AnotherPicture().downloadPicture
}

fun callExtFunc(){
picture.extFunc()
}
}

Peki ya AnotherPicture içindeki downloadPicture fonksiyonuna erişmek isteseydik? O halde etiket kullanmamız gerekiyor. this@AnotherPicture.downloadPicture() kullanımı ile içinde bulunduğumuz fonksiyonu çağrabiliriz. AnotherPicture sınıfı constructorında istenen parametreyle de extension fonksiyonunu çağrabiliyoruz.

  • Extension fonksiyonlar da diğer fonksiyonlar gibi visibility modifier’a sahip olabilir. Ayrıca override da edilebilirler.
abstract class Picture {
protected abstract fun AnotherPicture.extFunc()
}

class AnotherPicture: Picture() {
override fun AnotherPicture.extFunc() {
}
}
  • Eğer bir extension fonksiyon top-level olarak yazılmışsa ve receiver, private veya protected alanlar içeriyorsa extension fonksiyon içerisinden bu yapılara erişilemez.
Top-level extension fonksiyonlardan receiver içerisindeki private, protected yapılara erişilemez.

Extension ve infix fonksiyonları hakkında bilmemiz gereken çoğu şeyi yazmaya çalıştım. Umarım faydalı bir yazı olmuştur. Başka bir yazıda görüşmek üzere :)

Bana Linkedin üzerinden ulaşabilirsiniz.

Son Güncelleme: 22.07.2023

--

--