重構為 Kotlin

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

本程式碼研究室適合使用 Java 的開發人員,他們正考慮將專案遷移至 Kotlin。首先,我們會使用 IDE 將幾個 Java 類別轉換為 Kotlin。接著,我們將查看轉換後的程式碼,瞭解如何讓程式碼更道地,並避免常見的陷阱。

課程內容

您將瞭解如何將 Java 轉換為 Kotlin。過程中,您將瞭解下列 Kotlin 語言功能和概念:

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

假設

您應已熟悉 Java。

軟硬體需求

建立新專案

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

如果您使用 Android Studio,請建立沒有 Activity 的新專案。

代碼

我們會建立 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;
    }

}

視專案類型而定,如果您使用 Android 專案,請匯入 androidx.annotation.Nullable;否則請匯入 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;
    }
}

我們的 IDE 可以自動將 Java 程式碼重構為 Kotlin 程式碼,但有時需要一些協助。我們會先這麼做,然後逐步瞭解重構後的程式碼,瞭解重構方式和原因。

前往 User.java 檔案並轉換為 Kotlin:依序點選「選單列」->「程式碼」->「將 Java 檔案轉換為 Kotlin 檔案」

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

您應該會看到下列 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")

Equality

Kotlin 具有兩種類型的等式:

  • 結構等式會使用 == 運算子並呼叫 equals(),藉此判斷兩個例項是否相等。
  • 參照等式會使用 === 運算子,藉此檢查兩個參照是否指向同一個物件。

在資料類別主要建構函式中定義的屬性,可用於結構等式檢查。

val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false

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

呼叫函式時,可以為函式參數命名:

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

繼續操作前,請確認 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)
   }
}

我們來看看自動轉換器做了什麼:

  • 新增了 init 區塊 (Repository.kt#L50)
  • static 欄位現在是 companion object 區塊的一部分 (Repository.kt#L33)
  • 由於物件並未在宣告時例項化 (Repository.kt#L7),因此 users 清單可為空值。
  • getFormattedUserNames() 方法現在是名為 formattedUserNames 的屬性 (Repository.kt#L11)
  • 使用者清單的疊代 (最初是 getFormattedUserNames( 的一部分) 與 Java 的語法不同 (Repository.kt#L15)。

在繼續之前,我們先稍微清理一下程式碼。我們可以發現,轉換器將 users 清單轉換為可變動清單,其中包含可為空值的物件。雖然清單確實可以為空值,但假設清單無法保留空值使用者。因此請執行下列操作:

  • users 型別宣告中,移除 User? 內的 ?
  • getUsers 應會傳回 List<User>?

自動轉換器也會不必要地將使用者變數和 init 區塊中定義的變數宣告分成 2 行。我們將每個變數宣告放在一行。程式碼應如下所示:

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

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 users = Repository.users

解構

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

舉例來說,資料類別支援解構,因此我們可以在 for 迴圈中將 User 物件解構為 (firstName, lastName)。這樣我們就能直接使用 firstNamelastName 值。請更新 for 迴圈,如下所示:

 
for ((firstName, lastName) in users!!) {
       val name: String?

       if (lastName != null) {
          if (firstName != null) {
                name = "$firstName $lastName"
          } else {
                name = lastName
          }
       } else if (firstName != null) {
            name = firstName
       } else {
            name = "Unknown"
       }
       userNames.add(name)
}

Repository 類別轉換為 Kotlin 時,自動轉換器會將使用者清單設為可為空值,因為清單在宣告時並未初始化為物件。對於 users 物件的所有用法,都會使用非空值斷言運算子 !!。這個運算子會將任何變數轉換為非空值類型。如果值為空值,則擲回例外狀況。使用 !! 時,您可能會在執行階段擲回例外狀況。

建議改用下列其中一種方法處理可為空值的情況:

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

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

建立集合型別的例項時,Kotlin 提供多個輔助函式,讓程式碼更易於閱讀及更具彈性。這裡我們使用 MutableList 做為 users

private var users: MutableList<User>? = null

為求簡單,我們可以採用 mutableListOf() 函式、提供清單元素型別、從 init 區塊中移除 ArrayList 建構函式呼叫,以及移除 users 屬性的明確型別宣告。

private val users = mutableListOf<User>()

我們也將 var 變更為 val,因為使用者會包含對使用者清單的不變動參照。請注意,參照是不可變動的,但清單本身是可變動的 (您可以新增或移除元素)。

完成這些變更後,我們的 users 屬性現在為非空值,我們可以移除所有不必要的 !! 運算子出現位置。

val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
    ...
}

此外,由於使用者變數已初始化,我們必須從 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)
}

由於 lastNamefirstName 都可以是 null,因此在建構格式化使用者名稱清單時,我們需要處理可為空值的問題。由於我們希望在缺少任一名稱時顯示 "Unknown",因此可以從型別宣告中移除 ?,讓名稱成為非空值。

val name: String

如果 lastName 為空值,則 namefirstName"Unknown"

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

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

因此,如果下列程式碼不是空值,就會傳回 user.firstName。如果 user.firstName 是空值,運算式會傳回右側的值 "Unknown"

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

Kotlin 可透過字串範本輕鬆處理 String。字串範本可讓您在字串宣告中參照變數。

自動轉換器已更新名字和姓氏的串連,直接在字串中參照變數名稱 (使用 $ 符號),並將運算式放在 { } 之間。

// Java
name = user.getFirstName() + " " + user.getLastName();

// Kotlin
name = "${user.firstName} ${user.lastName}"

在程式碼中,將字串串連替換為:

name = "$firstName $lastName"

在 Kotlin 中,ifwhenforwhile 都是運算式,會傳回值。IDE 甚至會顯示警告,指出應將指派作業從 if 中移除:

我們按照 IDE 的建議,將指派作業從兩個 if 陳述式中移出。系統會指派 if 陳述式的最後一行。這樣一來,這個區塊的唯一用途就是初始化名稱值,就更清楚了:

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

接著,我們會收到警告,指出 name 宣告可以與指派作業合併。我們也來套用這個設定。由於可以推斷名稱變數的型別,因此我們可以移除明確的型別宣告。現在,我們的 formattedUserNames 看起來會像這樣:

val formattedUserNames: List<String?>
   get() {
       val userNames: MutableList<String?> = ArrayList(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
   }

讓我們進一步瞭解 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
            }
        }

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

我們發現自動轉換器將 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
}

問題在於,只要傳回 users,Repository 類別的任何消費者都能修改使用者清單,這並非好主意!讓我們使用支援屬性修正這個問題。

首先,將 users 重新命名為 _users。現在新增可傳回使用者清單的公開不可變動屬性。我們將其稱為 users

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

進行這項變更後,私有 _users 屬性會成為公開 users 屬性的支援屬性。在 Repository 類別外,_users 清單無法修改,因為類別的消費者只能透過 users 存取清單。

完整程式碼:

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

目前 Repository 類別知道如何計算 User 物件的格式化使用者名稱。但如果我們想在其他類別中重複使用相同的格式化邏輯,就必須複製並貼上,或是將其移至 User 類別。

Kotlin 允許在任何類別、物件或介面以外宣告函式和屬性。舉例來說,我們用來建立 List 新例項的 mutableListOf() 函式,是直接在標準程式庫的 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 類別的一部分一樣,使用擴充功能函式和屬性。

由於格式化名稱是使用者的屬性,而非 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 的擴充功能函式。

Repository 類別程式碼中,我們將數個使用者物件新增至 _users 清單。範圍函式可協助您以更慣用的方式進行這些呼叫。

如要只在特定物件的環境中執行程式碼,不必根據名稱存取物件,Kotlin 建立了 5 個範圍函式:letapplywithrunalso。這些函式簡短有力,全都具有接收器 (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)
    }
 }

在本程式碼研究室中,我們介紹了將程式碼從 Java 重構為 Kotlin 的基本概念。這項重構作業與開發平台無關,有助於確保您編寫的程式碼符合慣例。

使用 Kotlin 慣用語可簡潔地編寫程式碼。Kotlin 提供許多功能,可讓程式碼更安全、簡潔且易於閱讀。舉例來說,我們甚至可以透過在宣告中直接使用使用者例項建立 _users 清單,藉此最佳化 Repository 類別,並擺脫 init 區塊:

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

我們涵蓋了許多主題,從處理可為空值、單例項、字串和集合,到擴充函式、頂層函式、屬性和範圍函式等主題。我們從兩個 Java 類別改為兩個 Kotlin 類別,現在看起來如下所示:

User.kt

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