重構為 Kotlin

在這個程式碼研究室中,您將瞭解如何將程式碼從 Java 轉換為 Kotlin。您也將會瞭解 Kotlin 語言慣例,以及如何確保所編寫的程式碼遵循這些規範。

本程式碼研究室適合任何想使用 Java 開發專案的開發人員使用。我們先從幾個 Java 類別開始,然後您使用 IDE 轉換為 Kotlin。接下來,我們會檢視轉換後的程式碼,看看怎樣才能更符合實用原則,並且避免常見錯誤。

課程內容

您將瞭解如何將 Java 轉換為 Kotlin。透過這種方式,您將學會下列 Kotlin 語言功能和概念:

  • 處理是否可為空值
  • 實作單調
  • 資料類別
  • 處理字串
  • Elvis 運算子
  • 解構
  • 屬性和備用屬性
  • 預設引數和已命名的參數
  • 使用集合
  • 擴充功能函式
  • 頂層函式與參數
  • letapplywithrun 個關鍵字

假設

您應該已經熟悉 Java,

軟硬體需求

建立新專案

如果您使用的是 IntelliJ IDEA,請使用 Kotlin/JVM 建立新的 Java 專案。

如果您使用的是 Android Studio,請建立一個不含活動的新專案。

程式碼

我們會建立 User 模型物件和 Repository 單體類別,該類別會搭配 User 物件,並公開使用者清單和格式化的使用者名稱。

在 app/java/<<您的套件名稱>&gt 之下建立名為 User.java 的新檔案,然後貼到以下程式碼:

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;
    }

}

根據專案類型,請使用 Android 專案匯入 androidx.annotation.Nullable,否則請匯入 org.jetbrains.annotations.Nullable

建立名為 Repository.java 的新檔案並貼入下列程式碼:

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 可以有效地將 Java 程式碼自動重構為 Kotlin 程式碼,但有時需要一些協助。我們會先完成這項程序,再進入重構程式碼,瞭解程式碼的重構及原因。

前往 User.java 檔案並轉換為 Kotlin:Menu bar -> Code -> 將 Java 檔案轉換成 Kotlin 檔案

如果轉換後 IDE 顯示提示進行修正,請按 [是]。

您應該會看到下列 Kotlin 程式碼: :

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

請注意,User.java 已重新命名為「User.kt」。Kotlin 檔案的副檔名是 .kt。

在我們的 Java User 類別中,我們有兩個屬性:firstNamelastName。每個方法都有 getter 和 setter 方法,因此其值可以變動。Kotlin 的可變動變數關鍵字為 var,因此轉換者會針對這些屬性使用 var。如果我們的 Java 屬性只包含 getter,它將無法變更,並且會宣告為 val 變數。val 與 Java 中的 final 關鍵字類似。

Kotlin 和 Java 的主要差異之一,就是 Kotlin 明確指定變數是否可以接受空值。方法是將「?」附加到類型宣告。

因為我們將 firstNamelastName 標示為空值,自動轉換工具會自動將屬性標示為 String? 為空值。如果您將 Java 成員加註為非空值 (使用 org.jetbrains.annotations.NotNullandroidx.annotation.NonNull),轉換工具就會辨識此資訊,並在 Kotlin 中將欄位設為非空值。

基本重組已經完成。不過,我們能以更慣用的方式撰寫這個編號。我們來看看

資料類別

我們的 User 類別僅保存資料。Kotlin 為具有下列角色的類別提供關鍵字:data。將這個類別標記為 data 類別後,編譯器就會自動為您建立 getter 和 setter。也會產生 equals()hashCode()toString() 函式。

data 關鍵字新增至「User」類別:

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

Kotlin 與 Java 一樣,可以擁有主要建構函式以及一或多個次要建構函式。上述範例中的一項是 User 類別的主要建構函式。如果您要轉換的 Java 類別含有多個建構函式,轉換工具也會在 Kotlin 中自動建立多個建構函式。這類廣告使用 constructor 關鍵字來定義。

如果要建立這個類別的 執行個體,可按照以下方式進行:

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

平等

Kotlin 提供兩種平等類型:

  • 結構等式使用 == 運算子並呼叫 equals() 以判斷兩個執行個體是否相等。
  • 參照等式使用 === 運算子,可檢查兩個參照是否指向同一個物件。

資料類別主要建構函式中定義的屬性將用於結構平等檢查。

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

在 Kotlin 中,我們可以為函式呼叫中的引數指派預設值。如省略引數,系統會使用預設值。在 Kotlin 中,建構函式也是函式,因此我們可以使用預設引數,將 lastName 的預設值指定為 null。方法很簡單,只要將 null 指派給 lastName 即可。

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

呼叫函式時可命名函式參數:

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

針對另一個用途,我們假設 firstName 含有 null 做為預設值,而 lastName 沒有預設值。在此情況下,由於預設參數的前面會有一個沒有預設值的參數,因此您必須呼叫包含已命名引數的函式:

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

在繼續之前,請確認您的 User 類別是 data 類別。將 Repository 類別轉換為 Kotlin。自動轉換結果應如下所示:

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

我們來看看自動轉換工具有什麼作用:

  • 已新增一個 init 區塊 (Repository.kt#L50)
  • static 欄位現已成為 companion object 區塊的一部分 (Repository.kt#L33)
  • 由於物件在宣告時未即時化 (Repository.kt#L7),users 清單為無效
  • getFormattedUserNames() 方法現在是名為 formattedUserNames 的屬性 (Repository.kt#L11)
  • 針對使用者清單進行疊代作業 (原為 getFormattedUserNames( 的一部分) 的語法與 Java 不同 (Repository.kt#L15)

在進一步之前,讓我們先清理程式碼。我們可以看到轉換者在 users 清單中建立了可變動的清單,其中包含一個可空值的物件。雖然該清單實際上可以為 null,但假設該清單並未包含空值的使用者。讓我們完成以下動作:

  • 移除 users 類型宣告中的 User? ?
  • getUsers應傳回 List<User>?

此外,自動轉換工具也會另外分割 2 行,列出使用者變數和 init 區塊中定義的變數宣告。我們就將每個變數宣告放在一行。程式碼的樣式如下:

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

起始區塊

在 Kotlin 中,主要建構函式不得包含任何程式碼,因此將初始化程式碼放置在 init 個區塊中。功能相同。

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 程式碼的多個部分會處理初始化的屬性。您也可以在資源宣告中完成這個步驟。舉例來說,在 Repository 的 Kotlin 版本中,我們發現使用者屬性已在宣告中初始化。

private var users: MutableList<User>? = null

Kotlin'sstatic 屬性與方法

在 Java 中,針對欄位或函式使用 static 關鍵字,可表示它們屬於某個類別,但不屬於該類別的執行個體。因此,我們在 Repository 類別中建立了 INSTANCE 靜態欄位。Kotlin 等同於 companion object 區塊。您還可以在這裡宣告靜態欄位和靜態函式。轉換工具已建立並移動 INSTANCE 欄位。

處理單例

由於我們只需要 Repository 類別的執行個體,因此在 Java 中使用了 singleton 模式。使用 Kotlin 時,只要將 class 關鍵字替換為 object,即可在編譯器層級強制執行這個模式。

移除私人建構函式和隨播廣告物件,並將類別定義替換為 object Repository

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 類別時,我們只會直接在物件上呼叫函式和屬性,如下所示:

val users = Repository.users

解構

Kotlin 會使用「破壞宣告」語法,將物件解構為多個變數。我們建立了多個變數,可以單獨使用。

例如,資料類別支援解構,以便將 for 迴圈中的 User 物件分解成 (firstName, lastName)。因此,我們可以直接使用 firstNamelastName 值。讓 for 迴圈更新如下:

 
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 類別轉換為 Kotlin 時,自動轉換器會將使用者清單設為空值,因為系統未在宣告物件時,將其初始化為物件。對於 users 物件的所有用途,系統會使用非空值的宣告運算子 !!。它會將任何變數轉換為非空值類型,並在值為空值時擲回例外狀況。如果採用「!!」,您將會面臨在執行階段擲回例外狀況的情形。

因此,建議您採用下列其中一種做法來解決空值:

  • 執行空值檢查 ( if (users != null) {...} )
  • 使用 elvis 運算子 ?: (稍後在程式碼研究室中說明)
  • 使用部分 Kotlin 標準函式 (稍後在程式碼研究室中說明)

在我們的案例中,我們知道使用者清單不需要是空值,因為物件會在建構物件之後立即初始化,所以我們可以在宣告物件後直接對該物件執行個體化。

Kotlin 在建立集合類型時,提供多個輔助函式,讓您更容易讀取程式碼並靈活調整。以下是我們針對「users」使用的 MutableList

private var users: MutableList<User>? = null

為了簡化說明,我們可以使用 mutableListOf() 函式、提供清單元素類型、從 init 區塊中移除 ArrayList 建構函式呼叫,以及移除 users 屬性的明確類型宣告。

private val users = mutableListOf<User>()

此外,我們也將異動內容改成 val 值,因為使用者將包含一份無法修改的使用者名單。請注意,參考資料不可變更,但清單本身是不可變動的 (你可以新增或移除元素)。

經過這些調整後,我們的 users 屬性現在不是空值,而且可以移除所有不必要的 !! 運算子。

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

此外,由於使用者變數已初始化,因此必須從 init 區塊中移除初始化:

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

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

由於 lastNamefirstName 可以是 null,因此我們在建立格式的使用者名稱清單時需要處理空值。由於我們想遺漏 "Unknown" 而遺漏其中一個名稱,因此只要從類型宣告中移除 ?,就可以將名稱設為非空值。

val name: String

如果 lastName 為空值,name 就是 firstName"Unknown"

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

只要使用 elvis 運算子 ?:,就能更靈活地撰寫這些文字。如果 elvis 運算子不是 null,則在其左側傳回運算式,如果左側為 null,則會傳回右側運算式。

所以如果下列程式碼中,user.firstName 不是空值,就會傳回。如果 user.firstName 為空值,運算式會傳回右側 "Unknown" 的值:

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

Kotlin 可讓您輕鬆使用 StringString 範本。字串範本可讓您參照字串宣告中的變數。

自動轉換工具會以 $ 符號更新名字和姓氏的串連,以直接在字串中參照變數名稱,然後將運算式放在 { } 之間。

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

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

在程式碼中,以下列字串取代字串串連:

name = "$firstName $lastName"

在 Kotlin 中,ifwhenforwhile 都是運算式,它們會傳回一個值。IDE 甚至會顯示警告訊息,指出應將指派作業移出 if

我們依照 IDE 的建議提出,並解除這兩個 if 陳述式的指派作業。系統會指派 if 陳述式的最後一行。就像這樣,這很清楚地表明這個區塊的唯一目的是初始化名稱值:

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

接著,我們會傳送警告訊息,告知 name 宣告可以加入作業中。也請套用這個做法。由於名稱變數類型可刪除,因此我們可以移除明確的類型宣告。現在,formattedUserNames 看起來像這樣:

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,並瞭解我們如何讓它更具吸引力。現在,程式碼會執行下列作業:

  • 建立新的字串清單
  • 逐一查看使用者清單
  • 根據使用者的名字和姓氏,為每個使用者建構格式化的名稱
  • 傳回新建立的清單
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 的功能,進而加快開發速度。其中一個是 map 函式。此函式會傳回一個新清單,其中包含將指定轉換函式套用至原始清單中的每個元素的結果。因此,您可以使用 map 函式並移動 map 主體內 for 迴圈內的邏輯,而不需建立新清單並手動逐一查看使用者清單。根據預設,map 中目前的清單項目名稱為 it,但為了方便您閱讀,您可以將 it 替換成您自己的變數名稱。以我們的案例來說,請將其命名為 user

    
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
            }
        }

為了簡化這個過程,我們可以完全移除 name 變數:

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

我們發現自動轉換工具的「getFormattedUserNames()」函式已取代為「formattedUserNames」的屬性,其中包含自訂 getter。實際上,Kotlin 仍會產生傳回 ListgetFormattedUserNames() 方法。

在 Java 中,我們會透過 getter 和 setter 函式來呈現類別屬性。透過 Kotlin,我們可以針對類別屬性 (以欄位表示) 和功能 (類別所能執行的操作,使用函式表示) 提供更好的區分。在這個案例中,Repository 類別非常簡單,而且並未進行任何動作,因此該欄位僅包含欄位。

呼叫 formattedUserNames Kotlin 屬性的 getter 時,會觸發在 Java getFormattedUserNames() 函式中觸發的邏輯。

雖然我們沒有明確對 formattedUserNames 屬性對應的欄位,但 Kotlin 確實提供名為 field 的自動支援欄位,日後有自訂 terter 和 setter 就能存取。

但是,有時我們需要一些額外的功能,才能讓自動支援欄位提供某些功能。我們來看看下面這個例子。

Repository 類別中,有一份可變動的使用者清單 (在我們的 Java 程式碼產生的 getUsers() 函式中)。

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

這個問題在於,如果將 users 傳回 Repository 類別,任何使用者都能修改我們的使用者清單,並非很好!我們使用備用屬性來修正此問題。

首先,請將 users 重新命名為「_users」。現在新增一個可變動的使用者屬性,可傳回使用者清單。讓我們稱之為「users」:

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

這項變更生效後,私人的 _users 屬性會變成公開 users 資源的支援屬性。在 Repository 類別外,您無法修改 _users 清單,因為該類別的消費者只能透過 users 存取這份清單。

完整程式碼:

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

Repository 類別現在知道如何為 User 物件計算格式化的使用者名稱。不過,如果您想在其他課程中重複使用相同的格式設定邏輯,就必須複製和貼上該邏輯,或是將其移到 User 類別中。

Kotlin 可讓您針對任何類別、物件或介面外宣告函式和屬性。舉例來說,我們先前用來建立 List 新標記的 mutableListOf() 函式,都是直接在標準程式庫的 Collections.kt 中定義。

在 Java 中,當您需要某些公用程式功能時,很可能建立 Util 類別並宣告該功能為靜態函式。在 Kotlin 中,您可以宣告頂層函式,而不需設定類別。不過,Kotlin 也提供建立擴充功能函式的功能。這些函式會延伸特定類型的,但會在類型外宣告。因此這類廣告素材也特別感興趣。

使用瀏覽權限修飾符可以限制擴充功能函式和屬性的顯示設定。這些限制只會套用到需要擴充功能的類別,並不影響空間命名空間。

針對 User 類別,我們可以新增計算該格式化名稱的擴充函式,或是保留格式化名稱 (位於擴充功能屬性中)。可新增至 Repository 類別外的同一個檔案中:

// 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

然後,你就可以使用擴充功能函式和屬性,就像是 User 類別的一部分。

由於格式化的名稱是使用者的屬性,而不是 Repository 類別的功能,因此讓我們使用擴充屬性。我們的 Repository 檔案現在看起來像這樣:

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 標準程式庫使用擴充功能函式來擴充多個 Java API 的功能;IterableCollection 的許多功能都以擴充功能函式實作。舉例來說,我們在上一個步驟中使用的 map 函式是 Iterable 的擴充功能函式。

Repository 類別程式碼中,將多個使用者物件加入 _users 清單。有了範圍函式的輔助,這些呼叫就可以以更理想的方式進行。

為了僅在特定物件的內容中執行程式碼,且不需依據物件名稱存取物件,Kotlin 建立了 5 個範圍函式:letapplywithrunalso。簡單又強大的功能,所有函式都有一個接收端 (this),可能有引數 (it) 且可能會傳回一個值。您可根據想要達成的目標,決定要使用哪一個。

這份實用的一覽表或許能記住以下幾點:

由於我們已在 Repository 中設定 _users 物件,因此可以使用 apply 函式讓程式碼更具體:

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

在本程式碼研究室中,我們說明瞭開始從 Java 重構程式碼到 Kotlin 程式碼的基本概念。這項重構工作與開發平台無關,而且可協助您確保寫入的程式碼是慣用的。

Idymatic Kotlin 讓撰寫程式碼變得簡單又甜美。透過 Kotlin 提供的全部功能,您可以採用多種方法讓程式碼更加安全、簡潔且容易閱讀。舉例來說,我們甚至可以直接在宣告中宣告使用者,將 _users 清單執行個體化,而不使用 init 區塊,藉此將 Repository 類別最佳化:

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

我們探討了許多主題,包括處理空值、單調、字串和集合,以及擴充功能函式、頂層函式、屬性和範圍函式等主題。我們已經從兩個 Java 類別改為 2 個 Kotlin 類別,如下所示:

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 功能的 TL;DR 和對應至 Kotlin 的 TL;DR:

Java

Kotlin

final 個物件

val 個物件

equals()

==

==

===

僅保存資料的類別

data 類別

建構函式中的初始化

init 區塊中的初始化

static 個欄位和函式

companion object 中宣告的欄位和函式

Singleton 類別

object

如要進一步瞭解 Kotlin 及其在平台上的使用方式,請查看下列資源: