Kotlin Nedir? Java ile Farkları Nelerdir?
Android geliştirimi yıllardır Java ile devam etmektedir. Geliştiriciler de Java diline diğer platformlardan aşina oldukları için kolayca adapte olabildiler. Fakat Oracle’ın Java’yı satın alması ve Google’a dava açmasından sonra Google, Android ilgili programlama operasyonlarını yavaş yavaş Kotlin’e kaydırmış durumda. Google Trends’te de yavaş ama kararlı bir artış söz konusu:
Stackoverflow’da da ilk çıktığında çok az kaynak soru bulunmasına rağmen son 2 yıldaki artış muazzam görünüyor:
Google’ın Kotlin’e iyi bir yatırım yaptığı açık. Peki Kotlin dilini Java dilinden farklı kılan nedir?
Kotlin ile Java karşılaştırması
Öncelikle Kotlin’e ilk bakışta noktalı virgül karakterinin opsiyonel oluşu ve Java’nın açıklayıcı (verbose) yazımın aksine daha kısa kodla ayni işin yapılması göze çarpıyor. Bunun haricinde null kontrolü işlemleri Swift ve C# diline benzer bir şekilde operatörlerle gerçekleştirilmesi sağlanmış. Dilerseniz iki dilin ayrımına yakından bakalım:
Java’daki bazı problemler giderilmiştir.
Java’dan aşina olduğumuz null hataları gibi bazı problemler giderilmiş görünüyor. Bunlardan bazıları aşağıdaki başlıklarda yer almaktadır.
Belirtilmedikçe Null ataması yapılamamaktadır
Null referansını icat eden Tony Hoare’nin de dediği gibi null referanslarının keşfi milyar dolarlık bir hata teşkil etmektedir. Kotlin’deki tip sistemi de kod içerisindeki null referanslardan gelen tehlikeleri engellemek için tasarlanmıştır. Bu sayede direkt olarak bir değişkene null ataması yapılamaz.
Java ile geliştirim yaparken aşina olduğumuz NullPointerException
hataları Kotlin’in tip sistemi ile giderilmeye çalışılmıştır. Kotlin’de her nesne null olmamak zorunda olduğu için eğer bir null hatası alıyorsanız büyük ihtimalle aşağıdakilerden birini gerçekleştiriyorsunuz demektir:
- Açık ve belirgin bir şekilde kodunuzda
throw NullPointerException()
varsa, - Nullable bir değişkenin içeriği null ise ve
!!
operatörü ile veri almaya çalışıyorsanız, - Constructor’da bulunan atanmamış this değişkeni varsa ve bu değişkeni başka bir metodda kullanıyorsanız,
- Türetilen sınıfın constructor’ında, ana sınıfın atanmamış değişkenini kullanıyorsanız,
- Java API’sinden kaynaklı tiplerde kullanılan null atamaları ve kullanımları gerçekleştiriyorsanız null hatası almanız oldukça doğaldır.
Kotlin’de tip sistemi o kadar iyi ayarlanmış ki null değişkenler kullanmadan kodunuzu yazmanızı zorlar. Örneğin verisi atanmış ve null olamayan bir değişkene null atamaya çalıştığınızda aşağıdaki gibi bir derleme hatası alırsınız:
Null olabilecek (Nullable) bir değişken oluşturmak için ?
operatörü ile belirtmek gerekir:
Kotlin’in en güzel özelliklerinden birisi de kodunuzu null kontrolleriyle doldurmadan yazabiliyor olmanız. Aşağıdaki gibi null olmaması garanti bir nesnenin özelliğine erişmeye çalıştığınızda hiçbir problem olmaz:
Fakat daha önce null olarak atadığımız b değişkeninin length
özelliğine erişmeye çalıştığımızda derleme hatası verir. Bu özelliği ? operatörü (safe check) ile yazdırmamız gerekir:
Null kontrolleri ile ilgili daha detaylı bilgi için buraya bakabilirsiniz.
Java’daki raw (ham) tipler Kotlin’de yer almaz
Java’da raw tipler List
gibi içerisinde argüman tipinin ne olduğunu belli etmeyen yapılardır. Örnek:
Bu durumun doğurduğu bazı problemler vardır:
- Genellikle değişken kullanılırken tip dönüşümü (casting) gereklidir.
- Type-safe olmadığı için, tiplerle ilişkili bazı hatalar uygulamanın çalışma zamanında ortaya çıkar.
- Raw tipler ile yazılan kodlar parametreli tipler kadar okunaklı değildir ve kod kendini iyi bir şekilde ifade etmez.
Halbuki List<String>
şeklinde bir atama yapıldığında, ilgili listenin içerisinde String değişkenler olduğu ifade edilir ve çalışma zamanı doğabilecek hatalar, derleme zamanında engellenmiş olur.
Kotlin’de bu durumdan kaçınılmak için ilgili ifadenin tipinin belirtilmesi zorunlu hale gelmiştir. Aksi halde aşağıdaki gibi bir durumda derleyici hata vermektedir:
Kotlin’in Java ile çalışabilmesi için*
operatörü kullanılır. Örneğin List<*>
şeklinde tanımlama yapmak gereklidir. Java ile ortak çalışma hakında detaylı bilgi için buraya bakabilirsiniz.
Kotlin’deki Array’ler invariant (değişmez) yapıdadırlar
Öncelikle invariant (değişmez) kavramını açıklamak için tersi olan covariant (değişken) kavramından da bahsetmek gerekir. Eğer bir class (örneğin Integer
), diğer bir class’tan (örneğin: Object
) türetilmişse türetilen class yerine base class (Object) kullanılabilir. Java’daki array’ler varsayılan olarak covariant yapıdadırlar. Örnek:
Array’lerin covariant olmasındaki problem, tip uyuşmazlığı sonucu çıkan çalışma zamanı hatalardır. Generic’lerde bu problem derleme zamanında giderilmiştir. Örneğin List
için:
İki yöntemde de Integer
içeren bir container içerisine String
bir ifade atanamaz. Fakat aradaki fark, array’lerde hata çalışma zamanında, generic’lerde ise derleme zamanında ortaya çıkar. Hatta Effective Java, 3rd Edition kitabında Item 28'de Prefer lists to arrays denilerek array yerine liste kullanımının gerektiği ifade edilmiştir.
Java’dan ders alınarak, Kotlin’deki array’ler invariant (değişmez) olarak tasarlanmıştır. Yani Array<Int>
tipindeki bir değişken, Array<Any>
tipindeki bir değişkene atanamaz. Bu sayede çalışma zamanında çıkacak olası bir hata, derleme zamanında engellenmiş olur:
Kotlin’de fonksiyon tipleri için SAM-dönüşümüne ihtiyaç yoktur
Java 8'de gelen SAM (Single Abstract Method) sayesinde Callable, Runnable gibi interface’ler kullanılarak fonksiyon tipleri oluşturulabilir. Örneğin:
Kotlin’de ise böyle bir SAM pattern’ına gerek yoktur:
Fonksiyonel tipler hakkında daha fazla bilgi için buraya bakabilirsiniz.
Use-site variance yerine declaration-site variance kullanılır
Variance (varyans) kavramı aslında bir sınıfın diğer bir sınıfa cast edilmesi gibi düşünülebilir. Örneğin, Kisi
sınıfından türetilen her bir Ogrenci
sınıfı nesnesi Kisi
sınıfı olarak kullanılabilir:
Variance’ın 3 çeşidi bulunmaktadır: covariance, contravariance, invariance. Declaration-site variance’dan bahsetmeden önce Java’da bu variance tiplerinin use-site olarak nasıl kullanıldığını açıklayalım:
Covariance
Covariance (kovaryans), üstte belirttiğimiz gibi kalıtım ağacındaki alt sınıfların bir üst sınıflara dönüştürülebilmesi işlemidir. Covariance’ta üst sınıftan alt sınıfa dönüşüm yapılmak istendiğinde hata verir:
Generic’lerde ise aşağıdaki gibi bir kullanım söz konusudur:
? extends
wildcard’ı ile listenin içereceği elemanlar belirtilir. Öğrenci listesinden Kişi içeren listeye dönüşüm bu wildcard kullanılarak gerçekleştirilir. Dönüşüm gerçekleştikten sonra üst tipe (Öğrenci tipine) dönüştürme işlemi hata verir.
Contravariance
Contravariance (kontravaryans), covariance’ın tam tersidir. Her Kisi
nesnesinin, Ogrenci
nesnesi olarak dönüştürülmesi gibi düşünebilirsiniz. Java’da böyle bir kalıtım türü bulunmamaktadır. Fakat generic’lerde contravariance özelliği sağlanır:
Covariance’larda benzer olarak gibi burada da ? super
wildcard’ı kullanılarak contravariance list özelliği sağlanır. Örnekteki insanlar listesinde, listenin barındırdığı alt tipin ne olduğu bilinmediği için get işlemlerinde dönüşüm işlemi belirtilmediği sürece hata verecektir.
Invariance
Invariance (değişmezlik), Java’daki generic’lerde yer almaktadır. Örneğin Integer
sınıfı Object
sınıfından türediği halde birList<Integer>
değişkeni List<Object>
değişkeninin alt tipi değildir.
Java’da generic’lerin invariant olması sayesinde çalışma zamanında oluşabilecek hatalar giderilmiş olur.
Declaration-site variance
Java’da use-site variance’da kullanılan wildcard’ların aksine generic sınıfı tanımlanırken C#’taki gibi in
ve out
keyword’lerinin kullanılmasıdır. Öncelikle Java’da use-site variance’dan dolayı oluşan problemi inceleyelim:
Burada ValueProducer<T>
adlı basit bir generic sınıfımız var. ValueProducer<Object>
nesnesine ValueProducer<String>
ataması yapılmaya çalışıldığında derleyici hata verir. Halbuki Object bir değere String bir ifade atama durumu gayet normal bir durumdur. Bunun çözümü için ? extends wildcard’ı gerekmektedir. Bu problem Kotlin tarafında wildcard kullanmaksızın out parametresi ile çözülmüştür:
T
parametresininout
olarak tanımlanması ile T tipi artık covariant karaktere sahip bir hale gelmiştir. Bu durumun contravariant tarafı için de in
ifadesi kullanılır. Detaylı bilgi için bkz: https://kotlinlang.org/docs/reference/generics.html
Java’daki zorunlu checked exception’lar yoktur
Checked exception’lar (kontrollü exception’lar), throw edilen bir exception’ın kullanan bir fonksiyonda handle edilmesinin zorunlu tutulması halidir. Kodun derleme zamanında kontrol edildiği için bu adı almışlardır. Java’daki StringBuilder tarafından implement edilen Appendable interface’i aşağıdaki gibidir:
Java’daki Writer class’ı da Appendable sınıfından türetilmiştir ve bu sınıfta da append metodu kullanırken IOException handle edilmesi zorunludur. Yazılımcı bu exception’ı catch bloğuna hiçbir kod yazmadan yalancı bir şekilde handle edebilir:
Bu şekilde Exception’ın ignore edilmesi iyi bir yöntem değildir ve Effective Java kitabında da “Dont ignore exceptions” diyerek catch bloğunun boş bırakılmasının kötü bir yöntem olduğu dile getirilmiştir.
Sonuç Olarak
- Null için yeni bir tip sistemi oluşturulmuştur. Bu sayede derleme zamanında null hataları tespiti sağlanmıştır.
- List yerine List<String> gibi kullanım zorunlu tutulmuştur. int içeren bir List’e String ataması derleme zamanında engellenmiştir.
- Değişmez array yapıları ile farklı tipteki array’lerin birbirine atanması derleme zamanında engellenmiştir.
- SAM interface’lerinin kullanılması yerine lambda ifadelerle fonksiyon tiplerinin oluşturulması sağlanmıştır.
- Wildcard operatörler kullanılmadan use-site covariance sağlanmıştır.
- Checked exception’ların yer almaması sayesinde, exception’ların ignore edilmesi problemi giderilmiştir.
Eğer bu yazı ile ilgili soru ve düşünceleriniz varsa aşağıda yorum kısmından yazabilirsiniz. Teşekkür etmeniz için alkış butonuna basmanız yeterli. Sonraki yazımda Kotlin’in getirdiği güzel yeniliklerden bahsedeceğim. Görüşmek üzere 👋