(已淘汰) 轉換為 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 以及如何在平台上使用,請參閱下列資源: