Kotlin Nedir? Neden Tercih Edelim?

Kotlin Temelleri

Mehmet Fethi Mese
Delivery Hero Tech Hub
9 min readAug 4, 2022

--

Bugünkü yazımda daha çok android yazılımcı arkadaşların yakından bildiği Kotlin dilini ve temellerini birlikte öğrenmeye çalışacağız.

Kotlin, 2010 yılında JetBrains firması tarafından ortaya çıkan, JVM (Java Virtual Machine) üzerinde çalışan bir programlama dilidir. İsmini Rusya’da bulunan Kotlin adasından almaktadır. Java üzerine geliştirilen bir dil olduğundan dolayı Java bilen bir yazılımcı hızlı bir şekilde Kotlin’e uyum sağlayabilir ve geliştirme yapmaya başlayabilir. Kotlin, Java gibi object oriented bir dil olup Apache 2.0 lisansı altında geliştirilmiş ücretsiz ve açık kaynaklıdır. Kodlarına link üzerinden ulaşabilirsiniz.

Peki Kotlin dili neden geliştirildi ve bize ne sağlıyor. Bu soruya Jetbrains firması “Performanstan ödün vermeden Java’dan daha özlü kodlar yazmayı sağlamak için bu dili geliştirdik” cevabını vermiştir.

Kotlin, Object Oriented bir dil olup Java ve Android ile %100 uyumlu çalışabilmektedir. Kotlin içerisinde Java, Java içerisinde ise Kotlin dilini kullanabiliyorsunuz. Kotlin dilini öne çıkaran diğer bir etken ise Google şirketinin bünyesinde bulunan Android yazılımcılara Kotlin dilini tercih etmelerini söylemesidir.

Kotlin, sadece Android için değil aynı zamanda backend geliştirmeleri için de kullanılan önemli dillerden bir tanesidir.

Kotlin’de var olan bazı önemli özellikleri listelersek.

  • Fonksiyonlar, Kotlin içerisinde first class olarak yer alır, yani int, bool, string, class gibi tip olarak tanımlanmışlardır. Bu sayede, fonksiyonları collection içerisinde kullanabiliriz veya bir fonksiyonu farklı bir fonksiyona parametre olarak geçebiliriz. Bu özellik yazılımcıya büyük bir esneklik sağlamaktadır.
  • Kotlin, Java dili ile kıyaslandığında, daha az rituel ve seremoni içerdiği için daha hızlı ve anlaşılır kod yazmaya imkan vermektedir.
  • Kotlin, class keyword’üne hiç ihtiyaç duymadan kod yazılabilen bir dil iken Java class keyword’üne mutlak ihtiyaç duyar.
  • Kotlin, mutable (değişen) ve immutable (değişmeyen) değerler oluşturmaya izin veriyor. Mutable değerler sayesinde kodlarımızı hata almaya daha kapalı hale getirebiliyoruz. Örneğin, val ile değişken tanımladığımızda immutable, var ile değişken tanımladığımızda mutable değişken tanımlamış oluruz. Yani var ile tanımladığımız değişkeni sonradan ezip değiştirebilirken, val ile tanımladığımız değişkeni ezemeyiz. val olan bir değişkeni ezmeye çalıştığımızda compile time esnasında syntax hatası alırız.
  • if ve when ifadeleri Java’da herhangi bir dönüş değeri içermezken, Kotlin’de dönüş değeri içerir ve herhangi bir değişkene atanabilir.
  • Java’da referans tipinde olan değişkenler otomatik olarak null değer içerirken, Kotlin’de null değer içermesi için özellikle soru işareti “?” ile null değer alabileceğini belirtmek gerekiyor. Bu sayede null pointer exception hatalarının önüne geçilmiş oluyor. Kotlin’de null olabilecek durumlar compile time da hata olarak karşımıza çıkıyor ve bize önceden bu hatayı giderme imkanı sunuyor. Bu sayede runtime da null durumuna bağlı süpriz bir hata ile karşılaşmıyoruz :)
  • Kotlin’de range 1..10 şeklinde veya ‘a’..’z’ şeklinde kolayca for döngüsü içerisinde kullanılabilirken, Java’da böyle bir yapı bulunmuyor.

Interfaces

Interface’ler object oriented programlamanın vazgeçilmez parçalarından biridir. Kotlin’de interface’ler Java’dan biraz daha farklı kullanılabilmektedir.

  • Kotlin’de interface’ler de default method implementasyonu yapılabilmektedir. Default implementasyona sahip fonksiyonlar kalıtım alınan sınıflar tarafından ayrıca implemente edilmek zorunda değildir.
  • Kotlin’de Interface, Class ve içerdiği methodlar default olarak public’dir fakat Java’da default olarak internal.

Yukarıdaki örnekte Time interface GoodTime sınıfı tarafından kalıtım alınmıştır ve içerisinde setTime methodu override edilerek implemente edilmiştir. Burada Java’dan farklı olarak override edilmesi Kotlin’de zorunludur.

Multiple inheritance için farklı 2 interface aynı method imzasını taşıyor ve inherit alınıyorsa bu durumda GoodTime sınıfında olduğu gibi super<Time>.setTime(time) ve super<Time2>.setTime(time) şeklinde hangi interface methodunun kullanılacağı ayrı ayrı generic olarak belirtilmelidir.

Classes

Kotlin, class kullanımını Java’da olduğundan daha elverişli bir şekilde yapmaktadır. Kotlin’de sınıflar, sınıf içerisindeki fonksiyon ve özellikleri default olarak final’dır yani inheritance alınamaz. Abstract sınıflar desteklenmeye devam etmektedir. Modifiers, sealed classes, constructors ve data classes, Kotlin’de kullanılan anahtar kelimelerdir. Aşağıda göreceğiniz şekilde sınıflar abstract, public, private, internal veya sealed olarak işaretlenebiliyor. Bir sınıfı inherit edebilmek için open anahtar kelimesini sınıfın başına yazmalıyız. Eğer sınıfımız abstract bir sınıf ise o zaman open anahtar kelimesini kullanmadan kalıtım verebiliriz. Aşağıda basit bir sınıf örneğini görebilirsiniz.

Sealed sınıflar hiyerarşi sınıflarının önüne geçmek için kullanılıyor ve daha çok “enums on steroids” şeklinde kullanılıyorlar. Aşağıda göreceğiniz gibi bir kişiye ait olayları sealed sınıfını kullanarak belirtebiliyoruz ve bunu when statement içerisinde handle edebiliyoruz. Bu sayede “enum to steroids” şeklinde iş mantığını işletebiliyoruz.

Data Classes sadece data tutmak için kullanılırlar ve herhangi bir iş fonksiyonu içermezler. Otomatik olarak equals, hashCode ve toString gibi methodları kendi içerisinde implemente edebilirler. Data sınıfları, immutable (değişmez) sınıflardır. Primary constructor en az bir parametre almak zorundadır. Open, abstrace veya sealed anahtar kelimelerini alamazlar. Diğer sınıfları extend ederek genişletebilirler. Entity sınıflarının data sınıfı olması uygundur. Data sınıfları ayrıca “copy” methodunu kendi içerisinde barındırırlar. Copy methodu sayesinde, bir sınıfı kolayca kopyalayabiliriz ve istediğimiz özelliklerini değiştirebiliriz. Aşağıdaki örnekte, data classes hashCode kontrolü yapmadığı için iki data sınıfını eşitlediğimizde eğer içerisindeki datalar aynı ise true sonucu döner. Eğer Employee sınıfı data sınıfı olmasaydı eşitlik false dönecekti. Data sınıflarının diğer bir artısı ise println(dataclass) yapıldığında sınıfa ait adres bilgisi değilde içeriğindeki özellik verileri gösterilir.

Companion Objects

Kotlin, static anahtar kelimesine sahip değildir, bu nedenle singleton nesneleri oluşturmak için companion objects kullanmaktadır. Singleton nesne oluşturmak için “object” anahtar kelimesi kullanılır. Static anahtar kelimesinin yerine “companion object” anahtar kelimesini kullanıyoruz. Objeler properties, methods ve initializers durumlarına sahipken, constructor sahip değildirler. Companion objelerin asıl kullanım yeri factory patern’dir ve static üyeleri oluşturmak ve yönetmektir.

Ayrıca aşağıdaki gibi companion nesnelerini factory pattern şeklinde kullanabiliyoruz.

Functions

Fonksiyonlar, Java’da olduğu gibi sınıfın bir parçası olmak zorunda değildir. Kotlin’de first class citizens olarak işlem görmektedirler. Yani integer, class, bool gibi bir tip olarak kullanılabiliyorlar. “fun” anahtar kelimesi ile tanımlanırlar. Default parametreleri ve named parametreleri olabilir. Var olan tiplere “extend” edilebilirler.

Yukarıdaki fonksiyon örneğini incelersek. Buradaki main() fonksiyonu Kotlinde her zaman ilk olarak çalıştırılacak fonksiyondur ve argument olarak args değişkeni ile alır. Main fonksiyonundaki args değişkenlerine değer atamak için intelij programındaki edit “configuration->program arguments” kısmına boşuk bırakarak yazabiliriz. Aşağıda file.JvmName(“ConnectFunctions”) kısmı sadece java tarafından Kotlin kodu çağrılırken kullanılmaktadır. Java tarafından bu kod ConnectFunctions.connect(“address”) şeklinde çağrılabilir. Ayrıca fonksiyonu çağırırken parametre ismini vererek Named parametresi olarak kullanabiliriz ve bu sayede parametrelerin yerlerini de değiştirerek kullanabiliriz.

Extension fonksiyonlar için static tanımlamalar yaparak ayrıca utility sınıfları oluşturmanın önüne geçmiş olur. Extension fonksiyonlar sayesinde kod okuması ve kod yazması utility fonksiyonlarına göre çok daha kolay olur. Yukarıdaki örnekte connectEx bir extension fonksiyondur ve String bir değişkene extend olmaktadır.

Infix fonksiyonlar sayesinde okuması daha kolay extension fonksiyonlar oluşturabiliriz. Yukarıda Header.plus() fonksiyonu bir infix fonksiyonudur ve kullanımı extension fonksiyondan farklı olarak dot “.” kullanmak yerine parametreler arası boşluk karakteri kullanılarak çağrılabiliyor. Ayrıca operator Infix fonksiyonu ile operator tiplerini de override edebiliyoruz. Operator infix kullanırken dikkatli olmalıyız. Yukarıdaki örnekte, val h5 = h1 + h2 operator infix fonksiyonu olan Header.plus() fonksiyonu çağrılmıştır.

Tail Recursive fonksiyonlar çok kullanışlıdırlar ama büyük rakamlardan bahsettiğimizde stack over flow hatalarıyla karşılaşmamız muhtemeldir. Yukarıdaki örnekte fib() fonksiyonu rakamın büyüklüğünden dolayı normalde stack over flow hatası alması gerekirken tailrec keyword ile bu hatanın önüne geçerek sonuç dönülebilmiştir.

High Level Functions

Fonksiyonların first class citizens olması sayesinde, bir fonksiyona farklı bir fonksiyonu parametre olarak tanımlayabilir ve geri dönüş değeri alabiliriz. Fonksiyonları, collection içerisinde store ederek strategy pattern rahatlıkla uygulayabiliriz. Aşağıda strategy patternin uygulandığı örneği inceleyebilirsiniz.

Higher Order Functions

Bir fonksiyonun başka bir fonksiyonu argument olarak alıyor olmasıdır. Aşağıdaki örnekte “->” ifadesi fonksiyonun dönüş tipini göstermektedir. Unit anahtar kelimesi ise void yani fonksiyon değer dönmeyeceği anlamına gelmektedir. Aşağıdaki örnekte görüleceği üzere iki farklı şekilde higher order fonksiyonu tanımlanabilmektedir. Birincisi, calc fonksiyonunda parametreler süslü parantez içerisinde tanımlanırken ikincisinde calc2 fonksiyonunda parametreler ayrıca belirtilmiştir.

Lambda fonksiyonları anonymous sınıfa map edilmektedir bu nedenle her seferinde extra sınıf ve method oluşturulmaktadır. Sonuç olarak, memory kullanımı açısından pahalı ve maliyetli bir işlemdir. Buradaki maliyeti düşürmek için inline anahtar kelimesi kullanılmaktadır. Aşağıdaki kodu inline anahtar kelimesi olmadan java koduna dönüştürdüğümüzde maliyet fazla çıkarken inline anahtar kelimesini eklediğimizde daha az maliyetli olmaktadır. Ayrıca, tüm kotlin collection operasyonları (map, filter, etc) inline edilebilmektedir. Fakat sequences inline edilememektedir, çünkü collection memory’de sonradan işlem görmektedir.

With ve Apply

Belirli operasyonları daha doğal yolla yapmayı sağlar. Kotlin’e ait bir anahtar sözcük olmasa da sanki dile ait bir anahtar kelime gibidir. Aslında arka planda lamda fonksiyonu kullanmaktadırlar. Aşağıdaki örnekte Meeting sınıfı with ve apply kullanılarak daha okunur bir şekilde initialize edilmektedir. Ayrıca apply ile initialize edilen nesnenin bir methodunu sonrasında çağırabiliyoruz. Aşağıda apply sonrası create() methodunun çağırıldığını görebilirsiniz.

Functional Style and Collections (Filter, Map, Predicate)

Kotlin’de kod yazımını daha basit hale getirmek için kullanılan kütüphane fonksiyonları bulunmaktadır. Bunlardan filter methodu collection’lardan istenmeyen elemanları çıkarmak için kullanılmaktadır. Sql’de where ifadesine benzetebiliriz. Map methodu ise collection içerisindeki elemanları dönüştürmek için kullanılmakdadır. Sql’de select ifadesine benzetilebilir.

Predicate lamda fonksiyonunun belirli dönüş değeri almasını sağlayan bir extension fonksiyondur. all, any, count ve find methodları predicate methodlarıdır.

FlatMap ise Kotlin’de bir collection içerisinden farklı bir collection return etmek için kullanılmaktadır. Örneğin, aşağıda Meeting sınıfı içerisinde tanımlanan people collection değişkenini almak ve for içerisinde ekrana basmak istediğimizda flatmap() fonksiyonunu kullanarak kolayca yapabiliriz. distinct() methodu hashMap durumuna göre işlem yaptığı için Person sınıfını data class olarak tanımladık bu sayede veri çoklamasının önüne geçmiş olduk.

Nullability

Java veya C# gibi programlama dilleri null hatası durumunda NullPointerException fırlatmaktadırlar. Bunu engellemek içinse kod yazarken çok dikkatli olmamız ve bu durumlara karşı önleyici kodlar yazmamız gerekmektedir. Kotlin nullable tipleri de desteklemektedir ama sadece explicit değişkenler null olabilmektedirler. Kotlin’de bir değişkenin veya sınıfın null değer almasını istiyorsak tip isminden sonra “?” işareti ile özellikle belirtmek durumundayız yoksa o değişken null alamaz. Null değer alan bir değişkeni safe call “?.” operatorunu kullanarak exception durumundan kurtarabiliyoruz. Bir diğer operator ise elvis “?:” , bu operatoru kullanarak eğer null ise farklı bir değer ata diyebiliyoruz. Elvis operatoru null-coalescing (null birleştirme) operatoru olarak da bilinir. Sonuç olarak Elvis ya değer döner yada null döner. Diğer bir operator ise safe cast “as?” operatorudur. Safe cast operatoru null değer alabilen bir değişkeni bir tipe cast etmek için kullanılır. Sonuç olarak ya tipe cast eder yada null döner. Not-Null assertions “!!” operatoru is nullable bir değişkenin kesinlikle null almayacağını söyleyerek compile hatasından kurtulmak için kullanılır. Eğer herhangi bir durumdan dolayı değişkenimiz null alırsa o zaman NullPointerException hatası fırlatılır. Hata durumu oluşabileceğinden dolayı Not-Null assertions operatorunun kullanılması çok önerilmemektedir. Let lambda fonksiyonu sayesinde sadece null olmayan durumlarda işlem yapılmasını sağlayabiliyoruz. Lateinit anahtar kelimesini ise bir değişkene default değer atamak istemediğimiz durumlarda kullanabiliyoruz. Ama lateinit ile belirlenmiş değişkeni kullanmadan önce mutlaka bir değer atadığımızdan emin olmalıyız yoksa hata ile karşılaşırız.

Annotations

“@Nullable,@NotNull, javax.annotation, android.support.annotation, org.jetbrains.annotation” annotations Kotlin içerisinde kullanılabilmektedir. Kotlin içerisinde Java fonksiyon ve sınıflarını çağırırken buradaki annotations’lar Java içerisinde kullanılarak hataların önüne geçilmeye çalışılır. Annotation’ların kullanılmasının amacı Java ve Kotlin arasındaki mutable ve immutable durumların önüne geçmektir.

Collections

Collections null değer alabilirler ve null değer tutabilirler. Collections read-only veya mutable (değişebilir) özellik gösterebilirler. Kotlin, collections ve Java collections birlikte çalışabilirler. Yani Java’da olan bir collection Kotlin tarafından veya Kotlin’de buluna collection Java tarafından kullanılabilir. Kotlin’de bulunan collection tipleri listOf, setOf, mapOf şeklindedir. Explicit read-only collection olarak arrayListOf, hashSetOf, hashMapOf tipleri kullanılırken, collection’a yeni değer atama update etme gibi değişkenlik gösteren durumlarda mutableListOf tipi kullanılır.

Sequences

Büyük liste elemanlarının var olduğu durumlarda filter ve map kullanmak ciddi performans sorunlarına yol açmaktadır. Bu nedenle sequences geliştirilmiştir. Sequences nesne içeriğini direkt olarak memory’e allocate etmek yerine collection’a ait özellik ve yapıyı tutmaktadır. asSequence() methodu toList() methodu çağrılmadığı sürece veya collection for içerisinde iterate edilmediği sürece evaluate edilmemektedir ve memory’e veriler yüklenmemektedir. Yani sequences lazy evaluated bir işlemdir. Evaluation toList() gibi terminal operasyonu kullanıldığında başlamaktadır.

Generics

Generics çoklanmış edilmiş kodların önüne geçmek ve daha yönetilebilir bir kod yapısına sahip olmak için kullanılırlar. Kotlin kütüphanesinde collection’ları yönetmek için kullanılan listOf(), mutableListOf<>(), List<> tipleri generic olarak tanımlanmışlardır. Kotlin’de fonksiyonu veya bir sınıfı generic olarak tanımlayabiliyoruz. Bazı durumlarda generic tiplerine kısıtlama getirebiliriz.

Aşağıdaki örnekte Node<T: Number> T type değerini Number olarak kısıtladığımız için bu tip sadece Number tiplerini yani Int, Double gibi değerleri alır yoksa compile hatası ile karşılaşırız.

Java’da generic tiplere ait bilgiler runtime esnasında tamamen silinirken Kotlin’de bu işlem Reify edilir yani somutlaştırılır. Bu nedenle generic tiplerini run time esnasında kontrol edemeyiz. Örneğin items is List<String> ifadesinden runtime da hata alırız çünkü List<String> generic ifade runtime esnasında silinmiştir. Aynı şekilde items as List<String> şeklinde cast işlemini de runtime da yapamayız. Bu durumları çözmek için Kotlin’de sadece inline fonksiyonlar olmak üzere reified olarak tipi işaretleyebiliriz. Bazı durumlarda da reified olan bir tipin noinline olarak işaretlememiz gerekebilir. Aşağıdaki kod örneklerini inceleyebilirsiniz.

Types ve Sub-Types konusuna gelirsek. Type’lar kendi içerisinde bir ilişkiye sahiptir. Örneğin, Student sınıfının Person sınıfından kalıtım aldığını düşünürsek, Student tipi Person tipinin bir sub-type’ıdır. Generic tiplerde ise bu durum daha da karmaşıktır. Örneğin, List<Student> tipi List<Person> tipinin bir sub-type’dır.

Kotlin variance durumlarına sahiptir. Variance durumları için “in” ve “out” anahtar kelimeleri kullanılır. Yani in ve out anahtar kelimelerini mutable olan sınıf veya fonksiyonumuzu immutable hale getirmek için kullanıyoruz. In anahtar kelimesi kullanılırsa o parametre sadece o fonksiyon içerisinde kullanılabilir. Out anahtar kelimesine bağlı olan parametre ise sadece return edilmek için kullanılabilir.

Diğer taraftan in ve out anahtar kelimelerini üst seviye bir tip alan parametre yerine temel seviye bir tipi gönderebilmek için kullanıyoruz. Out anahtar kelimesi covariant durumları gösterirken, In anahtar kelimesi contravariant durumları gösterir. Aşağıdaki örnekte, attendAllMeetings() fonksiyonu temel sınıf olan Meeting sınıfını parametre olarak beklerken FinanceMeeting’i out anahtar kelimesi sayesinde gönderebiliyoruz.

Variance sayesinde mutable (değişen) listeler öngörülebilir durumda olurlar.

Conclusion

Gördüğünüz gibi Kotlin yazması ve anlaması rahat bir dil. Gerek dilin yalınlığı gerekse hızlı geliştirme yapılabiliyor olması Kotlin’i ön plana çıkarmaktadır. Ayrıca, Java’nın hataya açık noktalarının güzel bir şekilde kapatılmasıyla çok güzel bir dil olmuş. Bu yazımı hazırlarken Kevin Jones’un Kotlin temel eğitim videolarından büyük oranda faydalandım. Bu eğitime link’inden erişebilirsiniz. Ben genel olarak Kotlin’i çok beğendim umarım sizde beğenmişsinizdir. Bu yazımda anladığım kadarıyla ve bilgim yettiğince Kotlin temellerinden bahsetmeye çalıştım.

Bir sonraki yazımda görüşmek dileğiyle…

--

--