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,请使用“无活动”模板创建一个新项目。选择 Kotlin 作为项目语言。最低 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;
}
}
3. 声明可空性、val、var 和数据类
我们的 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()
函数。
让我们将 data
关键字添加到我们的 User
类中
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
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>?
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
代码块。在这里,您也可以声明静态字段和静态函数。转换器创建了 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
}
// 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
请注意,如果属性上没有可见性修饰符,则默认为 public,例如 Repository
对象中的 formattedUserNames
属性。
6. 处理可空性
在将 Repository
类转换为 Kotlin 时,自动转换器使用户列表可为空,因为它在声明时未初始化为对象。因此,对于 users
对象的所有用法,都需要使用非空断言运算符 !!
。(您将在转换后的代码中看到 users!!
和 user!!
。)!!
运算符将任何变量转换为非空类型,因此您可以访问其属性或调用其函数。但是,如果变量值确实为空,则会抛出异常。通过使用 !!
,您冒着在运行时抛出异常的风险。
相反,建议使用以下方法之一处理可空性
- 执行空检查(
if (users != null) {...}
) - 使用 Elvis 运算符
?:
(在后面的 codelab 中介绍) - 使用一些 Kotlin 标准函数(在后面的 codelab 中介绍)
在我们的例子中,我们知道用户列表不需要是可空的,因为它在对象构造后立即初始化(在 init
代码块中)。因此,我们可以在声明 users
对象时直接实例化它。
在创建集合类型实例时,Kotlin 提供了一些辅助函数来使您的代码更具可读性和灵活性。这里我们为 users
使用了一个 MutableList
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
都可能为 null
,因此我们在构建格式化用户名列表时需要处理可空性。如果任一名称丢失,我们希望显示 "Unknown"
。由于变量 name
设置后将不再更改,因此我们可以使用 val
而不是 var
。首先进行此更改。
val name: String
看看设置 name 变量的代码。看到一个变量被设置为等于一个 if
/ else
代码块,您可能会觉得很新奇。这是允许的,因为在 Kotlin 中 if
和 when
是表达式——它们会返回一个值。if
语句的最后一行将被赋值给 name
。此代码块的唯一目的是初始化 name
值。
从本质上讲,这里提供的逻辑是,如果 lastName
为 null,则 name
将设置为 firstName
或 "Unknown"
。
name = if (lastName != null) {
if (firstName != null) {
firstName + " " + lastName
} else {
lastName
}
} else if (firstName != null) {
firstName
} else {
"Unknown"
}
Elvis 运算符
此代码可以通过使用 Elvis 运算符 ?:
以更惯用的方式编写。Elvis 运算符将在其左侧表达式不为 null 时返回左侧表达式,如果左侧表达式为 null,则返回右侧表达式。
因此,在以下代码中,如果 firstName
不为 null,则返回 firstName
。如果 firstName
为 null,则表达式返回右侧的值 "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 Collections 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
为 null 时返回 "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. 属性和支持属性
我们看到自动转换器用名为formattedUserNames
的属性替换了getFormattedUserNames()
函数,该属性具有自定义 getter。在底层,Kotlin 仍然生成一个getFormattedUserNames()
方法,该方法返回一个List
。
在 Java 中,我们会通过 getter 和 setter 函数公开类的属性。Kotlin 允许我们更好地区分用字段表示的类的属性和用函数表示的功能(类可以执行的操作)。在我们的例子中,Repository
类非常简单,不执行任何操作,因此它只有字段。
Java getFormattedUserNames()
函数中触发的逻辑现在在调用formattedUserNames
Kotlin 属性的 getter 时触发。
虽然我们没有明确对应于formattedUserNames
属性的字段,但 Kotlin 为我们提供了一个名为field
的自动支持字段,如果需要,我们可以从自定义 getter 和 setter 中访问它。
但是,有时我们希望获得自动支持字段无法提供的某些额外功能。
让我们来看一个例子。
在我们的Repository
类中,我们有一个用户可变列表,该列表在从 Java 代码生成的getUsers()
函数中公开。
fun getUsers(): List<User>? {
return users
}
因为我们不希望Repository
类的调用者修改用户列表,所以我们创建了getUsers()
函数,该函数返回一个只读List<User>
。在 Kotlin 中,我们更喜欢在这种情况下使用属性而不是函数。更准确地说,我们将公开一个由mutableListOf<User>
支持的只读List<User>
。
首先,让我们将users
重命名为_users
。突出显示变量名称,右键单击以**重构 > 重命名**变量。然后添加一个返回用户列表的公共只读属性。我们将其命名为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
类代码中,我们正在向_users
列表中添加多个User
对象。借助 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. 总结
在本 Codelab 中,我们介绍了开始将代码从 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 以及如何在您的平台上使用它的更多信息,请查看以下资源