在这个 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/<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;
}
}
我们的 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
块。您也可以在这里声明静态字段和函数。转换器已创建并将其移至此处。
处理单例
由于我们只需要一个 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!!
)。运算符 !!
将任何变量转换为非空类型,因此您可以访问该对象的属性或调用其函数。但是,如果变量实际上为 null,则会抛出异常。使用 !!
,您可能会在运行时遇到异常。
相反,请尝试使用以下方法之一处理空值
- 进行空值检查 (
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
的类型指定为存储 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
都可能为 null,因此在构建格式化的用户名列表时,我们需要处理空值。如果任一名称缺失,我们希望显示 "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>
让我们仔细看看 getter 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
}
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
}
}
请注意,我们使用了 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 仍然会生成一个返回 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
类代码中,我们正在将多个 User
对象添加到 _users
列表中。借助作用域函数,这些调用可以更具 Kotlin 风格。
为了仅在特定对象的上下文中执行代码,而无需根据其名称访问该对象,Kotlin 创建了 5 个作用域函数:let
、apply
、with
、run
和 also
。这些函数使代码更易于阅读,并且更简洁。所有这些函数都有一个接收者(this
),可以有一个参数(it
),并且可以返回值。根据您想要实现的目标,您将决定使用哪一个。
这是一个有用的备忘单(cheat sheet),可以帮助您记住这些内容
由于我们在 Repository
中配置了 _users
对象,因此我们可以使用 apply
函数使代码更具 Kotlin 风格。
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 风格的代码使编写代码变得简短而愉快。凭借 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 以及如何在您的平台上使用它的更多信息,请查看以下资料