Kotlin 中的类和对象实例

1. 准备工作

在此衔接课程的 Codelab 中,您将构建一个 Dice Roller Android 应用。当用户“掷骰子”时,系统会生成一个随机结果。生成结果时会考虑骰子的面数。例如,6 面的骰子,只能生成 1 到 6 的值。

下面是最终应用呈现的样子。

8299aaca25c93863.png

为帮助您专注于了解此应用的全新编程概念,您将使用基于浏览器的 Kotlin 编程工具来创建核心应用功能。该程序会将您的结果输出到控制台。之后您将在 Android Studio 中实现界面。

在此第一个 Codelab 中,您将创建一个 Kotlin 程序,以模拟掷骰子并输出一个随机数字,就像实际掷骰子的情形一样。

前提条件

  • 已了解如何在 https://developer.android.com/training/kotlinplayground 中打开、修改和运行代码
  • 能够创建并运行使用变量和函数的 Kotlin 程序,并将结果输出到控制台。
  • 能够使用采用 ${variable} 表示法的字符串模板设置文本中数字的格式。

学习内容

  • 如何编写程序来生成随机数字以模拟掷骰子。
  • 如何使用变量和方法创建 Dice 类,以构建代码。
  • 如何创建类的对象实例、修改其变量以及调用其方法。

您将构建的内容

  • 使用基于浏览器的 Kotlin 编程工具构建可执行随机掷骰子操作的 Kotlin 程序。

所需工具

  • 一台已连接到互联网的计算机

2. 掷出随机数字

游戏通常具有随机性。您可以赢得随机奖励,或在棋盘游戏中移动随机步数。在日常生活中,您可以使用随机数字和字母来生成更安全的密码!

您可以编写一个程序来模拟掷骰子,而无需真的掷骰子。每次掷骰子时,结果可以是可能值范围内的任何数字。幸运的是,您无需为此类程序自行构建随机数字生成器。大多数编程语言(包括 Kotlin)都内置有用于生成随机数字的方法。在此任务中,您将使用 Kotlin 代码来生成随机数字。

设置起始代码

  1. 在浏览器中,打开网站 https://developer.android.com/training/kotlinplayground
  2. 删除代码编辑器中的所有现有代码,并将其替换为以下代码。这是您在之前的 Codelab 中使用的 main() 函数。
fun main() {

}

使用随机函数

如要掷骰子,您需要一种方法来表示所有有效的掷骰结果值。对于常规的 6 面骰子,可接受的掷骰结果值包括:1、2、3、4、5 和 6。

在前面的课程中,您学习了数据类型,比如 Int 表示整数,String 表示文字。IntRange 是另一种数据类型,表示从起始值到结束值的整数范围。IntRange 是适合用于表示掷骰可能产生的值的数据类型。

  1. main() 函数中,将一个变量定义为名为 diceRangeval。将其分配给 1 到 6 的 IntRange,用于表示 6 面骰子可以掷出的整数值范围。
val diceRange = 1..6

可以看出,1..6 是一个 Kotlin 范围,因为它包含起始数字、两个点以及一个结束数字(中间没有空格)。下面再列举两个整数范围的其他例子,2..5 表示数字 2 到 5,100..200 表示数字 100 到 200。

与调用 println() 可指示系统输出给定文本类似,您可以使用 random() 函数生成并返回给定范围内的随机数字。同之前一样,您可以将结果存储在变量中。

  1. main() 中,将一个变量定义为名为 randomNumberval
  2. randomNumber 设置对 diceRange 范围调用 random() 的结果值,如下所示。
 val randomNumber = diceRange.random()

请注意,在变量和函数调用之间使用一个句点(即点),即可对 diceRange 调用 random()。您可以将其解读为“从 diceRange 中生成随机数字”。然后结果会存储在 randomNumber 变量中。

  1. 为了显示随机生成的数字,请使用字符串格式表示法(也称为“字符串模板”)${randomNumber} 进行输出,如下所示。
println("Random number: ${randomNumber}")

完成后的代码应如下所示。

fun main() {
    val diceRange = 1..6
    val randomNumber = diceRange.random()
    println("Random number: ${randomNumber}")
}
  1. 运行几次代码。每次,您都应看到如下输出内容,其中会显示不同的随机数字。
Random number: 4

3. 创建 Dice 类

当您掷骰子时,它们是您手中的真实物体。而您刚刚编写的代码尽管可以正常运行,但是很难将其与真实的骰子关联起来。编写程序时,如果能让程序更贴合其表示的事物,就会更容易让人理解。所以,如果能用程序创建一个可以投掷的骰子,那就太棒了!

所有骰子的工作原理基本都相同。它们具有相同的属性(如骰面)、相同的行为(例如可以滚动)。在 Kotlin 中,您可以创建骰子的程序蓝图,说明骰子有骰面,并可以掷出一个随机数字。此蓝图称为“类”。

然后,您可以在该类中创建称为“对象实例”的真实骰子对象。例如,您可以创建 12 面的骰子或 4 面的骰子。

定义 Dice 类

在以下步骤中,您将定义一个名为 Dice 的新类来表示可滚动的骰子。

  1. 为了重新开始,请清除 main() 函数中的代码,确保最终代码如下所示。
fun main() {

}
  1. 在此 main() 函数下,添加一个空行,然后添加代码以创建 Dice 类。如下所示,先输入关键字 class,后跟类名称,再跟左大括号和右大括号。在大括号内留出空间,以便放入类的代码。
class Dice {

}

在类定义中,可以使用变量为类指定一个或多个属性。真实的骰子可以具有多个面、一种颜色或重量。在此任务中,您将着重处理骰子的面数属性。

  1. Dice 类中,添加一个名为 sidesvar,用于表示骰子将具有的面数。将 sides 设置为 6。
class Dice {
    var sides = 6
}

大功告成。现在,您有了一个表示骰子的非常简单的类。

创建 Dice 类的实例

有了这个 Dice 类,您就有了表示骰子的蓝图。为了在程序中呈现出一个真实的骰子,您需要创建一个 Dice 对象实例。(如果您需要有三个骰子,就要创建三个对象实例。)

ba2038022410942c.jpeg

  1. 为了创建 Dice 的对象实例,请在 main() 函数中创建一个名为 myFirstDiceval,并将其初始化为 Dice 类的实例。请注意类名称后面的括号,其表示您在通过该类创建一个新的对象实例。
fun main() {
    val myFirstDice = Dice()
}

现在,您已经有了依据蓝图构建的 myFirstDice 对象,接下来可以访问其属性了。Dice 的唯一属性是其 sides。您使用“点分表示法”访问属性。因此,如需访问 myFirstDicesides 属性,请调用 myFirstDice.sides,这读作“myFirstDicesides”。

  1. myFirstDice 声明的下方,添加 println() 语句以输出 myFirstDice.sides 数量。
println(myFirstDice.sides)

您的代码应如下所示。

fun main() {
    val myFirstDice = Dice()
    println(myFirstDice.sides)
}

class Dice {
    var sides = 6
}
  1. 运行程序,它应输出在 Dice 类中定义的 sides 的数量。
6

现在,您有了一个 Dice 类和一个具有 6 个 sides 的真实骰子 myFirstDice

我们来掷骰子吧!

掷骰子

您之前曾使用函数执行过输出蛋糕层的操作。掷骰子操作也可以用函数实现。由于所有骰子都可以滚动,因此您可以在 Dice 类中为其添加一个函数。在类中定义的函数也称为方法。

  1. Dice 类的 sides 变量下方,插入一个空行,然后创建一个用于掷骰子的新函数。首先输入 Kotlin 关键字 fun,再输入方法的名称,然后是括号 (),然后再是左大括号和右大括号 {}。可以在大括号内留一个空行,以便输入更多代码,如下所示。您的类应如下所示。
class Dice {
    var sides = 6

    fun roll() {

    }
}

当您掷一个六面骰子时,它会产生一个从 1 到 6 的随机数字。

  1. roll() 方法中,创建一个 val randomNumber。在 1..6 范围内为其分配一个随机数字。使用点分表示法对该范围调用 random()
val randomNumber = (1..6).random()
  1. 生成随机数字后,将其输出到控制台。完成后的 roll() 方法应如以下代码所示。
fun roll() {
     val randomNumber = (1..6).random()
     println(randomNumber)
}
  1. 为了真实地滚动 myFirstDice,请在 main() 中对 myFirstDice 调用 roll() 方法。您使用“点分表示法”调用方法。因此,为了对 myFirstDice 调用 roll() 方法,请输入 myFirstDice.roll(),这读作“myFirstDice dot roll()”。
myFirstDice.roll()

完成后的代码应如下所示。

fun main() {
    val myFirstDice = Dice()
    println(myFirstDice.sides)
    myFirstDice.roll()
}

class Dice {
    var sides = 6

    fun roll() {
        val randomNumber = (1..6).random()
        println(randomNumber)
    }
}
  1. 运行代码!您应会看到随机掷骰子的结果小于骰子面数。多运行几次代码,会发现面数保持不变,掷骰子的结果值发生变化。
6
4

祝贺您!您已使用 sides 变量和 roll() 函数定义了 Dice 类。在 main() 函数中,您创建了一个新的 Dice 对象实例,然后对该实例调用了 roll() 方法,以生成随机数字。

4. 返回您掷骰子的结果值

您目前是在 roll() 函数中输出 randomNumber 的值,运行一切正常!但有时,将函数的结果返回给调用该函数的对象会更有用。例如,您可以将 roll() 方法的结果分配给某个变量,然后将玩家移动相应的步数!我们来看看具体操作方法。

  1. main() 中,修改显示 myFirstDice.roll() 的代码行。创建名为 diceRollval。将其设置为与 roll() 方法返回的值相等。
val diceRoll = myFirstDice.roll()

目前还没有任何效果,因为 roll() 尚未返回任何内容。为了使上述代码按预期运行,roll() 必须返回一些内容。

在之前的 Codelab 中,您了解到需要为函数的输入参数指定数据类型。同样,您也必须为函数返回的数据指定数据类型。

  1. 更改 roll() 函数以指定将返回的数据类型。在本例中,随机数字为 Int,因此返回类型为 Int。指定返回类型的语法如下:在函数名称后面,在括号后添加冒号、空格,然后为函数返回类型添加 Int 关键字。函数定义应如下代码所示。
fun roll(): Int {
  1. 运行该代码。您将在问题视图中看到一个错误。错误内容是:
A ‘return' expression required in a function with a block body (‘{...}')

您将函数定义改为了返回 Int,但系统提示说您的代码实际上并未返回 Int。“块主体”或“函数主体”是指函数大括号内的代码。您可以修复此错误,方法是在函数主体末尾使用 return 语句从函数返回值。

  1. roll() 中,移除 println() 语句,并将其替换为 randomNumberreturn 语句。您的 roll() 函数应如以下代码所示。
fun roll(): Int {
     val randomNumber = (1..6).random()
     return randomNumber
}
  1. main() 中,移除有关骰子面数的输出语句。
  2. 添加一个语句,用信息性句子输出 sidesdiceRoll 的值。您完成的 main() 函数应类似于以下代码。
fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}
  1. 运行代码,输出应如下所示。
Your 6 sided dice rolled 4!

以下是到目前为止所编写的所有代码。

fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}

class Dice {
    var sides = 6

    fun roll(): Int {
        val randomNumber = (1..6).random()
        return randomNumber
    }
}

5. 更改骰子的面数

并非所有骰子都有 6 个面!骰子形状各异,大小不一:有 4 面的、8 面的,还有 120 面的!

  1. Dice 类的 roll() 方法中,将硬编码的 1..6 更改为使用 sides,这样,范围和掷出的随机数字将始终适用于面数。
val randomNumber = (1..sides).random()
  1. main() 函数中,在输出掷骰结果的后面,将 myFirstDicesides 设置为 20。
myFirstDice.sides = 20
  1. 复制下面的现有输出语句并将其粘贴在更改面数的位置下方。
  2. diceRoll 的输出结果替换为对 myFirstDice 调用 roll() 方法的输出结果。
println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!")

您的程序应如下所示。

fun main() {

    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")

    myFirstDice.sides = 20
    println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!")
}

class Dice {
    var sides = 6

    fun roll(): Int {
        val randomNumber = (1..sides).random()
        return randomNumber
    }
}
  1. 运行程序,您应该会看到一条有关 6 面骰子的消息,以及另一条有关 20 面骰子的消息。
Your 6 sided dice rolled 3!
Your 20 sided dice rolled 15!

6. 自定义骰子

类的概念是代表一个事物,通常是现实世界中的事物。在本例中,Dice 类代表实际的骰子。在现实世界中,骰子的面数是不会变的。如果您需要不同的面数,就需要使用不同的骰子。在编程上,这意味着您应该创建一个具有所需面数的新骰子对象实例,而不是更改现有 Dice 对象实例的面数属性。

在此任务中,您将修改 Dice 类,以便在创建新实例时指定面数。您将更改 Dice 类定义,以便可以提供面数。这类似于函数接受输入参数的方式。

  1. 修改 Dice 类定义,以接受名为 numSides 的整数。类中的代码不变。
class Dice(val numSides: Int) {
   // Code inside does not change.
}
  1. Dice 类中,删除 sides 变量,因为您现在可以使用 numSides
  2. 此外,修改范围以使用 numSides

您的 Dice 类应如下所示。

class Dice (val numSides: Int) {

    fun roll(): Int {
        val randomNumber = (1..numSides).random()
        return randomNumber
    }
}

如果运行这段代码,您将看到大量错误,因为需要更新 main() 才能使用对 Dice 类的更改。

  1. main() 中,为了创建具有 6 个面的 myFirstDice,您现在必须提供面数作为 Dice 类的参数,如下所示。
    val myFirstDice = Dice(6)
  1. 在输出语句中,将 sides 更改为 numSides
  2. 然后在下方,删除将 sides 更改为 20 的代码,因为该变量已不存在。
  3. 同时删除其下方的 println 语句。

您的 main() 函数应类似于以下代码,如果运行该代码段,应该不会再出现错误了。

fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
}
  1. 输出第一个掷骰结果后,添加代码以创建并输出第二个名为 mySecondDiceDice 对象,其具有 20 个面。
val mySecondDice = Dice(20)
  1. 添加用于掷骰并输出返回值的输出语句。
println("Your ${mySecondDice.numSides} sided dice rolled  ${mySecondDice.roll()}!")
  1. 完成后的 main() 函数应如下所示。
fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")

    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        val randomNumber = (1..numSides).random()
        return randomNumber
    }
}
  1. 运行已完成的程序,输出内容应如下所示。
Your 6 sided dice rolled 5!
Your 20 sided dice rolled 7!

7. 采用规范的编码做法

编写代码时,应尽量简洁。您可以去掉 randomNumber 变量并直接返回随机数字。

  1. 更改 return 语句以直接返回随机数字。
fun roll(): Int {
    return (1..numSides).random()
}

在第二个输出语句中,您将用于获取随机数的调用放入字符串模板。您可以按照在第一个输出语句中采用的相同方法来去掉 diceRoll 变量。

  1. 在字符串模板中调用 myFirstDice.roll() 并删除 diceRoll 变量。您的 main() 代码的前两行现在如下所示。
val myFirstDice = Dice(6)
println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
  1. 运行代码,输出内容应该没有变化。

以下是您重构后的最终代码。

fun main() {
    val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")

    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}

8. 解决方案代码

fun main() {
    val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")

    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}

9. 总结

  • IntRange 调用 random() 函数以生成随机数字:(1..6).random()
  • 类就像是对象的蓝图。它们可以具有作为变量和函数实现的属性和行为。
  • 类的实例代表对象,通常是实物,如骰子。可以调用针对对象的操作并更改其属性。
  • 创建实例时,可以向类提供值。例如:创建 class Dice(val numSides: Int),然后通过 Dice(6) 创建实例。
  • 函数可以返回一些内容。可以在函数定义中指定要返回的数据类型,并在函数正文中使用 return 语句来返回内容。例如:fun example(): Int { return 5 }

10. 了解详情

11. 自行练习

进行以下练习

  • Dice 类指定另一种颜色属性,并创建多个具有不同面数和颜色的实例!
  • 创建 Coin 类,使其能够翻转,创建该类的实例,并抛掷多个硬币!您将如何将 random() 函数与范围结合使用来实现硬币的抛掷?