转换为 Kotlin

1. 欢迎!

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

此 Codelab 适用于任何使用 Java 并考虑将其项目迁移到 Kotlin 的开发者。我们将从几个 Java 类开始,您将使用 IDE 将它们转换为 Kotlin。然后,我们将查看转换后的代码,并了解如何通过使其更具 惯用性 并避免常见陷阱来改进它。

学习内容

您将学习如何将 Java 转换为 Kotlin。在此过程中,您将学习以下 Kotlin 语言功能和概念

  • 处理可空性
  • 实现单例
  • 数据类
  • 处理字符串
  • Elvis 操作符
  • 解构
  • 属性和支持属性
  • 默认参数和命名参数
  • 使用集合
  • 扩展函数
  • 顶层函数和参数
  • letapplywithrun 关键字

假设

您应该已经熟悉 Java。

所需条件

2. 设置

创建新项目

如果您使用的是 IntelliJ IDEA,请使用 Kotlin/JVM 创建一个新的 Java 项目。

如果您使用的是 Android Studio,请使用“无 Activity”模板创建一个新项目。选择 Kotlin 作为项目语言。最低 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;
    }
}

3. 声明可空性、val、var 和数据类

我们的 IDE 可以很好地自动将 Java 代码转换为 Kotlin 代码,但有时需要一点帮助。让我们让我们的 IDE 对转换进行初步处理。然后,我们将逐步了解生成的代码,以了解它是如何以及为何以这种方式转换的。

转到 User.java 文件并将其转换为 Kotlin:**菜单栏 -> 代码 -> 将 Java 文件转换为 Kotlin 文件**。

如果您的 IDE 在转换后提示进行更正,请按

e6f96eace5dabe5f.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 明确指定变量是否可以接受空值。它通过在类型声明中追加 ? 来实现此目的。

因为我们标记了 firstNamelastName 为可空的,所以自动转换器会自动使用 String? 将属性标记为可空的。如果您将 Java 成员注释为非空(使用 org.jetbrains.annotations.NotNullandroidx.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 代码中一个重要且常用的概念。在我们的 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 列表是可空的,因为对象在声明时未实例化
  • 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

请注意,如果属性上没有可见性修饰符,则默认为公共,例如 Repository 对象中的 formattedUserNames 属性。

6. 处理可空性

Repository 类转换为 Kotlin 时,自动转换器使用户列表可为空,因为它在声明时未初始化为对象。因此,对于 users 对象的所有用法,都需要使用非空断言运算符 !!。(您将在转换后的代码中看到 users!!user!!。)!! 运算符将任何变量转换为非空类型,因此您可以访问其属性或调用其函数。但是,如果变量值确实为空,则会抛出异常。通过使用 !!,您冒着在运行时抛出异常的风险。

相反,建议使用以下方法之一处理可空性

  • 执行空检查(if (users != null) {...}
  • 使用 Elvis 运算符 ?:(将在后面的代码实验室中介绍)
  • 使用一些 Kotlin 标准函数(将在后面的代码实验室中介绍)

在我们的例子中,我们知道用户列表不需要可为空,因为它在对象构造后立即初始化(在 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 中看到编译错误,但请继续执行代码实验室的接下来的几个步骤以解决它们。

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 表达式

userNames 列表中的名称尚未完全采用我们想要的格式。由于 lastNamefirstName 都可能为 null,因此我们在构建格式化用户名列表时需要处理可空性。如果任一名称丢失,我们希望显示 "Unknown"。由于 name 变量在设置后不会更改,因此我们可以使用 val 而不是 var。首先进行此更改。

val name: String

看看设置 name 变量的代码。您可能以前没见过将变量设置为等于 if / else 代码块。这是允许的,因为在 Kotlin 中 ifwhen 是表达式——它们返回一个值。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}

您的代码目前使用字符串连接将 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
    }

我们可以进行一次额外的调整。我们的 UI 逻辑在第一个和最后一个名称丢失的情况下显示 "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 函数并将我们之前在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?,而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 仍然会生成一个返回ListgetFormattedUserNames() 方法。

在 Java 中,我们会通过 getter 和 setter 函数公开类的属性。Kotlin 允许我们更好地区分类的属性(用字段表示)和功能(类可以执行的操作,用函数表示)。在本例中,Repository 类非常简单,不执行任何操作,因此它只有字段。

现在,在调用formattedUserNames Kotlin 属性的 getter 时,会触发在 Java getFormattedUserNames() 函数中触发的逻辑。

虽然我们没有显式地拥有与formattedUserNames 属性相对应的字段,但 Kotlin 为我们提供了名为field 的自动支持字段,如果需要,我们可以在自定义 getter 和 setter 中访问它。

但是,有时我们需要自动支持字段无法提供的额外功能。

让我们来看一个例子。

在我们的Repository 类中,我们有一个可变的用户列表,该列表在从 Java 代码生成的getUsers() 函数中公开。

fun getUsers(): List<User>? {
    return users
}

因为我们不希望Repository 类的调用者修改用户列表,所以我们创建了返回只读List<User>getUsers() 函数。在 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 的功能;IterableCollection 上的大量功能都是作为扩展函数实现的。例如,我们在前面步骤中使用的map 函数是Iterable 上的扩展函数。

11. 范围函数:let、apply、with、run、also

在我们的Repository 类代码中,我们正在向_users 列表中添加多个User 对象。借助 Kotlin 范围函数,可以使这些调用更符合惯例。

为了仅在特定对象的上下文中执行代码,而无需根据其名称访问该对象,Kotlin 提供了 5 个范围函数:letapplywithrunalso。这些函数使您的代码更易于阅读和更简洁。所有范围函数都有一个接收器(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)
    }
 }

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

final 对象

val 对象

equals()

==

==

===

仅保存数据的类

data

在构造函数中初始化

init 块中初始化

static 字段和函数

companion object 中声明的字段和函数

单例类

object

要了解有关 Kotlin 以及如何在您的平台上使用它的更多信息,请查看以下资源