Kotlin'e göre yeniden düzenleme

Bu codelab'de kodunuzu Java'dan Kotlin'e nasıl dönüştüreceğinizi öğreneceksiniz. Ayrıca Kotlin dil kurallarının neler olduğunu ve yazdığınız kodun bu kurallara uyduğundan nasıl emin olacağınızı öğreneceksiniz.

Bu codelab, projesini Kotlin'e taşımayı düşünen ve Java kullanan tüm geliştiriciler için uygundur. IDE'yi kullanarak Kotlin'e dönüştüreceğiniz birkaç Java sınıfıyla başlayacağız. Ardından, dönüştürülen koda göz atıp kodu daha idiomatik hale getirerek ve yaygın sorunlardan kaçınarak nasıl iyileştirebileceğimizi göreceğiz.

Neler öğreneceksiniz?

Java'yı Kotlin'e dönüştürmeyi öğreneceksiniz. Bu alıştırmayı yaparken aşağıdaki Kotlin dil özelliklerini ve kavramlarını öğreneceksiniz:

  • Null değer alabilme durumunu işleme
  • Tekil öğeleri uygulama
  • Veri sınıfları
  • Dizeleri işleme
  • Elvis operatörü
  • Yapı bozma
  • Özellikler ve destekleyici özellikler
  • Varsayılan bağımsız değişkenler ve adlandırılmış parametreler
  • Koleksiyonlarla çalışma
  • Uzantı işlevleri
  • Üst düzey işlevler ve parametreler
  • let, apply, with ve run anahtar kelimeleri

Varsayımlar

Java'yı zaten biliyor olmanız gerekir.

İhtiyacınız olanlar

Yeni proje oluşturma

IntelliJ IDEA kullanıyorsanız Kotlin/JVM ile yeni bir Java projesi oluşturun.

Android Studio kullanıyorsanız Etkinlik içermeyen yeni bir proje oluşturun.

Kod

User model nesnesi ve User nesneleriyle çalışan, kullanıcı listelerini ve biçimlendirilmiş kullanıcı adlarını gösteren bir Repository tekil sınıfı oluşturacağız.

app/java/<yourpackagename> altında User.java adlı yeni bir dosya oluşturun ve aşağıdaki kodu yapıştırın:

public class User {

    @Nullable
    private String firstName;
    @Nullable
    private String lastName;

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

}

Proje türünüze bağlı olarak, Android projesi kullanıyorsanız androidx.annotation.Nullable, aksi takdirde org.jetbrains.annotations.Nullable öğesini içe aktarın.

Repository.java adlı yeni bir dosya oluşturun ve aşağıdaki kodu yapıştırın:

import java.util.ArrayList;
import java.util.List;

public class Repository {

    private static Repository INSTANCE = null;

    private List<User> users = null;

    public static Repository getInstance() {
        if (INSTANCE == null) {
            synchronized (Repository.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Repository();
                }
            }
        }
        return INSTANCE;
    }

    // keeping the constructor private to enforce the usage of getInstance
    private Repository() {

        User user1 = new User("Jane", "");
        User user2 = new User("John", null);
        User user3 = new User("Anne", "Doe");

        users = new ArrayList();
        users.add(user1);
        users.add(user2);
        users.add(user3);
    }

    public List<User> getUsers() {
        return users;
    }

    public List<String> getFormattedUserNames() {
        List<String> userNames = new ArrayList<>(users.size());
        for (User user : users) {
            String name;

            if (user.getLastName() != null) {
                if (user.getFirstName() != null) {
                    name = user.getFirstName() + " " + user.getLastName();
                } else {
                    name = user.getLastName();
                }
            } else if (user.getFirstName() != null) {
                name = user.getFirstName();
            } else {
                name = "Unknown";
            }
            userNames.add(name);
        }
        return userNames;
    }
}

IDE'miz, Java kodunu Kotlin koduna otomatik olarak yeniden düzenleme konusunda oldukça iyi bir iş çıkarabilir ancak bazen biraz yardıma ihtiyacı olur. Önce bunu yapacağız, ardından nasıl ve neden bu şekilde yeniden düzenlendiğini anlamak için yeniden düzenlenmiş kodu inceleyeceğiz.

User.java dosyasına gidin ve Menü çubuğu -> Kod -> Java Dosyasını Kotlin Dosyasına Dönüştür'ü seçerek dosyayı Kotlin'e dönüştürün.

IDE'niz dönüştürme işleminden sonra düzeltme yapmanızı isterse Evet'e basın.

Aşağıdaki Kotlin kodunu görmeniz gerekir:

class User(var firstName: String?, var lastName: String?)

User.java seçeneğinin adının User.kt olarak değiştirildiğini unutmayın. Kotlin dosyalarının uzantısı .kt'dir.

Java User sınıfımızda iki özellik vardı: firstName ve lastName. Her birinin getter ve setter yöntemi vardı, bu da değerini değiştirilebilir hale getiriyordu. Kotlin'de değiştirilebilir değişkenler için kullanılan anahtar kelime var olduğundan dönüştürücü, bu özelliklerin her biri için var kullanır. Java özelliklerimizde yalnızca getter'lar olsaydı bu özellikler değişmez olurdu ve val değişkenleri olarak bildirilirdi. val, Java'daki final anahtar kelimesine benzer.

Kotlin ile Java arasındaki temel farklardan biri, Kotlin'in bir değişkenin boş değer kabul edip edemeyeceğini açıkça belirtmesidir. Bu işlem, tür bildirimine `?` eklenerek yapılır.

firstName ve lastName değerlerini boş bırakılabilir olarak işaretlediğimiz için otomatik dönüştürücü, özellikleri String? ile boş bırakılabilir olarak işaretledi. Java üyelerinizi null olmayan olarak açıklama ekleyerek (org.jetbrains.annotations.NotNull veya androidx.annotation.NonNull kullanarak) işaretlerseniz dönüştürücü bunu tanır ve alanları Kotlin'de de null olmayan olarak ayarlar.

Temel yeniden düzenleme zaten yapılmıştır. Ancak bunu daha deyimsel bir şekilde yazabiliriz. Nasıl yapacağınızı görelim.

Veri sınıfı

User sınıfımız yalnızca verileri tutar. Kotlin'de bu role sahip sınıflar için data anahtar kelimesi bulunur. Bu sınıfı data sınıfı olarak işaretlediğimizde derleyici, bizim için otomatik olarak alıcılar ve ayarlayıcılar oluşturur. Ayrıca equals(), hashCode() ve toString() işlevlerini de türetir.

data anahtar kelimesini User sınıfımıza ekleyelim:

data class User(var firstName: String, var lastName: String)

Kotlin, Java gibi birincil oluşturucuya ve bir veya daha fazla ikincil oluşturucuya sahip olabilir. Yukarıdaki örnekte, User sınıfının birincil oluşturucusudur. Birden fazla oluşturucuya sahip bir Java sınıfını dönüştürüyorsanız dönüştürücü, Kotlin'de de otomatik olarak birden fazla oluşturucu oluşturur. constructor anahtar kelimesi kullanılarak tanımlanır.

Bu sınıfın bir örneğini oluşturmak istiyorsak bunu şu şekilde yapabiliriz:

val user1 = User("Jane", "Doe")

Equality

Kotlin'de iki tür eşitlik vardır:

  • Yapısal eşitlik, == operatörünü kullanır ve iki örneğin eşit olup olmadığını belirlemek için equals() işlevini çağırır.
  • Referans eşitliği, === operatörünü kullanır ve iki referansın aynı nesneyi işaret edip etmediğini kontrol eder.

Veri sınıfının birincil oluşturucusunda tanımlanan özellikler, yapısal eşitlik kontrolleri için kullanılır.

val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false

Kotlin'de işlev çağrılarındaki bağımsız değişkenlere varsayılan değerler atayabiliriz. Bağımsız değişken atlandığında varsayılan değer kullanılır. Kotlin'de oluşturucular da işlevdir. Bu nedenle, lastName varsayılan değerinin null olduğunu belirtmek için varsayılan bağımsız değişkenleri kullanabiliriz. Bunu yapmak için null değerini lastName değişkenine atamamız yeterlidir.

data class User(var firstName: String?, var lastName: String? = null)

// usage
val jane = User("Jane") // same as User("Jane", null)
val joe = User("John", "Doe")

İşlevler çağrılırken işlev parametreleri adlandırılabilir:

val john = User(firstName = "John", lastName = "Doe") 

Farklı bir kullanım alanı olarak, firstName özelliğinin varsayılan değerinin null olduğunu ve lastName özelliğinin varsayılan değeri olmadığını varsayalım. Bu durumda, varsayılan parametre varsayılan değeri olmayan bir parametreden önce geleceğinden işlevi adlandırılmış bağımsız değişkenlerle çağırmanız gerekir:

data class User(var firstName: String? = null, var lastName: String?)

// usage
val jane = User(lastName = "Doe") // same as User(null, "Doe")
val john = User("John", "Doe")

Devam etmeden önce User sınıfınızın data sınıfı olduğundan emin olun. Repository sınıfını Kotlin'e dönüştürelim. Otomatik dönüştürme sonucu şu şekilde görünmelidir:

import java.util.*

class Repository private constructor() {
   private var users: MutableList<User?>? = null
   fun getUsers(): List<User?>? {
       return users
   }

   val formattedUserNames: List<String?>
       get() {
           val userNames: MutableList<String?> =
               ArrayList(users!!.size)
           for (user in users) {
               var name: String
               name = if (user!!.lastName != null) {
                   if (user!!.firstName != null) {
                       user!!.firstName + " " + user!!.lastName
                   } else {
                       user!!.lastName
                   }
               } else if (user!!.firstName != null) {
                   user!!.firstName
               } else {
                   "Unknown"
               }
               userNames.add(name)
           }
           return userNames
       }

   companion object {
       private var INSTANCE: Repository? = null
       val instance: Repository?
           get() {
               if (INSTANCE == null) {
                   synchronized(Repository::class.java) {
                       if (INSTANCE == null) {
                           INSTANCE =
                               Repository()
                       }
                   }
               }
               return INSTANCE
           }
   }

   // keeping the constructor private to enforce the usage of getInstance
   init {
       val user1 = User("Jane", "")
       val user2 = User("John", null)
       val user3 = User("Anne", "Doe")
       users = ArrayList<Any?>()
       users.add(user1)
       users.add(user2)
       users.add(user3)
   }
}

Otomatik dönüştürücünün ne yaptığına bakalım:

  • init bloğu eklendi (Repository.kt#L50)
  • static alanı artık bir companion object bloğunun parçası (Repository.kt#L33)
  • Nesne bildirim sırasında oluşturulmadığından users listesi boş değer atanabilir (Repository.kt#L7).
  • getFormattedUserNames() yöntemi artık formattedUserNames (Repository.kt#L11) adlı bir özellik
  • Kullanıcı listesi üzerinde yapılan yinelemenin (başlangıçta getFormattedUserNames('nın bir parçasıydı) Java'dakiyle (Repository.kt#L15) farklı bir söz dizimi var.

Devam etmeden önce kodu biraz temizleyelim. Dönüştürücünün, users listemizi boş değer atanabilir nesneler içeren değiştirilebilir bir liste haline getirdiğini görüyoruz. Liste gerçekten de boş olabilir ancak boş kullanıcılar içeremez. Bu nedenle, aşağıdaki adımları uygulayalım:

  • users türü beyanı içindeki User? içinde ? öğesini kaldırın.
  • getUsers, List<User>? değerinde ödeme yapmalıdır.

Otomatik dönüştürücü, kullanıcı değişkenlerinin ve init bloğunda tanımlanan değişkenlerin değişken bildirimlerini de gereksiz yere 2 satıra bölüyordu. Her değişken bildirimini tek bir satıra yerleştirelim. Kodumuz aşağıdaki gibi görünmelidir:

class Repository private constructor() {
    private var users: MutableList<User>? = null

    fun getUsers(): List<User>? {
        return users
    }

    val formattedUserNames: List<String?>
        get() {
            val userNames: MutableList<String?> =
                ArrayList(users!!.size)
            for (user in users) {
                var name: String
                name = if (user!!.lastName != null) {
                    if (user!!.firstName != null) {
                        user!!.firstName + " " + user!!.lastName
                    } else {
                        user!!.lastName
                    }
                } else if (user!!.firstName != null) {
                    user!!.firstName
                } else {
                    "Unknown"
                }
                userNames.add(name)
            }
            return userNames
        }

    companion object {
        private var INSTANCE: Repository? = null
        val instance: Repository?
            get() {
                if (INSTANCE == null) {
                    synchronized(Repository::class.java) {
                        if (INSTANCE == null) {
                            INSTANCE =
                                Repository()
                        }
                    }
                }
                return INSTANCE
            }
    }

    // keeping the constructor private to enforce the usage of getInstance
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

Init block

Kotlin'de birincil oluşturucu herhangi bir kod içermez. Bu nedenle, başlatma kodu init bloklarına yerleştirilir. İşlev aynıdır.

class Repository private constructor() {
    ...
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

init kodunun büyük bir kısmı özellikleri başlatmayı ele alır. Bu işlem, mülkün beyanında da yapılabilir. Örneğin, Repository sınıfımızın Kotlin sürümünde, kullanıcılar özelliğinin bildirimde başlatıldığını görüyoruz.

private var users: MutableList<User>? = null

Kotlin'in static özellikleri ve yöntemleri

Java'da, alanların veya işlevlerin bir sınıfa ait olduğunu ancak sınıfın bir örneğine ait olmadığını belirtmek için static anahtar kelimesini kullanırız. Bu nedenle, Repository sınıfımızda INSTANCE statik alanını oluşturduk. Bunun Kotlin'deki karşılığı companion object bloğudur. Burada statik alanları ve statik işlevleri de bildirirsiniz. Dönüştürücü, INSTANCE alanını oluşturup buraya taşıdı.

Tekil öğeleri işleme

Repository sınıfının yalnızca bir örneğine ihtiyacımız olduğundan Java'da singleton kalıbını kullandık. Kotlin'de class anahtar kelimesini object ile değiştirerek bu kalıbı derleyici düzeyinde zorunlu kılabilirsiniz.

Özel oluşturucuyu ve eşlik eden nesneyi kaldırıp sınıf tanımını object Repository ile değiştirin.

object Repository {

    private var users: MutableList<User>? = null

    fun getUsers(): List<User>? {
       return users
    }

    val formattedUserNames: List<String>
        get() {
            val userNames: MutableList<String> =
                ArrayList(users!!.size)
        for (user in users) {
            var name: String
            name = if (user!!.lastName != null) {
                if (user!!.firstName != null) {
                    user!!.firstName + " " + user!!.lastName
                } else {
                    user!!.lastName
                }
            } else if (user!!.firstName != null) {
                user!!.firstName
            } else {
                "Unknown"
            }
            userNames.add(name)
       }
       return userNames
   }

    // keeping the constructor private to enforce the usage of getInstance
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

object sınıfını kullanırken işlevleri ve özellikleri doğrudan nesnede şu şekilde çağırırız:

val users = Repository.users

Destructuring

Kotlin, yapı bozma bildirimi adı verilen bir söz dizimi kullanarak bir nesnenin değişkenlere ayrılmasına olanak tanır. Birden fazla değişken oluşturup bunları bağımsız olarak kullanabiliriz.

Örneğin, veri sınıfları, User nesnesini for döngüsünde (firstName, lastName) olarak yapılandırabilmemiz için yapılandırmayı bozmayı destekler. Bu sayede doğrudan firstName ve lastName değerleriyle çalışabiliriz. for döngüsünü şu şekilde güncelleyelim:

 
for ((firstName, lastName) in users!!) {
       val name: String?

       if (lastName != null) {
          if (firstName != null) {
                name = "$firstName $lastName"
          } else {
                name = lastName
          }
       } else if (firstName != null) {
            name = firstName
       } else {
            name = "Unknown"
       }
       userNames.add(name)
}

Repository sınıfı Kotlin'e dönüştürülürken otomatik dönüştürücü, kullanıcı listesini null yapılabilir hale getirdi. Bunun nedeni, liste bildirilirken bir nesneyle başlatılmamış olmasıydı. users nesnesinin tüm kullanımlarında, null olmayan onaylama operatörü !! kullanılır. Tüm değişkenleri null olmayan bir türe dönüştürür ve değer null ise istisna oluşturur. !! kullanarak çalışma zamanında istisnaların oluşturulma riskini alırsınız.

Bunun yerine, aşağıdaki yöntemlerden birini kullanarak boş değerleri işlemeyi tercih edin:

  • Boşluk kontrolü yapma ( if (users != null) {...} )
  • ?: Elvis operatörünü kullanma (bu codelab'de daha sonra ele alınacaktır)
  • Kotlin standart işlevlerinden bazılarını kullanma (bu işlevler, codelab'in ilerleyen bölümlerinde ele alınacaktır)

Bizim durumumuzda, kullanıcı listesinin nesne oluşturulduktan hemen sonra başlatıldığı için boş değer atanabilir olması gerekmediğini biliyoruz. Bu nedenle, nesneyi bildirdiğimizde doğrudan örnekleyebiliriz.

Kotlin, koleksiyon türlerinin örneklerini oluştururken kodunuzu daha okunabilir ve esnek hale getirmek için çeşitli yardımcı işlevler sağlar. Burada users için MutableList kullanıyoruz:

private var users: MutableList<User>? = null

Basitlik için mutableListOf() işlevini kullanabilir, liste öğesi türünü sağlayabilir, ArrayList oluşturucu çağrısını init bloğundan kaldırabilir ve users özelliğinin açık tür bildirimini kaldırabiliriz.

private val users = mutableListOf<User>()

Ayrıca, kullanıcılar kullanıcı listesine değişmez bir referans içereceğinden var'ı val olarak değiştirdik. Referansın değişmez olduğunu ancak listenin kendisinin değişebilir olduğunu (öğe ekleyebilir veya kaldırabilirsiniz) unutmayın.

Bu değişikliklerle birlikte users özelliğimiz artık boş değil ve gereksiz tüm !! operatörlerini kaldırabiliriz.

val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
    ...
}

Ayrıca, kullanıcı değişkeni zaten başlatıldığından başlatma işlemini init bloğundan kaldırmamız gerekir:

init {
    val user1 = User("Jane", "")
    val user2 = User("John", null)
    val user3 = User("Anne", "Doe")

    users.add(user1)
    users.add(user2)
    users.add(user3)
}

Hem lastName hem de firstName null olabileceğinden, biçimlendirilmiş kullanıcı adları listesini oluştururken nullability'yi ele almamız gerekir. Adlardan biri eksikse "Unknown" değerini göstermek istediğimiz için tür bildiriminden ? değerini kaldırarak adı boş olmayan bir değer haline getirebiliriz.

val name: String

lastName değeri null ise name değeri firstName veya "Unknown" olur:

if (lastName != null) {
    if (firstName != null) {
        name = "$firstName $lastName"
    } else {
        name = lastName
    }
} else if (firstName != null) {
    name = firstName
} else {
    name = "Unknown"
}

Bu ifade, Elvis operatörü ?: kullanılarak daha deyimsel bir şekilde yazılabilir. Elvis operatörü, sol tarafı boş değilse sol taraftaki ifadeyi, sol tarafı boşsa sağ taraftaki ifadeyi döndürür.

Bu nedenle, aşağıdaki kodda boş değilse user.firstName döndürülür. user.firstName değeri null ise ifade, sağ taraftaki değeri ("Unknown") döndürür:

if (lastName != null) {
    ...
} else {
    name = firstName ?: "Unknown"
}

Kotlin, String ile çalışmayı String şablonları sayesinde kolaylaştırır. Dize şablonları, dize bildirimlerindeki değişkenlere referans vermenize olanak tanır.

Otomatik dönüştürücü, ad ve soyadın birleştirilmesini $ sembolünü kullanarak değişken adına doğrudan dizede referans verecek şekilde güncelledi ve ifadeyi { } arasına yerleştirdi .

// Java
name = user.getFirstName() + " " + user.getLastName();

// Kotlin
name = "${user.firstName} ${user.lastName}"

Kodda, dize birleştirmeyi aşağıdakilerle değiştirin:

name = "$firstName $lastName"

Kotlin'de if, when, for ve while ifadelerdir. Bunlar bir değer döndürür. IDE'niz, atamanın if dışına çıkarılması gerektiğine dair bir uyarı bile gösteriyor:

IDE'nin önerisini uygulayalım ve her iki if ifadesi için atamayı kaldıralım. if ifadesinin son satırı atanır. Bu şekilde, bu bloğun tek amacının ad değerini başlatmak olduğu daha net anlaşılıyor:

name = if (lastName != null) {
    if (firstName != null) {
        "$firstName $lastName"
    } else {
        lastName
    }
} else {
   firstName ?: "Unknown"
}

Ardından, name bildiriminin atamayla birleştirilebileceği konusunda bir uyarı alırız. Bunu da uygulayalım. Ad değişkeninin türü tahmin edilebildiğinden açık tür bildirimi kaldırılabilir. formattedUserNames artık şu şekilde görünüyor:

val formattedUserNames: List<String?>
   get() {
       val userNames: MutableList<String?> = ArrayList(users.size)
       for ((firstName, lastName) in users) {
           val name = if (lastName != null) {
               if (firstName != null) {
                   "$firstName $lastName"
               } else {
                   lastName
               }
           } else {
               firstName ?: "Unknown"
           }
           userNames.add(name)
       }
       return userNames
   }

formattedUserNames getter'ı daha yakından inceleyelim ve nasıl daha deyimsel hale getirebileceğimizi görelim. Şu anda kod şunları yapıyor:

  • Yeni bir dizeler listesi oluşturur.
  • Kullanıcı listesinde yinelenir.
  • Kullanıcının adını ve soyadını temel alarak her kullanıcı için biçimlendirilmiş adı oluşturur.
  • Yeni oluşturulan listeyi döndürür.
val formattedUserNames: List<String>
        get() {
            val userNames = ArrayList<String>(users.size)
            for ((firstName, lastName) in users) {
                val name = if (lastName != null) {
                    if (firstName != null) {
                        "$firstName $lastName"
                    } else {
                        lastName
                    }
                } else {
                    firstName ?: "Unknown"
                }

                userNames.add(name)
            }
            return userNames
        }

Kotlin, Java Collections API'nin özelliklerini genişleterek geliştirmeyi daha hızlı ve güvenli hale getiren kapsamlı bir koleksiyon dönüşümleri listesi sunar. Bunlardan biri map işlevidir. Bu işlev, verilen dönüştürme işlevinin orijinal listedeki her öğeye uygulanmasıyla elde edilen sonuçları içeren yeni bir liste döndürür. Bu nedenle, yeni bir liste oluşturup kullanıcı listesini manuel olarak yinelemek yerine map işlevini kullanabilir ve for döngüsündeki mantığı map gövdesine taşıyabiliriz. Varsayılan olarak, map içinde kullanılan mevcut liste öğesinin adı it'dir. Ancak okunabilirliği artırmak için it yerine kendi değişken adınızı kullanabilirsiniz. Bu örnekte, user adını verelim:

    
val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                val name = if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
                name
            }
        }

Bu işlemi daha da basitleştirmek için name değişkenini tamamen kaldırabiliriz:

    
val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

Otomatik dönüştürücünün, getFormattedUserNames() işlevini özel bir alıcıya sahip formattedUserNames adlı bir özellikle değiştirdiğini gördük. Kotlin, arka planda List döndüren bir getFormattedUserNames() yöntemi oluşturmaya devam eder.

Java'da sınıf özelliklerimizi getter ve setter işlevleri aracılığıyla kullanıma sunarız. Kotlin, alanlarla ifade edilen bir sınıfın özellikleri ve işlevlerle ifade edilen, bir sınıfın yapabileceği işlemler arasında daha iyi bir ayrım yapmamıza olanak tanır. Bizim örneğimizde Repository sınıfı çok basit ve herhangi bir işlem yapmıyor. Bu nedenle yalnızca alanları var.

Java getFormattedUserNames() işlevinde tetiklenen mantık artık formattedUserNames Kotlin özelliğinin alıcı işlevi çağrıldığında tetikleniyor.

formattedUserNames özelliğiyle açıkça eşleşen bir alan olmasa da Kotlin, gerekirse özel alıcılar ve ayarlayıcılardan erişebileceğimiz field adlı otomatik bir destek alanı sağlar.

Ancak bazen otomatik destek alanı tarafından sağlanmayan bazı ek işlevler isteriz. Aşağıdaki örneği inceleyelim.

Repository sınıfımızda, Java kodumuzdan oluşturulan getUsers() işlevinde kullanıma sunulan, değiştirilebilir bir kullanıcı listemiz var:

fun getUsers(): List<User>? {
    return users
}

Buradaki sorun, users döndürülerek Repository sınıfının herhangi bir tüketicisinin kullanıcı listemizi değiştirebilmesidir. Bu iyi bir fikir değildir. Destekleyici bir özellik kullanarak bu sorunu düzeltelim.

Öncelikle users öğesini _users olarak yeniden adlandıralım. Şimdi kullanıcı listesi döndüren herkese açık bir değişmez özellik ekleyin. Bu işleme users adını verelim:

private val _users = mutableListOf<User>()
val users: List<User>
      get() = _users

Bu değişiklikle birlikte, özel _users mülkü, herkese açık users mülkünün destekleyici mülkü haline gelir. Repository sınıfının dışında, sınıfın tüketicileri listeye yalnızca users üzerinden erişebildiğinden _users listesi değiştirilemez.

Tam kod:

object Repository {

    private val _users = mutableListOf<User>()
    val users: List<User>
        get() = _users

    val formattedUserNames: List<String>
        get() {
            return _users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

    init {

        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        _users.add(user1)
        _users.add(user2)
        _users.add(user3)
    }
}

Şu anda Repository sınıfı, bir User nesnesi için biçimlendirilmiş kullanıcı adını nasıl hesaplayacağını biliyor. Ancak aynı biçimlendirme mantığını diğer sınıflarda da kullanmak istiyorsak bu mantığı kopyalayıp yapıştırmamız veya User sınıfına taşımamız gerekir.

Kotlin, işlevleri ve özellikleri herhangi bir sınıf, nesne veya arayüzün dışında tanımlama olanağı sunar. Örneğin, List öğesinin yeni bir örneğini oluşturmak için kullandığımız mutableListOf() işlevi, Standart Kitaplık'taki Collections.kt içinde doğrudan tanımlanır.

Java'da, bir yardımcı işlevselliğe ihtiyacınız olduğunda büyük olasılıkla bir Util sınıfı oluşturur ve bu işlevselliği statik bir işlev olarak tanımlarsınız. Kotlin'de sınıf olmadan üst düzey işlevler tanımlayabilirsiniz. Ancak Kotlin, uzantı işlevleri oluşturma olanağı da sunar. Bunlar, belirli bir türü genişleten ancak türün dışında tanımlanan işlevlerdir. Bu nedenle, bu türle bir yakınlıkları vardır.

Görünürlük değiştiriciler kullanılarak uzantı işlevlerinin ve özelliklerinin görünürlüğü kısıtlanabilir. Bu özellikler, kullanımı yalnızca uzantıların gerekli olduğu sınıflarla kısıtlar ve ad alanını kirletmez.

User sınıfı için, biçimlendirilmiş adı hesaplayan bir uzantı işlevi ekleyebilir veya biçimlendirilmiş adı bir uzantı özelliğinde tutabiliriz. Aynı dosyada Repository sınıfının dışında eklenebilir:

// extension function
fun User.getFormattedName(): String {
    return if (lastName != null) {
        if (firstName != null) {
            "$firstName $lastName"
        } else {
            lastName ?: "Unknown"
        }
    } else {
        firstName ?: "Unknown"
    }
}

// extension property
val User.userFormattedName: String
    get() {
        return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

// usage:
val user = User(...)
val name = user.getFormattedName()
val formattedName = user.userFormattedName

Daha sonra, uzantı işlevlerini ve özelliklerini User sınıfının bir parçasıymış gibi kullanabiliriz.

Biçimlendirilmiş ad, Repository sınıfının işlevi değil, kullanıcının bir özelliği olduğundan uzantı özelliğini kullanalım. Repository dosyamız artık şu şekilde görünüyor:

val User.formattedName: String
    get() {
        return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

object Repository {

    private val _users = mutableListOf<User>()
    val users: List<User>
      get() = _users

    val formattedUserNames: List<String>
        get() {
            return _users.map { user -> user.formattedName }
        }

    init {

        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        _users.add(user1)
        _users.add(user2)
        _users.add(user3)
    }
}

Kotlin Standart Kitaplığı, çeşitli Java API'lerinin işlevselliğini genişletmek için uzantı işlevlerini kullanır. Iterable ve Collection'deki işlevlerin çoğu uzantı işlevleri olarak uygulanır. Örneğin, önceki adımda kullandığımız map işlevi, Iterable üzerinde bir uzantı işlevidir.

Repository sınıf kodumuzda, _users listesine birkaç kullanıcı nesnesi ekliyoruz. Bu çağrılar, kapsam işlevleri yardımıyla daha deyimsel hale getirilebilir.

Kotlin, koda yalnızca belirli bir nesne bağlamında, nesneye adına göre erişmeye gerek kalmadan yürütmek için 5 kapsam işlevi oluşturdu: let, apply, with, run ve also. Kısa ve güçlü olan bu işlevlerin hepsinde bir alıcı (this) bulunur, bağımsız değişken (it) içerebilir ve değer döndürebilir. Ne yapmak istediğinize bağlı olarak hangisini kullanacağınıza siz karar verirsiniz.

Bunu hatırlamanıza yardımcı olacak kullanışlı bir kopya kağıdı:

_users nesnemizi Repository içinde yapılandırdığımız için apply işlevini kullanarak kodu daha deyimsel hale getirebiliriz:

init {
    val user1 = User("Jane", "")
    val user2 = User("John", null)
    val user3 = User("Anne", "Doe")
   
    _users.apply {
       // this == _users
       add(user1)
       add(user2)
       add(user3)
    }
 }

Bu codelab'de, kodunuzu Java'dan Kotlin'e yeniden düzenlemeye başlamak için bilmeniz gereken temel bilgileri ele aldık. Bu yeniden düzenleme, geliştirme platformunuzdan bağımsızdır ve yazdığınız kodun deyimsel olmasını sağlar.

İyi Kotlin kodu, kısa ve öz bir şekilde yazılır. Kotlin'in sunduğu tüm özelliklerle kodunuzu daha güvenli, daha kısa ve daha okunabilir hale getirmenin birçok yolu vardır. Örneğin, Repository sınıfımızı, _users listesini doğrudan bildirimde kullanıcılarla oluşturarak optimize edebilir ve init bloğunu kaldırabiliriz:

private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))

Boş değerleri, tekil nesneleri, dizeleri ve koleksiyonları işleme, uzantı işlevleri, üst düzey işlevler, özellikler ve kapsam işlevleri gibi çok çeşitli konuları ele aldık. İki Java sınıfından iki Kotlin sınıfına geçtik. Bu sınıflar artık şu şekilde görünüyor:

User.kt

class User(var firstName: String?, var lastName: String?)

Repository.kt

val User.formattedName: String
    get() {
       return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

object Repository {

    private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
    val users: List<User>
        get() = _users

    val formattedUserNames: List<String>
        get() = _users.map { user -> user.formattedName }
}

Java işlevlerinin ve bunların Kotlin'e eşlenmesinin kısa bir özeti:

Java

Kotlin

final nesnesi

val nesnesi

equals()

==

==

===

Yalnızca veri içeren sınıf

data sınıf

Oluşturucuda başlatma

init bloğunda başlatma

static alan ve işlev

companion object içinde tanımlanan alanlar ve işlevler

Singleton sınıfı

object

Kotlin ve platformunuzda nasıl kullanacağınız hakkında daha fazla bilgi edinmek için şu kaynaklara göz atın: