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