1. 欢迎!
在此 Codelab 中,您将学习如何将 Java 代码转换为 Kotlin。此外,您还将学习 Kotlin 语言有何约定,以及如何确保您所编写的代码遵从这些约定。
此 Codelab 的适用对象为任何使用 Java 并考虑将其项目迁移到 Kotlin 的开发者。我们将从数个 Java 类入手,之后您需使用 IDE 将它们转换为 Kotlin。接着,我们会审视转换后的代码,研究如何加以改善,使其更符合使用习惯,同时避免常见缺陷。
您将学习的内容
您将学习如何将 Java 转换为 Kotlin。在此过程中,您将学习 Kotlin 语言的以下特点和概念:
- 处理是否可为 null
- 实现单一实例
- 数据类
- 处理字符串
- Elvis 运算符
- 解构
- 属性和支持属性
- 默认参数和具名参数
- 使用集合
- 扩展函数
- 顶层函数与参数
let、apply、with和run关键字
假设
您应已熟知 Java。
您需要具备的条件
- Android Studio 4.0 或 IntelliJ IDEA
2. 准备工作
创建新项目
若您在使用 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;
}
}
3. 声明是否可为 null、val、var 和数据类
IDE 在将 Java 代码自动转换为 Kotlin 代码时可以取得相当不错的效果,但有时也需要些许协助。我们让 IDE 执行转换过程中的第一步。然后我们将研究产生的代码,以便了解转换方式以及为何以此方式转换。
转至 User.java 文件并将其转换为 Kotlin:Menu bar(菜单栏)-> Code(代码)-> Convert Java File to Kotlin File(将 Java 文件转换为 Kotlin 文件)。
若 IDE 在转换后提示更正,请点按 Yes(是)。

您应会看到以下 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 会明确指定变量能否接受 null 值。具体而言,其是通过在类型声明中附加"?"以进行此项指定。
因为我们将 firstName 和 lastName 标记为可为 null,所以自动转换器会通过 String? 将属性自动标记为可为 null。若您使用 org.jetbrains.annotations.NotNull 或 androidx.annotation.NonNull 将 Java 成员标注为非 null,转换器将会识别这一情况,并在 Kotlin 中将这些字段同样设为非 null。
此时我们已完成基本的转换流程。不过,我们还可以使用更惯常的方式编写代码。下面就让我们一探究竟!
数据类
User 类仅存放数据。对于具有这一角色的类,Kotlin 会提供对应的关键字:data。在将此类标记为 data 类后,编译器便会自动创建 getter 和 setter。此外,其还会派生 equals()、hashCode() 和 toString() 函数。
我们向 User 类添加 data 关键字,具体如下:
data class User(var firstName: String, var lastName: String)
与 Java 类似,Kotlin 也可拥有一个主构造函数以及一个或多个辅助构造函数。以上示例中的构造函数是 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 代码中比较重要且经常用到的概念。在此 codelab 中,我们始终希望在 User 对象声明中指定名字和姓氏,因此不需要默认值。
5. 对象初始化、伴生对象和单一实例
在继续 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
}
}
// 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列表可为 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 代码大都用于处理属性的初始化。这项操作也可在声明属性时完成。例如,在 Kotlin 版本的 Repository 类中,我们可以看到,users 属性已在声明时进行初始化。
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 formattedUserNames = Repository.formattedUserNames
请注意,如果属性没有公开范围修饰符,则在默认情况下,该属性是公用的,就像 Repository 对象中的 formattedUserNames 属性一样。
6. 处理是否可为 null
将 Repository 类转换为 Kotlin 时,自动转换器已将用户列表设为可为 null,这是由于我们在声明时并未将其初始化为对象。因此,在 users 对象的所有使用情境中,需要使用非 null 断言运算符 !!。(您将在转换后的整段代码中看到 users!! 和 user!!。)!! 运算符可将任何变量转换为非 null 类型,以便您可以针对变量访问属性或调用函数。但是,如果变量值确实为 null,则会抛出异常。使用 !! 时,存在运行时抛出异常的风险。
建议您使用下列其中一种方法来处理是否可为 null:
- 执行 null 检查 (
if (users != null) {...}) - 使用 Elvis 运算符
?:(稍后将在 Codelab 中阐述) - 使用部分 Kotlin 标准函数(稍后将在 Codelab 中阐述)
在我们的示例中,我们知道用户列表无需可为 null,因为它在对象构建后立即进行初始化(在 init 块中)。因此,我们可以在声明时直接实例化 users 对象。
创建集合类实例时,您可以利用 Kotlin 所提供的多个帮助程序函数,让代码更易阅读而且更为灵活。本例中,我们将 MutableList 用于 users,具体如下:
private var users: MutableList<User>? = null
为简单起见,我们可以使用 mutableListOf() 函数,提供列表元素类型。mutableListOf<User>() 会创建一个可用于保存 User 对象的空列表。因为变量的数据类型现在可由编译器推断出来,所以可除去 users 属性的显式类型声明。
private val users = mutableListOf<User>()
我们还将 var 更改为 val,因为用户将包含对用户列表的不可变引用。请注意,引用是不可 变的,但列表本身是可变的(您可以添加或移除元素)。
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 属性现已变为非 null,我们此时亦可移除所有不必要的 !! 运算符实例。请注意,您仍会在 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,因此在构建经过格式化的用户名列表时,我们需要处理是否可为 null。我们希望在任一名称缺失时显示 "Unknown"。因为 name 变量一旦设定便不会更改,所以我们可以用 val 代替 var。先进行此更改。
val name: String
看一下设置名称变量的代码。您可能会对将变量设置为等于加上 if / else 条件的代码块比较陌生。这是因为,在 Kotlin 中,if、when、for 和 while 均为表达式,皆可返回值。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 运算符 ?:,我们将能以更惯常的方式编写此代码。若左侧表达式不为 null,则 Elvis 运算符将返回该表达式,否则便会返回右侧表达式。
基于此,若 firstName 不为 null,以下代码便会返回此值。若 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
}
我们可以进行另一项调整。在名称和姓氏缺失时我们的界面日志会显示 "Unknown",因此我们不支持 null 对象。基于这个原因,对于数据类型 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 函数,并移动 map 主体内 for 循环中的逻辑。默认情况下,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
}
}
请注意,如果 user.lastName 为 null,我们会使用 Elvis 运算符返回 "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. 属性和支持属性
我们已看到,自动转换器将 getFormattedUserNames() 函数替换成名为 formattedUserNames 的属性,且该属性具有自定义 getter。在底层,Kotlin 仍会生成 getFormattedUserNames() 方法,且该方法会返回 List。
在 Java 中,我们通过 getter 和 setter 函数公开类属性。Kotlin 可以让我们更好地区分类属性与功能(类可以执行的操作),其中前者以字段表示,而后者则以函数表示。本例中,Repository 类非常简单;该类不执行任何操作,因而只包含字段。
现在,当调用 formattedUserNames Kotlin 属性的 getter 时,即会触发以往在 Java getFormattedUserNames() 函数中触发的逻辑。
虽然我们并未具有与 formattedUserNames 属性完好对应的字段,但 Kotlin 确可提供名为 field 的自动支持字段,且我们还可从自定义 getter 和 setter 中访问该字段(如有需要)。
不过,我们有时还需要自动支持字段所无法提供的一些额外功能。
请审视示例。
Repository 类中存在可变的用户列表,该列表会在函数 getUsers() 中公开,而该函数则由 Java 代码生成,具体如下:
fun getUsers(): List<User>? {
return users
}
但此处存在一个问题:由于返回 users,Repository 类的任何使用者都有权修改用户列表,这并不是一种明智的做法!下面我们将使用支持属性解决这一问题。
首先,将 users 重命名为 _users。突出显示变量名称,右键点击 **Refactor(重构)> Rename(重命名)**变量。然后添加公用的不可变属性,使其返回用户列表。将其命名为 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)
}
}
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 类代码中,我们要将多个 User 对象添加到 _users 列表中。借助 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 提供的所有特性,您将能通过多种方法来提高代码的安全性、简洁性和可读性。例如,我们还可以进一步优化 Repository 类,即直接在声明中通过用户对 _users 列表进行实例化,从而免于使用 init 块,具体如下:
private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
我们阐述了许多主题,包括处理是否可为 null、单一实例、字符串和集合,以及扩展函数、顶层函数、属性和作用域函数等。我们已将两个 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 以及如何在您的平台上使用该语言,请参阅下列资源: