在本 Codelab 中,您将学习如何将您的 Java 代码转换为 Kotlin。您还将学习 Kotlin 语言约定以及如何确保您编写的代码遵循这些约定。
此 Codelab 适用于任何使用 Java 并考虑将项目迁移到 Kotlin 的开发者。我们将从一些 Java 类开始,您将使用 IDE 将其转换为 Kotlin。然后,我们将查看转换后的代码,并了解如何通过使其更具惯用性并避免常见陷阱来改进它。
学习内容
您将学习如何将 Java 转换为 Kotlin。通过这样做,您将学习以下 Kotlin 语言功能和概念
- 处理空值
- 实现单例
- 数据类
- 处理字符串
- Elvis 操作符
- 解构
- 属性和后备属性
- 默认参数和命名参数
- 处理集合
- 扩展函数
- 高阶函数和参数
- 关键字
let
、apply
、with
和run
假设
您应该已经熟悉 Java。
您需要什么
创建一个新项目
如果您使用的是 IntelliJ IDEA,请为 Kotlin/JVM 创建一个新的 Java 项目。
如果您使用的是 Android Studio,请创建一个没有 Activity 的新项目。为最小 SDK 选择任何值,这不会影响最终结果。
代码
我们将创建一个名为 User
的模型对象和一个名为 Repository
的单例类,该类使用 User
对象并公开用户列表和格式化的用户名。
在 app/java/<您的包名> 中创建一个名为 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;
}
}
我们的 IDE 可以很好地自动重构 Java 代码到 Kotlin 代码,但有时它需要一些帮助。让我们让我们的 IDE 完成转换的第一步。然后,我们将逐步了解转换后的代码,以了解它是如何以及为什么以这种方式转换的。
转到 User.java
文件并将其转换为 Kotlin:**菜单 -> 代码 -> 将 Java 文件转换为 Kotlin 文件。**
如果您的 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
标记为可为空 (nullable),自动转换器将这些属性标记为可为空的 String?
。如果您将 Java 成员注释为非空(使用 org.jetbrains.annotations.NotNull
或 androidx.annotation.NonNull
),转换器会自动识别并使 Kotlin 中的字段也变为非空。
基本的转换已经完成。但是我们可以用更惯用的方式来编写它。让我们看看如何做到。
数据类 (Data Classes)
我们的 User
类只包含数据。Kotlin 为此类角色提供了一个关键字:data
。将其标记为 data
类时,编译器会自动为我们创建 getter 和 setter。它还会派生 equals()
、hashCode()
和 toString()
函数。
让我们在 User
类中添加 data
关键字:
data class User(var firstName: String, var lastName: String)
Kotlin 和 Java 一样,可以拥有一个主构造函数和一个或多个次构造函数。上面示例中的构造函数是 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
在 Kotlin 中,我们可以为函数调用中的参数分配默认值。当省略参数时,将使用默认值。在 Kotlin 中,构造函数也是函数,因此我们可以使用默认参数来指定 lastName
的默认值为 null
。为此,我们只需将 null
分配给 lastName
即可。
data class User(var firstName: String?, var lastName: String? = null)
// Uso
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?)
// Uso
val jane = User(lastName = "Doe") // é o mesmo que User(null, "Doe")
val john = User("John", "Doe")
默认值非常重要,并且在 Kotlin 代码中非常常用。在我们的 Codelab 中,我们始终希望在 User
对象声明中指定第一个和最后一个名称,因此我们不需要默认值。
在继续 Codelab 之前,请确保您的 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
}
}
// mantendo o construtor privado para forçar o uso do 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>?
。
init 块
在 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
。同时删除伴随对象 (companion object)。
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
}
// mantendo o construtor privado para forçar o uso do 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
属性就是这种情况。
在将 Repository
类转换为 Kotlin 时,自动转换器使用户列表变为可空 (nullable),因为在声明时没有在对象中对其进行初始化。因此,在所有使用 users
对象的地方,都需要使用非空断言运算符 !!
(您将在转换后的代码中看到 users!!
和 user!!
)。!!
运算符将任何变量转换为非空类型,以便您可以访问该对象的属性或调用其函数。但是,如果变量实际上为空,则会抛出异常。使用 !!
,您冒着在运行时抛出异常的风险。
相反,最好使用以下方法之一处理空值
- 进行空值检查 (
if (users != null) {...}
) - 使用 Elvis 运算符
?:
(稍后将在 Codelab 中介绍) - 使用一些 Kotlin 标准函数(稍后将在 Codelab 中介绍)
在我们的例子中,我们知道用户列表不需要是可空的 (nullable),因为它在对象构建后立即(在 init
块中)进行初始化。因此,我们可以在声明时直接实例化 users
对象。
在创建集合类型实例时,Kotlin 提供了许多辅助函数来使您的代码更具可读性和灵活性。这里我们使用 MutableList
作为 users
的类型。
private var users: MutableList<User>? = null
为了简化,我们可以使用 mutableListOf()
函数并提供列表的元素类型。mutableListOf<User>()
创建一个空列表,它可以存储 User
类型的对象。由于编译器可以推断变量的类型,因此请删除 users
属性的显式类型声明。
private val users = mutableListOf<User>()
我们还可以将 var
更改为 val
,因为 users
将包含对用户列表的不可变引用。请注意,引用是不可变的,但列表本身是可变的(您可以添加或删除元素)。
由于我们的变量 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 中看到编译错误,但请继续执行 codelab 的后续步骤来解决它们。
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)
。这使我们能够直接使用 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 表达式
userNames 列表中的名称尚未处于我们想要的格式。由于 lastName
和 firstName
都可能为空,因此在构建格式化的用户名称列表时,我们需要处理空值。如果任何一个名称不存在,我们希望显示 "Unknown"
。由于变量 name
在赋值后不会被修改,因此我们可以使用 val
而不是 var
。请先进行此更改。
val name: String
请查看将名称赋值给变量的代码。您可能会觉得使用 if
/ else
块来赋值变量很新颖。这是允许的,因为在 Kotlin 中,if
、when
、for
和 while
都是表达式——它们返回一个值。if
语句的最后一行将被赋值给 name
。此块的唯一目的是初始化 name
的值。
从本质上讲,这里提供的逻辑是,如果 lastName
为空,则 name
将被赋值为 firstName
或 "Unknown"
。
如果 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"
}
Kotlin 使用 字符串模板 简化了字符串处理。字符串模板允许您在字符串文字中引用变量,方法是在变量之前使用 $ 符号。您还可以将表达式包含在字符串文字中,只需将表达式放在 {} 中并在其前面使用 $ 符号即可。例如:${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>
让我们仔细看看 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
,但为了便于阅读,您可以将其替换为您喜欢的名称。在我们的例子中,我们将称之为 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
}
}
请注意,我们使用了 Elvis 运算符,如果 user.lastName
为空,则返回 "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"
}
}
}
我们看到自动转换器将 getFormattedUserNames()
函数替换为一个名为 formattedUserNames
的属性,该属性具有自定义 getter。在幕后,Kotlin 仍然会生成一个 getFormattedUserNames()
方法,该方法返回一个 List
。
在 Java 中,我们的类属性将通过 getter 和 setter 函数公开。Kotlin 允许我们更好地区分类的属性(用字段表示)和功能(类可以执行的操作,用函数表示)。在我们的例子中,Repository 类非常简单,不执行任何操作,因此它只有字段。
现在,在 Java getFormattedUserNames()
函数中定义的逻辑在调用 Kotlin formattedUserNames
属性的 getter 时被触发。
虽然我们没有显式地为 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
通过此操作,您可以删除 getUsers()
方法。
通过上述更改,私有 _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
类之外,在同一个文件中。
// função de extensão
fun User.getFormattedName(): String {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// propriedade de extensão
val User.userFormattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// uso:
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
的一个扩展函数。
在我们的 Repository
类代码中,我们向 _users
列表添加了多个 User
对象。借助作用域函数,这些调用可以更具惯用性。
为了仅在特定对象的上下文中执行代码,而无需根据其名称访问该对象,Kotlin 创建了 5 个作用域函数:let
、apply
、with
、run
和 also
。这些函数使您的代码易于阅读且更简洁。所有这些函数都有一个接收者(this
),可以有一个参数(it
),并且可以返回值。您将根据想要达成的目标来决定使用哪一个。
这是一个有用的备忘单(cheat sheet),可帮助您记住这一点
由于我们在 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)
}
}
在本 Codelab 中,我们介绍了开始将 Java 代码重构为 Kotlin 所需的基本知识。此重构独立于您的开发平台,并有助于确保您编写的代码具有惯用性。
惯用的 Kotlin 使代码编写简洁且愉快。凭借 Kotlin 提供的所有功能,您可以通过多种方式使代码更安全、更简洁和更易读。例如,我们甚至可以优化 Repository
类,通过在声明中直接使用用户实例化 _users
列表,从而摆脱 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 的更多信息以及如何在您的平台上使用它,请查看以下资料