(已淘汰) 轉換為 Kotlin

1. 歡迎!

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

本程式碼研究室適合使用 Java 的開發人員,他們正考慮將專案遷移至 Kotlin。首先,我們會使用 IDE 將幾個 Java 類別轉換為 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:依序點選「選單列」->「程式碼」->「將 Java 檔案轉換為 Kotlin 檔案」

如果 IDE 在轉換後提示修正,請按一下「Yes」(是)

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? 將屬性標示為可為空值。如果您使用 org.jetbrains.annotations.NotNullandroidx.annotation.NonNull 將 Java 成員註解為非空值,轉換器會辨識出這點,並在 Kotlin 中將欄位設為非空值。

基本轉換已完成。但我們可以採用更慣用的方式編寫這項內容。現在就來一探究竟。

資料類別

我們的 User 類別只會保留資料。Kotlin 有一個專門用於這類類別的關鍵字:data。將這個類別標示為 data 類別後,編譯器就會自動為我們建立 getter 和 setter。也會衍生 equals()hashCode()toString() 函式。

讓我們將 data 關鍵字新增至 User 類別:

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? 內的 ?
  • User? 中移除 ?,做為 getUsers() 的傳回類型,以便傳回 List<User>?

Init block

在 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 的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 提供多個輔助函式,讓程式碼更易於閱讀及更具彈性。這裡我們使用 MutableList 做為 users

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

此外,如果是 userNames 值,如果將 ArrayList 的類型指定為保留 Strings,則可移除宣告中的明確類型,因為系統會推斷類型。

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 運算式

userName 清單中的名稱格式尚未完全符合我們的需求。由於 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 是空值,運算式會傳回右側的值 "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 Collections 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 類別中,我們有一個可變動的使用者清單,會透過從 Java 程式碼產生的 getUsers() 函式公開:

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,以及如何在平台上使用,請參閱下列資源: