Kotlin olarak yeniden düzenleme

Bu codelab'de, kodunuzu Java'dan Kotlin'e nasıl dönüştüreceğinizi öğreneceksiniz. Ayrıca, Kotlin dili kongrelerinin ne olduğunu ve yazdığınız kodun bu kurallara uygun olmasını nasıl sağlayacağınızı da öğreneceksiniz.

Bu codelab, projesini Kotlin'e taşımayı düşünen Java kullanan tüm geliştiriciler için uygundur. IDE kullanarak Kotlin'e dönüştüreceğiniz birkaç Java sınıfıyla başlayacağız. Ardından, dönüştürülmüş koda göz atarak kodu daha deyimsel hale getirerek ve yaptığımız hatalardan nasıl kaçınabileceğimizi göreceğiz.

Neler öğreneceksiniz?

Java'yı Kotlin'e nasıl dönüştüreceğinizi öğreneceksiniz. Bu şekilde, aşağıdaki Kotlin dili özelliklerini ve kavramlarını öğrenebilirsiniz:

  • Boş değer işleme
  • Tektonları uygulama
  • Veri sınıfları
  • Dizeleri işleme
  • Elvis operatörü
  • Yıkım aracı
  • Mülkler ve yedekleme özellikleri
  • 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 kelime

Tahminler

Java'ya aşina olmanız gerekir.

Gerekenler

Yeni proje oluşturma

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

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

Kod

Bir User model nesnesi ve User nesneleriyle çalışan ve kullanıcıların listeleri ile biçimlendirilmiş kullanıcı adlarını gösteren Repository tekli sınıf oluşturacağız.

app/java/<paketinizin adı> altında User.java adında yeni bir dosya oluşturun ve aşağıdaki koda 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;
    }

}

Projenizin türüne bağlı olarak, bir Android projesi kullanıyorsanız androidx.annotation.Nullable içe aktarın. Aksi takdirde org.jetbrains.annotations.Nullable içe aktarın.

Repository.java adlı yeni bir dosya oluşturun ve aşağıdaki koda 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 otomatik olarak Kotlin kodu olarak yeniden düzenleyebilir ancak bazen biraz yardıma ihtiyaç duyar. Öncelikle bu işlemi gerçekleştireceğiz ve ardından bu kodun nasıl yeniden yapılandırıldığını anlamak için kodu yeniden düzenleyeceğiz.

User.java dosyasına gidin ve bunu Kotlin biçimine dönüştürün: Menü çubuğu -> Kod -> Java Dosyasını Kotlin Dosyasına Dönüştürün.

IDE'niz dönüşümden sonra düzeltme yapılmasını isterse Evet'e basın.

Aşağıdaki Kotlin kodunu görürsünüz:

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

User.java öğesinin User.kt olarak yeniden adlandırıldığını unutmayın. Kotlin dosyalarının .kt uzantısı vardır.

Java User sınıfımızda iki mülk vardı: firstName ve lastName. Her birinin değeri ve değişkenliği, dolayısıyla değişken olabilir. Kotlin’in değişken değişkenler için anahtar kelimesi var. Bu nedenle, dönüştürücü bu özelliklerin her biri için var kullanır. Java özelliklerimizde yalnızca alıcı varsa bunlar sabit olmalı ve val değişkenleri olarak açıklanmalıdır. val, Java'daki final anahtar kelimeye benzer.

Kotlin ve Java arasındaki temel farklılıklardan biri, Kotlin'in bir değişkenin null değerini kabul edip edemeyeceğini açıkça belirtmesidir. Bunu, tür beyanına "?" ekleyerek yapar.

firstName ve lastName değerlerini boş olarak işaretlediğimiz için otomatik dönüştürücü, özellikleri otomatik olarak String? ile boş değer olarak işaretledi. Java üyelerinize boş olmayan (org.jetbrains.annotations.NotNull veya androidx.annotation.NonNull kullanarak) ek açıklamalar eklerseniz dönüştürücü bunu tanır ve Kotlin'deki alanları da null olmaz.

Temel yeniden düzenleme işlemi zaten tamamlanmıştır. Ancak biz bunu daha deyimsel bir şekilde yazabiliriz. Bunu nasıl yapacağınıza bakalım.

Veri sınıfı

User sınıfımız yalnızca veri içerir. Kotlin'in şu rolü olan sınıflar için bir anahtar kelimesi var: data. Bu sınıfı data sınıfı olarak işaretlediğinizde, derleyici bizim için otomatik olarak alıcı ve işleyiciler oluşturur. Ayrıca equals(), hashCode() ve toString() işlevlerini türetecektir.

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

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

Java'da olduğu gibi Kotlin'in de birincil kurucusu ve bir veya daha fazla ikincil kurucusu olabilir. Yukarıdaki örnekte bulunan kullanıcı, Kullanıcı sınıfının birincil kurucusudur. Birden fazla oluşturucuya sahip bir Java sınıfını dönüştürüyorsanız dönüştürücü de Kotlin'de otomatik olarak birden fazla oluşturucu oluşturur. Bunlar 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")

Eşitlik

Kotlin'in iki tür eşitliği vardır:

  • Yapısal eşitlik, iki operatörün eşit olup olmadığını belirlemek için == operatörünü kullanır ve equals() işlevini çağırır.
  • Referans eşitlik, === operatörünü kullanır ve iki referansın aynı nesneye 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 içinde, işlev çağrılarındaki bağımsız değişkenlere varsayılan değerler atayabiliriz. Bağımsız değişken çıkarıldığında varsayılan değer kullanılır. Kotlin'de oluşturucular da işlevdir. Bu nedenle, lastName özelliğinin varsayılan değerinin null olduğunu belirtmek için varsayılan bağımsız değişkenleri kullanabiliriz. Bunu yapmak için lastName adlı yöneticiye null atıyoruz.

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")

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

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

Farklı bir kullanım örneğinde, firstName öğesinin varsayılan değer olarak null olduğunu ve lastName değerinin olmadığını varsayalım. Bu durumda, varsayılan parametre varsayılan değeri olmayan bir parametrenin önünde yer alacağı için ada sahip bağımsız değişkenlerle işlevi ç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 biçimine dönüştürelim. Otomatik dönüşüm sonucu aşağıdaki gibi görünecektir:

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 işlevini görelim:

  • Bir init bloku eklendi (Repository.kt#L50)
  • static alanı artık bir companion object blokunun (Repository.kt#L33) parçası
  • Nesne bildirim zamanında örneklendirilmediği için users listesi boş olabilir (Repository.kt#L7)
  • getFormattedUserNames() yöntemi artık formattedUserNames (Repository.kt#L11) adlı bir mülktür
  • Kullanıcı listesi üzerinde yineleme (başlangıçta getFormattedUserNames( parçasıydı) Java sözdiziminden farklı bir söz dizimine sahiptir (Repository.kt#L15)

Devam etmeden önce kodu biraz temizleyelim. Dönüştürücünün, users listemizi boş değerli nesneler içeren değiştirilebilir bir liste haline getirdiğini görebiliriz. Liste gerçekten boş değerli olabilir, ancak kullanıcıların boş değerli olamayacağını söyleyelim. Bu nedenle aşağıdakileri yapalım:

  • users türü bildirimindeki User? öğesindeki ? öğesini kaldırın
  • getUsers, List<User>? değerini döndürmelidir

Ayrıca, otomatik dönüştürücü, kullanıcı değişkenlerinin ve init blokunda tanımlanan değişkenlerin değişken bildirimlerini gereksiz bir şekilde 2 satıra böler. Her değişken bildirimini tek bir satıra yerleştirelim. Kodumuz şu şekilde 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 bloku

Kotlin'de birincil oluşturucu herhangi bir kod içeremez. Bu nedenle, ilk kullanıma hazırlama kodu init bloka yerleştirilir. Aynı işleve sahiptir.

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 çoğu başlatma özelliklerini işler. 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, beyana göre kullanıcı özelliğinin başlatıldığını görüyoruz.

private var users: MutableList<User>? = null

Kotlin's static özellikleri ve yöntemleri

Java'da, bir sınıfa ait olduklarını ancak sınıfın bir örneğine ait olmadığını söyleyen alanlar veya işlevler için static anahtar kelimesini kullanırız. Bu nedenle, Repository sınıfımızda INSTANCE statik alanını oluşturduk. Bunun için Kotlin eşdeğeri companion object bloktur. Burada ayrıca, statik alanları ve statik işlevleri de bildirirsiniz. Dönüştürücü, INSTANCE alanını oluşturup buraya taşımıştır.

Tek tekleri 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 ile class parametresini object ile değiştirerek bu kalıbı derleyici düzeyinde zorunlu kılabilirsiniz.

Özel oluşturucuyu ve tamamlayıcı nesnesini kaldırın ve 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 nesne üzerinde çağırırız. Örneğin:

val users = Repository.users

Yıkıcı

Kotlin, yıkma bildirimi adı verilen bir söz dizimi kullanarak bir nesnenin birkaç değişkende yok edilmesine olanak tanır. Birden çok değişken oluşturur ve bunları bağımsız olarak kullanabiliriz.

Örneğin, veri sınıfları for nesnesindeki User nesnesinin (firstName, lastName) olarak yok edilebilmesi için yıkmayı destekler. Böylece firstName ve lastName değerleriyle doğrudan çalışabiliyoruz. 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ü, bildirildiğinde bir nesnede ilk kullanıma sunulmadığı için kullanıcı listesini boş değerli hale getirdi. users nesnesinin tüm kullanımları için, boş olmayan onaylama operatörü !! kullanılır. Herhangi bir değişkeni null olmayan bir türe dönüştürür ve değer null ise istisna oluşturur. !! özelliğini kullandığınızda, çalışma zamanında istisna alma riskiyle karşı karşıya kalırsınız.

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

  • Boş çek ( if (users != null) {...} )
  • elvis operatörünü ?: (kod laboratuvarının ilerleyen kısımlarında ele alınmıştır) kullanma
  • Kotlin standart işlevlerinin bazılarını kullanma (kod laboratuvarının ilerleyen bölümlerinde ele alınan)

Bu örnekte, nesne oluşturulduktan hemen sonra başlatıldığından, kullanıcı listesinin null (boş) olmaması gerektiğini biliyoruz. Bu nedenle, nesneyi bildirdiğimizde nesneyi anında örneklendirebiliriz.

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

private var users: MutableList<User>? = null

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

private val users = mutableListOf<User>()

Kullanıcılar kullanıcı listesine sabit bir referans içerdiğinden de var değerini val olarak değiştirdik. Referansın değiştirilemez ancak listenin kendisi değişebilir (öğe ekleyebilir veya kaldırabilirsiniz).

Bu değişiklikler sayesinde users özelliğimiz artık null değil ve gereksiz tüm !! operatör örneklerini 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, ilk kullanıma hazırlama işlemini init blokundan 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ı listesi oluştururken null değeri belirlememiz gerekir. İki addan biri eksikse "Unknown" kelimesini göstermek istediğimiz için tür beyanından ? ifadesini kaldırarak adı boş olamaz.

val name: String

lastName boşsa name, 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, ?: elvis operatörü kullanılarak daha deyimsel bir şekilde yazılabilir. elvis operatörü, boş değilse ifadenin sol tarafını, sol tarafta boşken ifadenin sağ tarafında ifade döndürür.

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

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

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

Otomatik dönüştürücü, $ simgesini kullanarak değişken adını doğrudan ifade içinde bulmak ve adın arasına { } girmek için ad ve soyadının birleştirilmesini güncelledi .

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

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

Kodda, Dize birleştirmeyi şununla değiştirin:

name = "$firstName $lastName"

Kotlin if, when, for ve while ifadeleridir; bir değer döndürürler. IDE'niz, atamanın if dışına kaldırılması gerektiğine dair bir uyarı da gösteriyor:

IDE'nin önerisini takip edelim ve ödevi her iki if ifadesi için kaldıralım. if ifadesi için son satır atanır. Böylece, bu engellemenin tek amacının ad değerini başlatmak olduğunu anlıyoruz:

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

Şimdi, name bildiriminin ödevle birleştirilebileceğine dair bir uyarı alırsınız. Bunu da uygulayalım. Ad değişkeninin türü çıkarılabilir. Bu nedenle, açık tür bildirimini kaldırabiliriz. Şimdi formattedUserNames aşağıdaki gibi 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 alıcısını daha ayrıntılı bir şekilde inceleyerek nasıl deyimsel hale getirebileceğimizi inceleyelim. Kod şu anda aşağıdakileri yapmaktadır:

  • Yeni bir dize listesi oluşturur
  • Kullanıcı listesinden tekrarlanır
  • 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, JavaCollection API'nin özelliklerini genişleterek geliştirmeyi daha hızlı ve güvenli hale getiren koleksiyon dönüşümlerinin kapsamlı bir listesini sunar. Bunlardan biri map işlevidir. Bu işlev, belirtilen dönüşüm işlevinin orijinal listedeki her öğeye uygulanmasıyla ilgili sonuçları içeren yeni bir liste döndürür. Bu nedenle, yeni bir liste oluşturmak ve kullanıcı listesini manuel olarak yinelemek yerine map işlevini kullanabilir ve for döngüsündeki mantığı map gövdesinin içine taşıyabiliriz. Varsayılan olarak, map içinde kullanılan mevcut liste öğesinin adı it'dir ancak okunabilirlik için it öğesini kendi değişken adınızla değiştirebilirsiniz. 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 alıcısı olan formattedUserNames adlı bir özellikle değiştirdiğini gördük. Kotlin altında, Kotlin yine de List değeri döndüren bir getFormattedUserNames() yöntemi oluşturur.

Java'da sınıf özelliklerimizi getter ve setter işlevleriyle açığa çıkarırdık. Kotlin, alanın özellikleri ve işlevler, sınıfın yapabildiği işlemler ve işlevlerle ifade edilerek sınıfın daha iyi ayırt edilebilmesini sağlar. Bu örnekte, Repository sınıfı çok basittir ve yalnızca alanlar içerdiğinden herhangi bir işlem yapmaz.

Java getFormattedUserNames() işlevinde tetiklenen mantık, formattedUserNames Kotlin mülkünün alıcısı çağrılırken tetiklenir.

formattedUserNames mülküne karşılık açık bir alanımız olmasa da Kotlin, field adlı bir otomatik yedekleme alanı sunuyor. Otomatik yedekleme alanı, gerekirse özel alıcı ve ayarlayıcılardan erişmemizi sağlıyor.

Ancak bazen otomatik yedekleme alanının sağlamadığı bazı ek işlevler istiyoruz. Aşağıdaki örneği inceleyelim.

Repository sınıfımızın içinde, Java kodumuzdan oluşturulan getUsers() işlevinde açığa çıkan çeşitli kullanıcılardan oluşan bir listemiz var:

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

Buradaki sorun, users deposunu döndürerek kullanıcı listemizi herhangi bir tüketicinin değiştirmesidir. Bu iyi bir fikir değildir. Bir yedekleme özelliği kullanarak bu sorunu çözelim.

Önce, users adını _users olarak değiştirelim. Şimdi, kullanıcıların listesini döndüren herkese açık sabit bir mülk ekleyin. 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 yedek mülkü haline gelir. Sınıfın tüketicileri listeye yalnızca users üzerinden erişebildiğinden, Repository sınıfı dışında _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ın nasıl hesaplanacağını biliyor. Ancak aynı biçimlendirme mantığını diğer sınıflarda da kullanmak istiyorsak, kopyalama ve yapıştırma veya User sınıfına taşımamız gerekir.

Kotlin; herhangi bir sınıfın, nesnenin veya arayüzün dışındaki işlevleri ve özellikleri beyan etme olanağı sağlar. Örneğin, List öğesinin yeni bir örneğini oluşturmak için kullandığımız mutableListOf() işlevi, doğrudan Standart Kitaplık'taki Collections.kt öğesinde tanımlanır.

Java'da bazı yardımcı program işlevlerine ihtiyaç duyduğunuzda büyük olasılıkla bir Util sınıfı oluşturur ve bu işlevi statik bir işlev olarak tanımlarsınız. Kotlin'de, sınıfınız olmadan üst düzey işlevleri bildirebilirsiniz. Ancak Kotlin, uzantı işlevleri oluşturma olanağı da sağlar. Bunlar, belirli bir türü genişleten ancak türün dışında beyan edilen işlevlerdir. Dolayısıyla, bu türden bir yakın ilgi alanları vardır.

Görünürlük değiştiriciler kullanılarak uzantı işlevlerinin ve özelliklerinin görünürlüğü kısıtlanabilir. Bunlar, kullanımı yalnızca uzantılara ihtiyaç duyan 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. Repository sınıfının dışına aynı dosyadan 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ı gibi kullanabiliriz.

Biçimlendirilmiş ad, Repository sınıfının işlevlerinden değil de 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şlevlerini genişletmek için uzantı işlevlerini kullanır. Iterable ve Collection alanlarındaki birçok işlev, uzantı işlevleri olarak uygulanır. Örneğin, önceki adımda kullandığımız map işlevi, Iterable üzerindeki bir uzantı işlevidir.

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

Kotlin, kodu nesneye göre erişmesi gerekmeden yalnızca belirli bir nesne bağlamında kod 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) olabilir, bir bağımsız değişken (it) olabilir ve bir değer döndürebilir. Ulaşmak istediğiniz şeye bağlı olarak hangisini kullanacağınıza karar verirsiniz.

Bunu hatırlamanıza yardımcı olacak pratik bir yardımcı kısa bilgiyi aşağıda bulabilirsiniz:

Repository nesnesindeki _users nesnemizi yapılandırdığımız için apply işlevini kullanarak kodu daha deyimli 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 ihtiyacınız olan temel bilgileri ele aldık. Bu yeniden düzenleme, geliştirme platformunuzdan bağımsızdır ve yazdığınız kodun deyimsel olmasına yardımcı olur.

Deyimsel Kotlin kodu, kodun kısa ve öz olmasını sağlar. Kotlin'in sunduğu tüm özellikler sayesinde kodunuzu daha güvenli, daha öz ve daha okunaklı hale getirmenin birçok yolu vardır. Örneğin, init blokunu ortadan kaldırarak _users listesini doğrudan beyandaki kullanıcılarla örnekleyerek Repository sınıfımızı optimize edebiliriz:

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

Boş değerler, tekliler, dizeler ve koleksiyonlar gibi 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, şu anda şöyle görünen iki Kotlin sınıfına geçtik:

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 TL TL ve Kotlin eşlemeleri aşağıda verilmiştir:

Java

Kotlin

final nesnesi

val nesnesi

equals()

==

==

===

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

data sınıf

Oluşturucuda başlatma

init blokunda ilk kullanıma hazırlama

static alan ve işlev

companion object içinde belirtilen alanlar ve işlevler

Singleton sınıfı

object

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