(已淘汰) 轉換為 Kotlin

1. 歡迎使用!

在這個程式碼研究室中,您將瞭解如何將 Java 程式碼轉換為 Kotlin。此外,您也將學習 Kotlin 語言慣例,以及如何確保自己編寫的程式碼符合這些慣例。

本程式碼研究室適合使用 Java 的任何開發人員,他們正在考慮將專案遷移至 Kotlin。我們將從幾個 Java 類別開始,您將使用 IDE 將這些類別轉換為 Kotlin。接著,我們將查看轉換後的程式碼,並瞭解如何改善程式碼,讓程式碼更具慣用語,並避免常見的陷阱。

課程內容

您將瞭解如何將 Java 轉換為 Kotlin。您將學習以下 Kotlin 語言功能和概念:

  • 處理是否可為空值
  • 實作單例
  • 資料類別
  • 處理字串
  • Elvis 運算子
  • 解構
  • 資源和支援資源
  • 預設引數和命名參數
  • 使用集合
  • 擴充功能函式
  • 頂層函式和參數
  • letapplywithrun 關鍵字

假設

您應該已熟悉 Java。

軟硬體需求

2. 開始設定

建立新專案

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

如果您使用 Android Studio,請使用「No Activity」範本建立新專案。選擇「Kotlin」做為專案語言。最低 SDK 版本可以是任何值,不會影響結果。

程式碼

我們將建立 User 模型物件和 Repository 單例類別,這些物件可與 User 物件搭配運作,並公開使用者清單和格式化的使用者名稱。

在 app/java/<yourpackagename> 下方建立名為 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;
    }

}

您會發現 IDE 會告知您未定義 @Nullable。因此,如果您使用 Android Studio,請匯入 androidx.annotation.Nullable;如果使用 IntelliJ,請匯入 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;
    }
}

3. 宣告可空性、val、var 和資料類別

我們的 IDE 可以自動將 Java 程式碼轉換為 Kotlin 程式碼,但有時需要一點協助。讓 IDE 進行轉換的初始階段。接著,我們將查看產生的程式碼,瞭解這類轉換方式的運作方式和原因。

前往 User.java 檔案並將其轉換為 Kotlin:依序點選「選單列」->「Code」->「Convert Java File to Kotlin File」

如果 IDE 在轉換後提示您修正,請按下「是」

e6f96eace5dabe5f.png

您應該會看到以下 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() 函式。

讓我們在 User 類別中新增 data 關鍵字:

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

與 Java 一樣,Kotlin 可以有主要建構函式和一或多個次要建構函式。上述範例中的建構函式是 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

4. 預設引數、具名引數

在 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("Joe", "Doe")

Kotlin 允許您在呼叫函式時為引數加上標籤:

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

預設值是 Kotlin 程式碼中常用的概念。在本程式碼研究室中,我們希望一律在 User 物件宣告中指定姓名,因此不需要預設值。

5. 物件初始化、伴生物件和單例

繼續進行程式碼研究室之前,請確認 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)
    }
}

讓我們看看自動轉換工具的運作方式:

  • 由於物件並未在宣告時例項化,因此 users 清單可為空值
  • Kotlin 中的函式 (例如 getUsers()) 會使用 fun 修飾符進行宣告
  • getFormattedUserNames() 方法現在是名為 formattedUserNames 的屬性
  • 針對使用者清單 (最初是 getFormattedUserNames( 的一部分) 的疊代,其語法與 Java 語法不同
  • static 欄位現在是 companion object 區塊的一部分
  • 已新增 init 區塊

在繼續之前,讓我們先清理一下程式碼。查看建構函式後,我們發現轉換器將 users 清單設為可變動清單,可儲存可為空值的物件。雖然清單確實可以為空值,但假設它無法保留空值使用者。請按照下列步驟操作:

  • users 類型宣告中,移除 User? 中的 ?
  • 針對 getUsers() 的傳回類型,從 User? 中移除 ?,以便傳回 List<User>?

初始化區塊

在 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 版本中,我們會看到 users 屬性是在宣告中初始化。

private var users: MutableList<User>? = null

Kotlin 的 static 屬性和方法

在 Java 中,我們會使用 static 關鍵字為欄位或函式標示,表示它們屬於某個類別,而非該類別的例項。因此,我們在 Repository 類別中建立了 INSTANCE 靜態欄位。在 Kotlin 中,這會對應到 companion object 區塊。您也可以在此宣告靜態欄位和靜態函式。轉換器會建立伴生物件區塊,並將 INSTANCE 欄位移至此處。

處理單例

由於我們只需要一個 Repository 類別的例項,因此我們使用了 Java 中的單例模式。使用 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 formattedUserNames = Repository.formattedUserNames

請注意,如果屬性沒有瀏覽權限修飾符,則預設為公開,例如 Repository 物件中的 formattedUserNames 屬性。

6. 處理是否可為空值

Repository 類別轉換為 Kotlin 時,自動轉換器會將使用者清單設為可為空值,因為在宣告時,系統並未將其初始化為物件。因此,對於 users 物件的所有用途,都必須使用非空值斷言運算子 !!。(您會在轉換後的程式碼中看到 users!!user!!)。!! 運算子會將任何變數轉換為非空值類型,以便您存取屬性或呼叫函式。不過,如果變數值確實為空值,系統會擲回例外狀況。使用 !! 時,您可能會在執行階段中傳回例外狀況。

建議您改用下列任一方法處理可空性:

  • 執行空值檢查 ( if (users != null) {...})
  • 使用 elvis 運算子 ?: (本程式碼研究室後續會介紹)
  • 使用部分 Kotlin 標準函式 (後續程式碼研究室會介紹)

在本例中,我們知道使用者清單不需要為可為空值,因為會在物件建構後立即初始化 (在 init 區塊中)。因此,我們可以在宣告 users 物件時,直接建立物件例項。

建立集合類型的例項時,Kotlin 會提供多個輔助函式,讓程式碼更易於解讀及靈活運用。以下是使用 MutableListusers 範例:

private var users: MutableList<User>? = null

為求簡單易懂,我們可以使用 mutableListOf() 函式,並提供清單元素類型。mutableListOf<User>() 會建立可容納 User 物件的空清單。由於編譯器現在可以推斷變數的資料類型,請移除 users 屬性的明確類型宣告。

private val users = mutableListOf<User>()

我們也將 var 變更為 val,因為使用者會包含使用者清單的唯讀參照。請注意,參照項目為唯讀,因此無法指向新清單,但清單本身仍可變動 (您可以新增或移除元素)。

由於 users 變數已初始化,請從 init 區塊中移除這項初始化作業:

users = ArrayList<Any?>()

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

經過這些變更後,我們的 users 屬性現在已非空值,因此可以移除所有不必要的 !! 運算子出現次數。請注意,您仍會在 Android Studio 中看到編譯錯誤,但請繼續執行程式碼研究室的後續步驟來解決這些錯誤。

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

此外,如果您將 ArrayList 的類型指定為 Strings,則可以從宣告中移除明確的類型,因為系統會推斷該類型。userNames

val userNames = ArrayList<String>(users.size)

重組

Kotlin 可使用稱為「解構宣告」的語法,將物件解構為多個變數。我們建立多個變數,並可獨立使用這些變數。

舉例來說,data 類別支援重組,因此我們可以將 for 迴圈中的 User 物件重組為 (firstName, lastName)。這樣一來,我們就能直接使用 firstNamelastName 值。請更新 for 迴圈,如下所示。將所有 user.firstName 替換為 firstName,並將 user.lastName 替換為 lastName

for ((firstName, lastName) in users) {
    var name: String
    name = if (lastName != null) {
        if (firstName != null) {
            firstName + " " + lastName
        } else {
            lastName
        }
    } else if (firstName != null) {
        firstName
    } else {
        "Unknown"
    }
    userNames.add(name)
}

if 運算式

使用者名稱清單中的名稱並未採用我們想要的格式。由於 lastNamefirstName 都可能是 null,因此我們在建立格式化使用者名稱清單時,需要處理可空性。如果缺少任一名稱,我們希望顯示 "Unknown"。由於 name 變數一旦設定就不會變更,因此我們可以使用 val 取代 var。請先進行這項變更。

val name: String

請查看設定名稱變數的程式碼。您可能會覺得將變數設為等於 if / else 程式碼區塊很新奇。這是因為在 Kotlin 中,ifwhen 都是運算式,會傳回值。if 陳述式的最後一行會指派給 name。這個區塊的唯一目的是初始化 name 值。

基本上,這裡提供的邏輯是,如果 lastName 為空值,name 會設為 firstName"Unknown"

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

Elvis 運算子

您可以使用 elvis 運算子 ?:,以更符合慣例的方式編寫這段程式碼。如果左側運算式不是空值,則 elvis 運算子會傳回左側運算式;如果左側運算式為空值,則會傳回右側運算式。

因此,在以下程式碼中,如果 firstName 非空值,則會傳回 firstName。如果 firstName 為空值,運算式會傳回右側的值 "Unknown"

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

7. 字串範本

Kotlin 提供字串範本,讓您輕鬆處理 String。字串範本可讓您在變數前使用 $ 符號,參照字串宣告中的變數。您也可以將運算式放在字串宣告中,方法是將運算式放在 { } 中,並在前面加上 $ 符號。範例:${user.firstName}

您的程式碼目前使用字串連接,將 firstNamelastName 組合成使用者名稱。

if (firstName != null) {
    firstName + " " + lastName
}

請改為將字串連接替換為:

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

使用字串範本可簡化程式碼。

如果有更符合慣例的方式來編寫程式碼,IDE 就會顯示警告。您會在程式碼中看到波浪形的底線,當您將滑鼠游標懸停在底線上時,系統會顯示建議,說明如何重構程式碼。

目前,您應該會看到警告,指出 name 宣告可與指派作業結合。讓我們來套用這個值。由於 name 變數的類型可推斷,因此我們可以移除明確的 String 類型宣告。現在,formattedUserNames 會如下所示:

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
    }

我們可以再進行一項調整。如果缺少名字和姓氏,我們的 UI 邏輯會顯示 "Unknown",因此我們不支援空值物件。因此,針對 formattedUserNames 的資料類型,請將 List<String?> 替換為 List<String>

val formattedUserNames: List<String>

8. 集合運算

讓我們進一步瞭解 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 集合 API 的功能,讓開發作業更快速、更安全。其中之一就是 map 函式。這個函式會傳回新清單,其中包含將指定轉換函式套用至原始清單中每個元素的結果。因此,我們可以使用 map 函式,並將 for 迴圈中的邏輯移至 map 主體,而不需要建立新清單,也不必手動逐一檢視使用者清單。根據預設,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
            }
        }

請注意,如果 user.lastName 為空值,我們會使用 Elvis 運算子傳回 "Unknown",因為 user.lastName 屬於 String? 類型,而 name 需要 String

...
else {
    user.lastName ?: "Unknown"
}
...

為了進一步簡化這項操作,我們可以完全移除 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"
                }
            }
        }

9. 屬性和支援屬性

我們發現自動轉換器已將 getFormattedUserNames() 函式替換為名為 formattedUserNames 的屬性,且該屬性含有自訂 getter。實際上,Kotlin 仍會產生會傳回 ListgetFormattedUserNames() 方法。

在 Java 中,我們會透過 getter 和 setter 函式公開類別屬性。Kotlin 可讓我們更清楚區分類別的屬性 (以欄位表示) 和功能 (以函式表示),也就是類別可執行的動作。在本例中,Repository 類別非常簡單,且不會執行任何動作,因此只包含欄位。

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

雖然我們沒有明確的欄位與 formattedUserNames 屬性相對應,但 Kotlin 確實提供了名為 field 的自動備援欄位,可在需要時透過自訂 getter 和 setter 存取。

不過,有時我們需要自動備用欄位未提供的額外功能。

我們來看個例子。

Repository 類別中,我們有一個可變動的使用者清單,該清單會在 getUsers() 函式中公開,而該函式是由 Java 程式碼產生的:

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

由於我們不希望 Repository 類別的呼叫端修改使用者清單,因此我們建立了 getUsers() 函式,該函式會傳回唯讀的 List<User>。在 Kotlin 中,我們建議在這種情況下使用屬性,而非函式。更精確地說,我們會公開由 mutableListOf<User> 支援的唯讀 List<User>

首先,我們將 users 重新命名為 _users。醒目顯示變數名稱,然後按一下滑鼠右鍵,依序點選「Refactor」>「Rename」變數。然後新增可傳回使用者清單的公開唯讀屬性。我們將其稱為 users

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

此時,您可以刪除 getUsers() 方法。

在進行上述變更後,私人 _users 屬性就會成為公開 users 屬性的幕後屬性。在 Repository 類別之外,_users 清單無法修改,因為類別的使用者只能透過 users 存取清單。

從 Kotlin 程式碼呼叫 users 時,系統會使用 Kotlin 標準程式庫的 List 實作項目,其中清單無法修改。如果從 Java 呼叫 users,系統會使用 java.util.List 實作,其中的清單可修改,且可使用 add() 和 remove() 等作業。

完整程式碼:

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

10. 頂層和擴充功能函式和屬性

目前 Repository 類別已瞭解如何計算 User 物件的格式化使用者名稱。不過,如果要在其他類別中重複使用相同的格式化邏輯,就必須複製貼上或將其移至 User 類別。

Kotlin 可讓您在任何類別、物件或介面之外宣告函式和屬性。舉例來說,我們用來建立 List 新例項的 mutableListOf() 函式,已在 Kotlin 標準程式庫的 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 類別的一部分。

由於格式化的名稱是 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 的擴充功能函式。

11. 範圍函式:let、apply、with、run、also

Repository 類別程式碼中,我們會將多個 User 物件新增至 _users 清單。透過 Kotlin 範圍函式的協助,這些呼叫可變得更符合慣用法。

如要只在特定物件的內容中執行程式碼,而不需要根據物件名稱存取物件,Kotlin 提供 5 個範圍函式:letapplywithrunalso。這些函式可讓程式碼更易讀且更精簡。所有範圍函式都有接收器 (this),可能會有引數 (it),也可能會傳回值。

以下是實用的小抄,可協助您記住何時使用各項函式:

6b9283d411fb6e7b.png

由於我們是在 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)
    }
 }

12. 總結

在本程式碼研究室中,我們將介紹從 Java 轉換為 Kotlin 所需的基本知識。這項轉換作業不受開發平台影響,有助於確保您編寫的程式碼符合 Kotlin 慣用語。

慣用 Kotlin 可讓您編寫簡短精簡的程式碼。透過 Kotlin 提供的所有功能,您可以透過多種方式讓程式碼更安全、更簡潔,也更易於閱讀。舉例來說,我們可以直接在宣告中使用 _users 清單例項化使用者,藉此最佳化 Repository 類別,並移除 init 區塊:

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

我們涵蓋了許多主題,從處理可空性、單例、字串和集合,到擴充函式、頂層函式、屬性和範圍函式等主題。我們從兩個 Java 類別轉換為兩個 Kotlin 類別,現在的樣子如下:

User.kt

data 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 功能和對應至 Kotlin 的功能摘要:

Java

Kotlin

final 個物件

val 個物件

equals()

==

==

===

只保留資料的類別

data 類別

在建構函式中進行初始化

init 區塊中進行初始化

static 欄位和函式

companion object 中宣告的欄位和函式

單例模式類別

object

如要進一步瞭解 Kotlin 以及如何在平台上使用,請參閱下列資源: