Kotlin Nedir? Java ile Farkları Nelerdir?

Zafer Ayan
6 min readDec 21, 2018

--

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:

Son 3 yıla ait “kotlin” içeren Google aramaları

Stackoverflow’da da ilk çıktığında çok az kaynak soru bulunmasına rağmen son 2 yıldaki artış muazzam görünüyor:

Stackoverflow’da Kotlin etiketine sahip soruların yıl bazında diğer sorulara göre oranları

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ı

Örnek Kotlin kodu

Ö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’nın aksine Kotlin null’a karşı bir yapıda bulunmaktadır.

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

Kotlin’de liste tipleri atama esnasında tanımlandığı için tekrar cast etmeye gerek yoktur.

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

Array<Int> tipindeki bir değişken, Array<Any> tipindeki bir değişkene atanamaz.

Ö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 Listiçin:

İki yöntemde de Integer içeren bir container içerisine Stringbir 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

Kotlin’de SAM (Single Abstract Method) (functional interfaces)’a gerek 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

Java’da değişken oluşturma zamanında kullanılan ? super ve ? extends wildcard’ları yerine, Kotlin’de class tanımında in ve out parametrelerle variance sağlanı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:

Her Ogrenci ve Ogretmen nesnesi aslında bir Kisi nesnesi olduğu için casting işlemi hata vermez. Bu duruma variance denir.

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’ı sayesinde List elemanları arası kalıtım aktarılabilir.

? 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:

Contravariance özelliği sayesinde List elemanları arası kalıtım ters yönlü olarak sağlanabilmektedir.

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 Integersınıfı Objectsı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 inve outkeyword’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:

Tparametresininoutolarak tanımlanması ile T tipi artık covariant karaktere sahip bir hale gelmiştir. Bu durumun contravariant tarafı için de inifadesi kullanılır. Detaylı bilgi için bkz: https://kotlinlang.org/docs/reference/generics.html

Java’daki zorunlu checked exception’lar yoktur

Java’da Method1()’in throw ettiği tüm exception’lar Method2() tarafından handle edilmek zorundadır.

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 👋

--

--

No responses yet