在本 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 的新项目。为 Minimum 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
标记为可为空的,因此自动转换器使用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)
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
) 可能为 null,因为在声明时未实例化对象。 - Kotlin 中的函数,如
getUsers()
,使用fun
修饰符声明。 getFormattedUserNames()
方法现在是一个名为formattedUserNames
的属性。- 遍历用户列表(最初是
getFormattedUserNames()
的一部分)的语法与 Java 不同。 static
字段现在是companion object
块的一部分。- 添加了一个
init
块。
在我们继续之前,让我们稍微清理一下代码。如果我们查看构造函数,我们会发现转换器将我们的用户列表 (users
) 变成一个可变列表,允许存储 null 对象。虽然列表本身可能为 null,但我们假设它不能存储 null 用户。因此,让我们执行以下操作
- 删除
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 时,自动转换器将用户列表设为可空类型(即可以为 null),因为它在声明时未在对象中初始化。结果,在所有使用 users
对象的地方,都需要使用非空断言运算符 !!
(您将在转换后的代码中看到 users!!
和 user!!
)。!!
运算符将任何变量转换为非空类型,因此您可以访问该对象的属性或调用其函数。但是,如果变量实际上为 null,则会引发异常。使用 !!
会冒在运行时引发异常的风险。
相反,最好使用以下方法之一处理可空性
- 进行可空性检查 (
if (users != null) {...}
) - 使用Elvis 运算符
?:
(稍后将在 Codelab 中介绍) - 使用一些 Kotlin 的标准函数(稍后将在 Codelab 中介绍)
在我们的例子中,我们知道用户列表不需要是可空的,因为它在对象构建后立即初始化(在 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
的类型为存储 String
,则您可以删除显式类型声明,因为它将被推断。
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 运算符来返回 "Unknown"
,如果 user.lastName
为空,因为 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 仍然生成一个返回 List
的 getFormattedUserNames()
方法。
在 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
),并且可以返回值。您将根据要实现的目标来决定使用哪个函数。
这里有一张方便的速查表,可以帮助您记住这些函数
由于我们在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以及如何在您的平台上使用它的更多信息,请查看这些资料