转换为 Kotlin

在本 Codelab 中,您将学习如何将代码从 Java 转换为 Kotlin。您还将学习 Kotlin 语言规范,以及如何确保您编写的代码符合这些规范。

此 Codelab 的适用对象为任何使用 Java 并考虑将其项目迁移到 Kotlin 的开发者。我们将从几个 Java 类入手,您需使用 IDE 将它们转换为 Kotlin 类。接着,我们将查看转换后的代码,了解如何加以改善,使其更符合编程习惯,同时避免常见缺陷。

学习内容

您将学习如何将 Java 转换为 Kotlin。在此过程中,您将了解 Kotlin 语言的以下特点和概念:

  • 处理可为 null 性
  • 实现单例
  • 数据类
  • 处理字符串
  • Elvis 运算符
  • 解构
  • 属性和后备属性
  • 默认参数和具名参数
  • 使用集合
  • 扩展函数
  • 顶级函数与参数
  • letapplywithrun 关键字

假设

您应已熟悉 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 文件,具体方法是依次点击 Menu bar -> Code -> Convert Java File to Kotlin File

如果 IDE 在转换后提示更正,请按 Yes

25e57db5b8e76557.png

您应该会看到以下 Kotlin 代码:

class User(var firstName: String?, var lastName: String?)

请注意,User.java 已重命名为 User.kt。Kotlin 文件的扩展名为 .kt。

Java User 类中有两个属性:firstNamelastName。这两个属性都具有 getter 和 setter 方法,因此它们的值可变。Kotlin 的可变变量的关键字是 var,因此对于这两个属性,转换器均会使用 var。如果 Java 属性只有 getter,则属性值不可变,且会声明为 val 变量。val 类似于 Java 中的 final 关键字。

Kotlin 与 Java 之间的一个关键区别在于,Kotlin 会明确指定变量能否接受 null 值。具体而言,它通过在类型声明中附加 ? 进行指定。

因为我们将 firstNamelastName 标记为可为 null,所以自动转换器会自动使用 String? 将这两个属性标记为可为 null。如果您使用 org.jetbrains.annotations.NotNullandroidx.annotation.NonNull 为 Java 成员添加非 null 的注解,转换器会识别这一情况,在 Kotlin 中将这些字段也设为非 null。

此时我们已完成基本的转换流程。不过,我们可以采用更符合编程习惯的方式编写代码。下面就让我们一探究竟。

数据类

User 类仅保存数据。对于具有这一角色的类,Kotlin 有一个对应的关键字:data。在将此类标记为 data 类后,编译器便会自动创建 getter 和 setter。此外,编译器还会派生 equals()hashCode()toString() 函数。

data 关键字添加到 User 类:

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

在 Kotlin 中,我们可以为函数调用中的参数分配默认值。当参数被省略时,系统便会使用默认值。在 Kotlin 中,构造函数也属于函数的一种,因此我们可以使用默认参数来将 lastName 的默认值指定为 null。为此,我们只需为 lastName 分配 null

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 对象声明中指定名字和姓氏,因此不需要默认值。

在继续本 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 类中,我们可以看到,用户属性已在声明中初始化。

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,同时移除伴生对象。

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 属性。

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)。这样一来,我们便可直接处理 firstNamelastName 值。更新 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 表达式

userName 列表中的名称还不完全是我们需要的格式。由于 lastNamefirstName 均可为 null,因此在构建设置了格式的用户名列表时,我们需要处理可为 null 性。我们希望在缺少两个名称中的一个时显示 "Unknown"。由于 name 变量一经设置便无法更改,因此我们可以使用 val 代替 var。先进行此项更改。

val name: String

我们来看一下设置名称变量的代码。您可能是初次看到设为 equal 的变量和 if/else 代码块。之所以允许这样做,是因为在 Kotlin 中,ifwhenforwhile 是表达式,它们会返回一个值。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"
}

Kotlin 提供字符串模板,让您可以轻松处理 String利用字符串模板,您可以通过在变量前加上 $ 符号,在字符串声明内引用变量。您还可以通过将表达式放在 { } 内并在其前面加上 $ 符号,将表达式放在字符串声明中。示例:${user.firstName}

您的代码目前使用字符串串联将 firstNamelastName 组合成用户名。

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>

下面我们来深入探讨 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,但为便于阅读,您可以将 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?,而 Stringname 来说是必需的。

...
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 仍会生成 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)
    }
}

目前 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 的功能;IterableCollection 上的许多功能则以扩展函数的形式实现。例如,我们在上一步中使用的 map 函数即为 Iterable 上的扩展函数。

Repository 类代码中,我们要将多个 User 对象添加到 _users 列表中。借助 Kotlin 作用域函数,我们能以更符合编程习惯的方式执行这些调用。

为仅在特定对象的上下文中执行代码,而无需根据名称来访问该对象,Kotlin 提供了 letapplywithrunalso 这 5 种作用域函数。这些函数可使代码更易于阅读且更简洁。所有作用域函数都具有接收器 (this),可能具有参数 (it),且可能返回值。

下面的实用备忘单可帮助您记住各种函数的使用情形:

6b9283d411fb6e7b.png

由于我们要在 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 符合编程习惯,可助您编写出简短而又亲切的代码。借助 Kotlin 提供的所有特性,您可以通过多种方法来提高代码的安全性、简洁性和可读性。例如,我们可以直接在声明中对包含用户的 _users 列表进行实例化,以避免使用 init 块,从而优化 Repository 类:

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 功能的 TL;DR 及其在 Kotlin 对应的项:

Java

Kotlin

final 对象

val 对象

equals()

==

==

===

仅保存数据的类

data

构造函数中的初始化

init 块中的初始化

static 字段和函数

companion object 中声明的字段和函数

单例类

object

如需详细了解 Kotlin 以及如何在您的平台上使用 Kotlin,请参阅下列资源: