关于此 Codelab
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 以及如何在您的平台上使用该语言,请参阅下列资源: