Kotlin’de vararg Kullanımı

Ömer Sungur
5 min readJul 20, 2023

Kotlin’de vararg ifadesinin ne olduğunu ve nasıl kullanıldığını inceleyeceğiz. Sadece ne işe yaradığını değil aynı zamanda farklı kullanımlarını görerek, performans farklılığı oluşturan detaylara göz atacağız.

vararg Ne Zaman Kullanılır?

Bir fonksiyon yazıp o fonksiyonun parametrelerini tanımlarken öyle durumlarla karşılaşırız ki kaç tane parametre yazacağımızı bilemeyiz. Belki de kullanıcı bir işlem yapmadığı takdirde hiçbir zaman kaç parametre yazmamız gerektiğini bilemeyeceğiz.

Örneğin bir fonksiyon yazalım ve bu fonksiyon iki tane int tipinde parametre alsın. Bu fonksiyonunun amacı bu 2 sayıyı toplayıp dışarıya return etmek olsun. Peki ya kullanıcıdan 3 tane değer isteyecek olursak? O halde overload işlemi yaparak bir fonksiyon daha yazarız ve bu sefer 3 parametre almasını sağlarız. Sonra o 3 değeri toplayıp return ederiz. Ya kullanıcı 4 tane değeri toplatmak isteseydi? Bu durum böyle uzayıp gider. Bütün durumlar için fonksiyon yazmak inanılmaz kötü bir senaryo olur. İşte tam olarak burada vararg devreye giriyor.

Bir fonksiyon yazdığımızda eğer o fonksiyonun parametresine kaç tane değer verileceğini tam olarak bilmiyorsak (üstteki örnekteki gibi) vararg (variable number of arguments) kullanmalıyız.

Kötü senaryo olarak overload işlemleriyle fonksiyonlarımızı oluşturalım. Sonra da vararg ile oluşturalım ve farkı kodlarda da görelim.

fun main() {
println(calculateSum(4,5))
println(calculateSum(3,4,5))
}

fun calculateSum(number1: Int, number2: Int): Int {
return number1 + number2
}

fun calculateSum(number1: Int, number2: Int, number3: Int): Int {
return number1 + number2 + number3
}

fun calculateSum(number1: Int, number2: Int, number3: Int, number4: Int): Int {
return number1 + number2 + number3 + number4
}

fun calculateSum(number1: Int, number2: Int, number3: Int, number4: Int, number5: Int): Int {
return number1 + number2 + number3 + number4 + number5
}

...
...
...
...
...
...

Fonksiyonları overload ederek bu şekilde bize lazım olan yapıları oluşturabiliyoruz. Bu arada overload terimi, fonksiyonların isimlerinin aynı kaldığı ama bu fonksiyonlara ait parametrelerinin ya tipleri ya da sayılarının farklı olması anlamına gelir. Burada binlerce senaryo olduğunu düşünecek olursak her bir fonksiyon için bu işlemi yapmanın hiçbir anlamı yoktur. Bir de vararg kullanarak yazalım.

fun main() {
println(calculateSum(5,10,20,50,70,90,50,20,50,90,100,5))
println(calculateSum(5,10,20))
}

fun calculateSum(vararg numbers: Int): Int {
var sum = 0

for (i in numbers) {
sum += i
}

return sum
}

Farkı görüyor musunuz? vararg ifadesini kullandık ve 12 tane sayıyı parametre değeri olarak gönderebildik. İstediğimiz sayı da değer gönderebiliriz. vararg’ın ne olduğunu öğrendik şimdi teknik detaylarına geçelim.

  • Aynı fonksiyon parametresinde sadece bir tane vararg kullanılabilir.
bir fonksiyon sadece bir tane vararg parametresine sahip olabilir

Burası yanlış anlaşılmasın, sadece 2 tane vararg kullanımına izin verilmiyor. vararg kullanılan fonksiyonda başka parametreler de tanımlayabiliyoruz (vararg olmadan).

vararg ve vararg olmayan parametreler aynı fonksiyon parametresinde tanımlanabilir
fun nameFunc(vararg a: String, b: Int) {

}

fun nameFunc2(b: Int, vararg a: String) {

}

fun main() {
nameFunc("Omer,Mehmet", b = 5)
nameFunc2(5,"Omer,Mehmet")
}

Bu ikili kullanımda eğer vararg sondaysa named argument kullanmak zorunda kalmayız. Yani parametrelerin isimlerini belirtmemiz gerekmez. Fakat vararg yapısı baştaysa diğer parametrelerin isimlerini yazmamız gerekiyor çünkü vararg değerlerinin nerede bittiğini IDE anlayamaz.

  • vararg diye bildiğimiz şey aslında bir arraydir. Bu yüzden dolayı bir parametreye birden fazla değer tanımlayabiliyoruz. Bu parametreye, arraylere erişim sağladığımız gibi ulaşabiliriz.
fun printNames(vararg names: String) {
println(names[0])
}

names parametresi bir dizi olduğu için indis değerleriyle o indise ait değeri kullanabiliriz. Fakat bunun kullanılması tehlikelidir çünkü erişim sırasında dizinin indis elemanından daha yüksek bir ifadeye erişirsek, dizilerle ilgili en çok rastlanan hata olan ArrayIndexOutOfBoundsException hatasını alırız.

fun main() {
println(printNames("Omer, Ahmet"))

}

fun printNames(vararg names: String) {
println(names[2])
}

Sadece 2 değer oluğu için burada kullanabileceğimiz indis değerleri 0 ve 1'dir. names[2] ifadesi ile hata alırız.

fun printNames(vararg names: String) {
println(names[0])
}

fun main() {
printNames(*arrayOf("Omer", "Ahmet"))
}

String vararg yapılarına değer tanımlarken *arrayOf ifadesiyle de bir kullanım yapılabilir.

  • Elimizde liste tipinden bir değişken varsa bunu da vararg parametresine dahil edebiliriz. Sadece vararg parametresine uygun bir tip dönüştürme işlemi yapmamız gerekiyor.
fun calculateSum(vararg numbers: Int): Int {
var sum = 0

for (i in numbers) {
sum += i
}

return sum
}

fun main() {
val array = arrayOf(1,2,3,4,5,6,7,8,9,10)
println(calculateSum(11, 17,25, *array.toIntArray(), 30))
}

arrayOf değil de direkt olarak val array = intArrayOf(1,2,3,4,5,6,7,8,9,10) şeklinde tanımlama yapsaydık üstteki gibi bir tip dönüşümü yapmamıza gerek kalmazdı. println(calculateSum(11, 17,25, *array, 30)) şeklinde kullanabilirdik. * ile o ifadenin vararg tipinde olduğunu işaretliyoruz ve böylece dizinin elemanları ayrı ayrı argümanlar olarak fonksiyona geçiriliyor.

Son olarak bilmemiz gereken şey, Java’da vararg tanımlanırken … (üç nokta) ifadesi ile tanımlanıyor. Kotlin, Java ile aynı makine kodunu üretiyor ve vararg’ın kullanıldığı yere göre arka planda daha farklı bir makine kodu üretimi söz konusudur.

  • vararg yapısı, tek parametre olarak kullanılırsa veya birden çok parametre arasından en sonda olacak şekilde yazılırsa, bu arka planda … ifadesi şeklinde oluşturulacaktır. Diğer durumlarda [ ] tipinde oluşturulacak. Örneklerini inceleyelim. 5 tane vararg parametresi alan fonksiyon tanımlayalım.
fun func(vararg a: Int, b: Int) { // vararg başta veya ortalarda kullanılırsa int[] tipine denk gelir

}

fun func2(b: Int, vararg a: Int) { // Eğer vararg son parametreyse bu Java'daki int... tipine denk gelir

}

fun func3(c: Int, vararg a: Int, b: Int) { // int[]

}

fun func4(vararg a: Int) { // int... tipine denk geliyor tekli kullanımda.

}

fun func5(vararg a: String) { // String...

}

// ... tipler [] tiplerine göre daha performanslıdır fakat göz ardı edilebilir bir farktır.

Bu fonksiyonların Java koduna decompile edelim ve çıktılarına bakalım. Bunun için shift+shift kısa yolu açılan pencerede “show kotlin bytecode” yazalım ve gerekli aracı açalım. Sağ ekranda açılan pencerede assembly kodlarını görüyoruz. Bu pencerenin üst taraflarında decompile butonuna tıklayalım ve bütün kodlarımızı Java koduna dönüştürelim. Açılan pencere tam olarak aşağıdaki gibidir.

Kotlin decompile

Bu farklı kullanımları performans farkından dolayı anlattık. int [] ve int… arasında küçükte olsa bir performans farkı vardır. int… yapısı daha performanslıdır fakat bu göz ardı edilebilecek bir farktır. Fakat yine de vararg kullanacaksak bu yapıyı ya tek başına ya da birden fazla parametre varsa en sonda kullanmak daha uygundur.

Bana Linkedin üzerinden ulaşabilirsiniz.

Bu kaynağı hazırlarken, videosundan faydalandığım Gökhan Öztürk’e (KeKod) teşekkür ederim.

Son Güncelleme: 30.08.2023

--

--