1. 歡迎使用!
在這個程式碼研究室中,您將瞭解如何將 Java 程式碼轉換為 Kotlin。此外,您也將學習 Kotlin 語言慣例,以及如何確保自己編寫的程式碼符合這些慣例。
本程式碼研究室適合使用 Java 的任何開發人員,他們正在考慮將專案遷移至 Kotlin。我們將從幾個 Java 類別開始,您將使用 IDE 將這些類別轉換為 Kotlin。接著,我們將查看轉換後的程式碼,並瞭解如何改善程式碼,讓程式碼更具慣用語,並避免常見的陷阱。
課程內容
您將瞭解如何將 Java 轉換為 Kotlin。您將學習以下 Kotlin 語言功能和概念:
- 處理是否可為空值
- 實作單例
- 資料類別
- 處理字串
- Elvis 運算子
- 解構
- 資源和支援資源
- 預設引數和命名參數
- 使用集合
- 擴充功能函式
- 頂層函式和參數
let
、apply
、with
和run
關鍵字
假設
您應該已熟悉 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 在轉換後提示您修正,請按下「是」。
您應該會看到以下 Kotlin 程式碼:
class User(var firstName: String?, var lastName: String?)
請注意,User.java
已重新命名為 User.kt
。Kotlin 檔案的副檔名為 .kt。
在 Java User
類別中,我們有兩個屬性:firstName
和 lastName
。每個類別都有 getter 和 setter 方法,可讓其值可變。Kotlin 變動變數的關鍵字為 var
,因此轉換工具會為這些屬性使用 var
。如果 Java 資源只有 getter,則會是唯讀,並且會宣告為 val
變數。val
類似��� Java 中的 final
關鍵字。
Kotlin 與 Java 之間的一個主要差異,在於 Kotlin 會明確指定變數是否可接受空值。方法是將 ?
附加至型別宣告。
由於我們將 firstName
和 lastName
標示為可為空值,自動轉換器會自動將屬性標示為可為空值,並使用 String?
標示。如果您將 Java 成員註解為非空值 (使用 org.jetbrains.annotations.NotNull
或 androidx.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 會提供多個輔助函式,讓程式碼更易於解讀及靈活運用。以下是使用 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)
}
此外,如果您將 ArrayList
的類型指定為 Strings
,則可以從宣告中移除明確的類型,因為系統會推斷該類型。userNames
val userNames = ArrayList<String>(users.size)
重組
Kotlin 可使用稱為「解構宣告」的語法,將物件解構為多個變數。我們建立多個變數,並可獨立使用這些變數��
舉例來說,data
類別支援重組,因此我們可以將 for
迴圈中的 User
物件重組為 (firstName, lastName)
。這樣一來,我們就能直接使用 firstName
和 lastName
值。請更新 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 運算式
使用者名稱清單中的名稱並未採用我們想要的格式。由於 lastName
和 firstName
都可能是 null
,因此我們在建立格式化使用者名稱清單時,需要處理可空性。如果缺少任一名稱,我們希望顯示 "Unknown"
。由於 name
變數一旦設定就不會變更,因此我們可以使用 val
取代 var
。請先進行這項變更。
val name: String
請查看設定名稱變數的程式碼。您可能會覺得將變數設為等於 if
/ else
程式碼區塊很新奇。這是因為在 Kotlin 中,if
和 when
都是運算式,會傳回值。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}
。
您的程式碼目前使用字串連接,將 firstName
和 lastName
組合成使用者名稱。
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 仍會產生會傳回 List
的 getFormattedUserNames()
方法。
在 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 的功能;Iterable
和 Collection
的許多功能都會實作為擴充功能函式。舉例來說,我們在前一個步驟中使用的 map
函式,就是 Iterable
的擴充功能函式。
11. 範圍函式:let、apply、with、run、also
在 Repository
類別程式碼中,我們會將多個 User
物件新增至 _users
清單。透過 Kotlin 範圍函式的協助,這些呼叫可變得更符合慣用法。
如要只在特定物件的內容中執行程式碼,而不需要根據物件名稱存取物件,Kotlin 提供 5 個範圍函式:let
、apply
、with
、run
和 also
。這些函式可讓程式碼更易讀且更精簡。所有範圍函式都有接收器 (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)
}
}
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 |
|
|
|
|
|
|
只保留資料的類別 |
|
在建構函式中進行初始化 | 在 |
| 在 |
單例模式類別 |
|
如要進一步瞭解 Kotlin 以及如何在平台上使用,請參閱下列資源: