上一篇:【向 Dice Roller 应用添加图片】
前提条件
main()
程序、能够返回值的带参数的函数、变量、数据类型和操作,以及 if/else
语句。学习内容
构建内容
所需条件
人们很自然地会将具有相似属性和行为的事物划分为组,甚至会在这些组之间建立某种类型的层次结构。例如,您可以有一个比较宽泛的类别(例如蔬菜),在该类别内,您可以设置更具体的类型(例如豆类)。在豆类中,您可以划分更加具体的类型,例如豌豆、黄豆、扁豆、鹰嘴豆和大豆。
这可以表示为层次结构,因为豆类包含或继承了蔬菜的所有属性(例如,它们属于植物,且可以食用)。同样地,豌豆、黄豆和扁豆均具有豆类的属性,同时也具有自己独特的属性。
我们来看看,如何用编程术语表示这种关系。如果您将 Vegetable
作为 Kotlin 中的一个类,您可以创建 Legume
作为 Vegetable
类的子级或子类。这意味着,Vegetable
类的所有属性和方法均会被 Legume
类继承(即,这些属性和方法同样可用于后者)。
您可以在类层次结构示意图中表示这种关系,如下所示。您可将 Vegetable
(蔬菜) 称为 Legume
(豆类) 类的父级或父类。
您可以通过创建 Legume
的子类(例如 Lentil
(扁豆) 和 Chickpea
(鹰嘴豆))继续扩展类层次结构。这会使 Legume
既是 Vegetable
的子级或子类,同时也是 Lentil
和 Chickpea
的父级或父类。Vegetable
是此层次结构的根类或顶级类(也称为基类)。
注意: 术语总结
下面总结了此文章中使用的术语,及其在 Kotlin 类语境下的含义。
- 类层次结构。一种将类按父级和子级层次结构进行整理的排列方式。层次结构示意图通常会以父级在上、子级在下的方式绘制。
- 子级或子类。层次结构中位于其他类下方的任意类。
- 父级、父类或基类。具有一个或多个子类的任意类。
- 根类或顶级类。位于类层次结构顶部(或根部)的类。
- 继承。子类包含(或继承)其父类的所有属性和方法的情况。借助继承,您可以共享和重复使用代码,从而使程序更易于理解和维护。
Android 类中的继承
正如您在之前的文章中所做的那样,虽然您可以在不使用类的情况下编写 Kotlin 代码,但 Android 的很多部分都是以类的形式向您提供的,包括 activity、视图和视图组。因此,理解类层次结构是 Android 应用开发的基础,并且有助于您利用 Android 框架提供的诸多功能。
例如,Android 中有一个 View
类,它表示屏幕上的一个矩形区域,并且负责绘制和事件处理。TextView
类是 View
类的子类,这意味着 TextView
会继承 View
类的所有属性和函数,并会添加特定的逻辑,以向用户显示文本。
再往下一级,EditText
和 Button
类是 TextView
类的子级。它们会继承 TextView
和 View
类的所有属性和方法,并会添加自己的特定逻辑。例如,EditText
添加了自己的功能,能够修改屏幕上的文本。
您不必从 View
和 TextView
类中复制所有逻辑,并将其粘贴到 EditText
类中,EditText
可以直接子类化 TextView
类,后者再子类化 View
类。然后,EditText
类中的代码便可专注于使此界面组件不同于其他视图的其他方面。
在 developer.android.com 网站上有关 Android 类的文档页面顶部,您可以看到类层次结构示意图。如果您在层次结构顶部看到 kotlin.Any
,这是因为在 Kotlin 中,所有类都具有一个通用父类 Any。请点击此处了解详情。
如您所见,学习利用类之间的继承关系,可使您的代码更易于编写、复用、阅读和测试。
住房的类层次结构
在此文章中,您将构建一个 Kotlin 程序,该程序会以具有建筑空间、楼层和住客的住房(人们的住处)为例,演示类层次结构的工作方式。
下面是您要构建的类层次结构的示意图。在根部,有一个住宅( Dwelling
),它指定了适用于所有住房的属性和函数,类似于蓝图。然后还有多个类,分别对应方形小木屋 (SquareCabin
)、圆形小屋 (RoundHut
) 和圆形塔楼 (RoundTower
)(即多楼层的 RoundHut
)。
您将实现的类:
Dwelling
:一个表示非特定住房的基类,可存储所有住房通用的信息。SquareCabin
:用木材建成的方形小木屋,建筑平面为方形。RoundHut
:用稻草搭成的圆形小屋,建筑平面为圆形,它是 RoundTower
的父级。RoundTower
:用石头建成的多楼层圆形塔楼,建筑平面为圆形。创建 Dwelling 抽象类
任何类都可以是类层次结构的基类或其他类的父类。
“抽象”类是一种无法实例化的类,因为它没有完全实现。您可以将其视为一个草图。草图包含关于某些事的想法和计划,但其中的信息通常并不足以完成构建。您可以使用草图(抽象类)来创建一个蓝图(类),然后基于后者构建实际的对象实例。
创建父类的一个常见好处是,能够包含其所有子类通用的属性和函数。如果属性的值及函数的实现情况未知,则可以将其作为抽象类。例如,Vegetables
中有许多所有蔬菜通用的属性,但您无法为某种非特定的蔬菜创建实例,因为您不知道关于它的某些信息,例如其形状或颜色。因此,Vegetable
是一个抽象类,每种蔬菜的具体细节交由子类来确定。
抽象类的声明以 abstract
关键字开头。
注意: 在本课程后面的文章中,您会遇到一些需要设置子类的 Android 中的抽象类。
Dwelling
像 Vegetable
一样,也会是一个抽象类。它将包含很多类住房通用的属性和函数,但属性的确切值和函数的实现细节未知。
访问 Kotlin Playground,网址为:https://developer.android.com/training/kotlinplayground。
在编辑器中,删除 main()
函数中的 println("Hello, world!")
。
然后,在 main()
函数下方添加以下代码,以创建一个名为 Dwelling
的 abstract
类。
abstract class Dwelling(){
}
添加有关建筑材料的属性
在此 Dwelling
类中,您可以定义适用于所有住房的属性,即使对于不同住房来说,这些属性的值可能会有所不同。所有住房都是由某种建筑材料建造而成的。
Dwelling
中,创建一个类型为 String
的 buildingMaterial
变量来表示建筑材料。由于建筑材料不会变化,可以使用 val
使其成为不可变变量。val buildingMaterial: String
Property must be initialized or be abstract
buildingMaterial
属性没有值。实际上,您不能为其提供值,因为非特定建筑并非由任何特定建筑材料建造而成。因此,如错误消息所示,您可以添加 abstract
关键字作为 buildingMaterial
声明的前缀,以表明该属性不会在此处进行定义。
abstract
关键字添加到变量定义中。abstract val buildingMaterial: String
main()
函数中构建一个 Dwelling
实例,然后运行代码。val dwelling = Dwelling()
Cannot create an instance of an abstract class
到目前为止,您已完成的代码如下所示:
abstract class Dwelling(){
abstract val buildingMaterial: String
}
添加有关可容纳人数的属性。
住房的另一个属性是可容纳人数,即屋内可以住多少人。
所有住房的可容纳人数都不会变。但是,不能在 Dwelling
父类中设置可容纳人数,而应在特定类型住房对应的子类中进行定义。
Dwelling
中,添加一个名为 capacity
的 abstract
整数 val
。abstract val capacity: Int
添加有关住客人数的私有属性
所有住房都有一定数量的 residents
(即住在该住房里的人数,该值可能小于或等于 capacity
),因此应在 Dwelling
父类中定义 residents
属性,以便所有子类继承和使用。
residents
作为传递给 Dwelling
类构造函数的参数。residents
属性是一个 var
,因为住客人数在实例创建后可能会变化。abstract class Dwelling(private var residents: Int) {
请注意,residents
属性使用 private
关键字进行了标记。Private 是 Kotlin 中的可见性修饰符,表示 residents
属性仅对此类可见(并且只能在此类中使用)。它不能从程序中的其他地方访问。您可以使用 private 关键字标记属性或方法。否则,如果不指定可见性修饰符,相应属性和方法会默认为 public
,可以从程序的其他部分访问。由于住房的住客人数通常是隐私信息(相对于建筑材料或可容纳人数方面的信息),将其标记为 private 是明智之举。
定义好住房的 capacity
和当前 residents
人数后,您可以创建一个函数 hasRoom()
来确定该住房能否容纳其他住客。您可以在 Dwelling
类中定义和实现 hasRoom()
函数,因为用于计算能否容纳其他住客的公式对于所有住房都一样。如果 residents
人数小于 capacity
,则意味着相应 Dwelling
能容纳其他住客,函数应按这种比较方法返回 true
或 false
。
hasRoom()
函数添加到 Dwelling
类。fun hasRoom(): Boolean {
return residents < capacity
}
完成后的代码应如下所示:
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
创建 SquareCabin 子类
Dwelling
类下,创建一个名为 SquareCabin
的类。class SquareCabin
SquareCabin
与 Dwelling
相关。在您的代码中,您需要表明 SquareCabin
扩展自 Dwelling
(或者是 Dwelling
) 的子类),因为 SquareCabin
将会提供 Dwelling
抽象部分的实现。通过在 SquareCabin
类名称后添加一个冒号 (:
),后跟用于初始化父级 Dwelling
类的调用,表明此继承关系。不要忘记在 Dwelling
类名称后添加圆括号。
class SquareCabin : Dwelling()
Dwelling
需要输入 residents
人数。您可以传入固定的住客人数,例如 3
。class SquareCabin : Dwelling(3)
不过,您希望您的程序能够更加灵活,并且允许 SquareCabins
拥有不同的住客人数。因此,在 SquareCabin
类定义中将 residents
作为一个参数。不要将 residents
声明为 val
,,因为您是在重复使用已在父类 Dwelling
中声明的属性。
class SquareCabin(residents: Int) : Dwelling(residents)
注意: 使用这些类定义后,系统会在后台执行许多操作。
在类标头中,您会看到
class SquareCabin(residents: Int) ...
这实际上是
class SquareCabin constructor(residents: Int) ...
的简写形式当您通过某个类创建对象实例时,系统会调用
constructor
。例如,当您调用SquareCabin(4)
时,系统会调用
SquareCabin
的constructor
,以初始化对象实例。
constructor
会根据相应类中的所有信息(包括传入的参数)构建实例。当某个类从父级继承属性和函数时,constructor
会调用父类的constructor
,以完成对象实例的初始化。因此,当您使用
SquareCabin(4)
创建实例时,系统将执行SquareCabin
的
constructor
,由于继承关系,同时还会执行Dwelling
构造函数。Dwelling
类定义指明其构造函数需要
residents
参数,因此,您会在SquareCabin
类定义中看到传递给Dwelling
构造函数的
residents
。在之后的 文章中,您将详细了解构造函数。
Class 'SquareCabin' is not abstract and does not implement abstract base class member public abstract val buildingMaterial: String defined in Dwelling
如果您声明抽象函数和变量,就表示您承诺稍后将为它们提供值和实现。对于变量,这意味着该抽象类的任何子类都需要为其提供值。对于函数,这意味着任何子类都需要实现函数主体。
在 Dwelling
类中,您定义了一个 abstract
变量 buildingMaterial
。SquareCabin
是 Dwelling
的子类,因此它必须为 buildingMaterial
提供值。使用 override
关键字来表明此属性是在父类中定义的,并且在此类中将被替换掉
SquareCabin
类中,override buildingMaterial
属性并为其分配值 "Wood"
。capacity
执行相同的操作,并表明一个` SquareCabin 中可以住 6 人。class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
完成后的代码应如下所示。
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
如需测试代码,请在程序中创建 SquareCabin
实例。
使用 SquareCabin
Dwelling
和 SquareCabin
类定义前插入一个空 main()
函数。fun main() {
}
abstract class Dwelling(private var residents: Int) {
...
}
class SquareCabin(residents: Int) : Dwelling(residents) {
...
}
main()
函数中,创建一个名为 squareCabin
且住客人数为 6 的 SquareCabin
实例。针对建筑材料、可容纳人数和 hasRoom()
函数添加输出语句。fun main() {
val squareCabin = SquareCabin(6)
println("\nSquare Cabin\n============")
println("Capacity: ${squareCabin.capacity}")
println("Material: ${squareCabin.buildingMaterial}")
println("Has room? ${squareCabin.hasRoom()}")
}
请注意,hasRoom()
函数不是在 SquareCabin
类中定义的,而是在 Dwelling
类中定义的。由于 SquareCabin
是 Dwelling
类的子类,hasRoom()
函数是未经任务操作直接继承而来的。现在,可以在 SquareCabin
的所有实例上调用 hasRoom()
函数,如代码段 squareCabin.hasRoom()
所示。
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false
您创建的 squareCabin
住客人数为 6,该值等于 capacity
的值,因此 hasRoom()
返回 false
。您可以尝试使用较少人数的 residents
初始化 SquareCabin
,当您再次运行程序时,hasRoom()
应该会返回 true
。
使用 with 简化代码
在 println()
语句中,每次引用 squareCabin
的属性或函数时,都必须反复输入 squareCabin.
。这会形成重复,可能在您复制和粘贴输出语句时造成错误。
当您使用某个类的特定实例,并需要访问该实例的多个属性和函数时,您可以使用 with
语句表明“对此实例对象执行以下所有操作”。先输入关键字 with
,再在圆括号内输入实例名称,然后再输入大括号并在其中指明您想要执行的操作。
with (instanceName) {
// all operations to do with instanceName
}
main()
函数中,将您的输出语句改为使用 with
。squareCabin.
”。with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false
完成的代码如下所示:
fun main() {
val squareCabin = SquareCabin(6)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
创建 RoundHut 子类
SquareCabin
相同的方式,为 Dwelling
添加另一个子类 RoundHut
。buildingMaterial
并为其提供值 “Straw
”。capacity
并将其设置为 4。class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
main()
中,创建一个住客人数为 3 的 RoundHut
实例。val roundHut = RoundHut(3)
roundHut
的相关信息。with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false
Round Hut
=========
Material: Straw
Capacity: 4
Has room? true
现在,您有了一个如下所示的类层次结构,其中 Dwelling
为根类,SquareCabin
和 RoundHut
为 Dwelling
的子类。
创建 RoundTower 子类
这个类层次结构中处于最终层级的类是圆形塔楼。您可以将圆形塔楼视为一个由石头建造而成的多楼层圆形小屋。因此,您可以将 RoundTower
作为 RoundHut
的子类。
RoundTower
类,它是 RoundHut
的子类。将 residents
参数添加到 RoundTower
的构造函数中,然后将该参数传递给 RoundHut
父类的构造函数。buildingMaterial
替换为 "Stone"
。capacity
设置为 4
。class RoundTower(residents: Int) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4
}
This type is final, so it cannot be inherited from
此错误意味着,RoundHut
类不能被子类化(或被继承)。默认情况下,在 Kotlin 中,类是最终层级,无法被子类化。您只能从 abstract
类或标记有 open
关键字的类继承。因此,您需要使用 open
关键字标记 RoundHut
类,使其能够被继承。
注意: 定义抽象类时,您无需使用
open
关键字。例如,无需使用open
关键字标记Dwelling
类。系统已假设您需要子类化抽象类,以实现抽象属性和函数。
RoundHut
声明的开头添加 open
关键字。open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
main()
中,创建一个 roundTower
实例并输出其相关信息。 val roundTower = RoundTower(4)
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
以下是完整代码。
fun main() {
val squareCabin = SquareCabin(6)
val roundHut = RoundHut(3)
val roundTower = RoundTower(4)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
class RoundTower(residents: Int) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4
}
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false
Round Hut
=========
Material: Straw
Capacity: 4
Has room? true
Round Tower
==========
Material: Stone
Capacity: 4
Has room? false
为 RoundTower 添加多个楼层
顾名思义,RoundHut
是单层建筑,而塔楼通常有多层(楼层)。
对于可容纳人数,塔楼的楼层越多,可容纳人数应该就越多。
您可以将 RoundTower
修改为多楼层,并根据楼层数量调整可容纳人数。
RoundTower
构造函数,以接受另一个整数参数 val floors
作为楼层数量。将其置于 residents
之后。请注意,您无需将此参数传递给父级 RoundHut
构造函数,因为 floors
是在 RoundTower
中定义的,而 RoundHut
没有 floors
。class RoundTower(
residents: Int,
val floors: Int) : RoundHut(residents) {
...
}
注意: 如果您有多个参数,而且类或函数定义太长,一行无法完全显示,您可以将其划分为多行,如上面的代码所示。
main()
方法中创建 roundTower
时出现错误,因为您没有为 floors
参数提供数字。您可以添加缺少的参数。或者,您也可以在 RoundTower
的类定义中为 floors
添加默认值,如下所示。然后,在没有 floors
值传递到构造函数的情况下,系统就可以使用默认值创建对象实例。
floors
声明后添加 = 2
,以为其分配默认值 2。class RoundTower(
residents: Int,
val floors: Int = 2) : RoundHut(residents) {
...
}
RoundTower(4)
现在会使用 2 层楼的默认值创建 RoundTower
对象实例。RoundTower
类中,更新 capacity
,用之前的值乘以楼层数。override val capacity = 4 * floors
RoundTower
的可容纳人数现在为 8 人(2 层)。下面是完成后的代码。
fun main() {
val squareCabin = SquareCabin(6)
val roundHut = RoundHut(3)
val roundTower = RoundTower(4)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
}
class SquareCabin(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
}
open class RoundHut(residents: Int) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
}
class RoundTower(
residents: Int,
val floors: Int = 2) : RoundHut(residents) {
override val buildingMaterial = "Stone"
override val capacity = 4 * floors
}
计算建筑面积
在本练习中,您将学习如何在抽象类中声明抽象函数,然后在子类中实现其函数。
所有住房都具有建筑面积,但其计算方式因住房形状而有所不同。
在 Dwelling 类中定义 floorArea()
Dwelling
类中添加一个 abstract
floorArea()
函数。返回 Double
。Double 与 String
和 Int
一样,是一种数据类型;它可用于浮点数(即带有小数点且后跟小数部分的数字,例如 5.8793)。abstract fun floorArea(): Double
抽象类中定义的所有抽象方法都必须在其任意子类中实现。您需要先在子类中实现 floorArea()
,然后才能运行代码。
针对 SquareCabin 实现 floorArea()
与实现 buildingMaterial
和 capacity
一样,由于您要实现的 abstract
函数是在父类中定义的,您需要使用 override
关键字。
SquareCabin
类中,首先输入关键字 override
,后跟 floorArea()
函数的实际实现,如下所示。override fun floorArea(): Double {
}
return length * length
。override fun floorArea(): Double {
return length * length
}
在此类中,长度是一个变量,每个实例的长度各不相同,因此您可以将其添加为 SquareCabin
类的构造函数参数。
SquareCabin
的类定义,以添加 Double
类型的 length
参数。将该属性声明为 val
,因为建筑的长度不会变化。class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {
Dwelling
具有构造函数参数 residents
,因此其所有子类都具有该参数。由于这是 Dwelling
构造函数中的第一个参数,所以最好也将其作为所有子类构造函数中的第一个参数,并在所有类定义中以相同的顺序排列参数。因此,在 residents
参数后插入新的 length
参数。
main()
中,更新创建 squareCabin
实例的代码。将 50.0
作为 length
传入 SquareCabin
构造函数。val squareCabin = SquareCabin(6, 50.0)
squareCabin
的 with
语句中,添加针对建筑面积的输出语句。println("Floor area: ${floorArea()}")
您的代码将不会运行,因为您还需要在 RoundHut
中实现 floorArea()
。
针对 RoundHut 实现 floorArea()
以同样的方式针对 RoundHut
实现建筑面积。RoundHut
也是 Dwelling
的直接子类,因此您需要使用 override
关键字。
圆形住房建筑面积的计算方法为,PI * 半径 * 半径。
PI
是一个数学值。它会在数学库中进行定义。库是一个预定义的函数和值集合,在程序外部定义,但可供程序使用。为了使用某个库函数或值,您需要告知编译器您将使用该函数或值。方法是将该函数或值导入到程序中。如需在程序中使用 PI
,您需要导入 kotlin.math.PI
。
PI
。将此导入语句放在文件的顶部,main()
之前。import kotlin.math.PI
RoundHut
实现 floorArea()
函数。override fun floorArea(): Double {
return PI * radius * radius
}
警告: 如果您不导入 kotlin.math.PI,将会遇到错误,因此请在使用之前导入此库。或者,您可以写出 PI 的完全限定版本,如“kotlin.math.PI * 半径 * 半径”中所示,然后便无需添加导入语句。
RoundHut
构造函数以传入 radius
。open class RoundHut(
residents: Int,
val radius: Double) : Dwelling(residents) {
main()
中,通过向 RoundHut
构造函数传入 radius
10.0
,更新 roundHut
的初始化。val roundHut = RoundHut(3, 10.0)
roundHut
的 with
语句中添加输出语句。println("Floor area: ${floorArea()}")
针对 RoundTower 实现 floorArea()
您的代码尚无法正常运行,它会失败并显示以下错误消息:
Error: No value passed for parameter 'radius'
在 RoundTower
中,为了使程序能够顺利编译,您不需要实现 floorArea()
,因为它能够从 RoundHut
继承,但您需要更新 RoundTower
类定义,使其具有与其父级 RoundHut
相同的 radius
参数。
radius
。将 radius
放在 residents
之后、floors
之前。建议将具有默认值的变量列在末尾。请记得将 radius
传递给父类构造函数。class RoundTower(
residents: Int,
radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
main()
中更新初始化 roundTower
的代码。val roundTower = RoundTower(4, 15.5)
floorArea()
的输出语句。println("Floor area: ${floorArea()}")
RoundTower
的计算方法不正确,因为它继承自 RoundHut
,并未考虑 floors
数量。RoundTower
中,override floorArea()
以便为其提供不同的实现,将面积乘以楼层数量。注意,您可以在抽象类 (Dwelling
) 中定义一个函数,在子类 (RoundHut
) 中实现它,然后在子类的子类 (RoundTower
) 中再次替换它。这是一种两全其美的做法,您既可以继承自己需要的函数,又能替换掉自己不需要的函数。override fun floorArea(): Double {
return PI * radius * radius * floors
}
此代码能够正常运行,不过有一种方法可以避免反复使用父类 RoundHut
中已有的代码。您可以从父类 RoundHut
中调用 floorArea()
函数,后者会返回 PI * radius * radius
。然后将该结果乘以 floors
数。
RoundTower
中,将 floorArea()
更新为使用其父类中的 floorArea()
实现。使用 super
关键字,调用父级中定义的函数。override fun floorArea(): Double {
return super.floorArea() * floors
}
RoundTower
将输出相应楼层数量对应的正确建筑面积。是完成后的代码:
import kotlin.math.PI
fun main() {
val squareCabin = SquareCabin(6, 50.0)
val roundHut = RoundHut(3, 10.0)
val roundTower = RoundTower(4, 15.5)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Has room? ${hasRoom()}")
println("Floor area: ${floorArea()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
println("Floor area: ${floorArea()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Has room? ${hasRoom()}")
println("Floor area: ${floorArea()}")
}
}
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
fun hasRoom(): Boolean {
return residents < capacity
}
abstract fun floorArea(): Double
}
class SquareCabin(residents: Int,
val length: Double) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
override fun floorArea(): Double {
return length * length
}
}
open class RoundHut(residents: Int,
val radius: Double) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
override fun floorArea(): Double {
return PI * radius * radius
}
}
class RoundTower(residents: Int, radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
override val buildingMaterial = "Stone"
override val capacity = 4 * floors
override fun floorArea(): Double {
return super.floorArea() * floors
}
}
输出应如下所示:
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false
Floor area: 2500.0
Round Hut
=========
Material: Straw
Capacity: 4
Has room? true
Floor area: 314.1592653589793
Round Tower
==========
Material: Stone
Capacity: 8
Has room? true
Floor area: 1509.5352700498956
注意: 对于面积值,如果仅显示 2 位小数,用户体验会更好。这不属于此文章的讨论范围,但您可以使用以下语句输出建筑面积:
println("Floor area: %.2f".format(floorArea()))
允许新住客获得房间
使用 getRoom()
函数,使新住客能够获得房间,该函数会使住客人数以 1 为增量递增。由于此逻辑适用于所有住房,您可以在 Dwelling
中实现该函数,这样它就能够用于所有子类及其子级。太棒了!
注意:
if
语句,以便仅在未达到可容纳人数上限时添加住客。residents++
作为 residents = residents + 1
的简写形式,用于向 residents
变量加 1。Dwelling
类中实现 getRoom()
函数。fun getRoom() {
if (capacity > residents) {
residents++
println("You got a room!")
} else {
println("Sorry, at capacity and no rooms left.")
}
}
提示: 在上面的代码块中,您可以替换
if
条件以使用hasRoom()
函数 -if(hasRoom()) {...}
。上面定义的hasRoom()
函数会将可容纳人数与住客人数进行比较。
roundHut
的 with
语句块添加一些输出语句,以同时使用 getRoom()
和 hasRoom()
观察变化。println("Has room? ${hasRoom()}")
getRoom()
println("Has room? ${hasRoom()}")
getRoom()
这些输出语句的输出如下所示:
Has room? true
You got a room!
Has room? false
Sorry, at capacity and no rooms left.
如需了解详情,请参阅解决方案代码。
注意: 这是一个示例,旨在说明继承功能为什么如此强大。您可以在
Dwelling
的所有子类上调用getRoom()
函数,而无需在这些类中添加额外的代码。
为圆形住房准备合适的地毯
假设您需要知道要为自己的 RoundHut
或 RoundTower
准备多大边长的地毯。将相应函数放到 RoundHut
中,使其可用于所有圆形住房。
kotlin.math
库导入 sqrt()
函数。import kotlin.math.sqrt
RoundHut
类中实现 calculateMaxCarpetLength()
函数。用于计算适合放入圆形中的方形地毯长度的公式为 sqrt(2) * radius
。上图对此进行了说明。fun calculateMaxCarpetLength(): Double {
return sqrt(2.0) * radius
}
将 Double
值 2.0
传入数学函数 sqrt(2.0)
,因为该函数的返回值类型是 Double
,而不是 Integer
。
calculateMaxCarpetLength()
方法可以在 RoundHut
和 RoundTower
实例上调用了。在 main()
函数中,为 roundHut
和 roundTower
添加输出语句。println("Carpet Length: ${calculateMaxCarpetLength()}")
如需了解详情,请参阅解决方案代码。
注意: 您已将
calculateMaxCarpetLength()
添加到RoundHut
,RoundTower
会继承该函数。不过,您不能在squareCabin
实例上调用此函数,因为 squareCabin 并未定义此函数,也没有定义过此函数的父类。
恭喜!您已经创建了一个包含属性和函数的完整类层次结构,并学习了创建更多实用类所需的所有知识!
以下是此 文章的完整解决方案代码,包括注释。
/**
* 为不同类型的住宅实现类的程序。
*演示实现:
* 创建具有继承性的类层次结构、变量和函数,
* 抽象类、重写和私有变量与公共变量。
*/
import kotlin.math.PI
import kotlin.math.sqrt
fun main() {
val squareCabin = SquareCabin(6, 50.0)
val roundHut = RoundHut(3, 10.0)
val roundTower = RoundTower(4, 15.5)
with(squareCabin) {
println("\nSquare Cabin\n============")
println("Capacity: ${capacity}")
println("Material: ${buildingMaterial}")
println("Floor area: ${floorArea()}")
}
with(roundHut) {
println("\nRound Hut\n=========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Floor area: ${floorArea()}")
println("Has room? ${hasRoom()}")
getRoom()
println("Has room? ${hasRoom()}")
getRoom()
println("Carpet size: ${calculateMaxCarpetLength()}")
}
with(roundTower) {
println("\nRound Tower\n==========")
println("Material: ${buildingMaterial}")
println("Capacity: ${capacity}")
println("Floor area: ${floorArea()}")
println("Carpet Length: ${calculateMaxCarpetLength()}")
}
}
/**
* 定义所有住宅共有的属性。
* 所有的住宅都有楼面面积,
* 但它的计算方式是特定于子类的。
* 在这里实现检查和获得一个房间
* 因为它们对于所有的住宅子类都是一样的。
*
* @param residents 目前居民的数量
*/
abstract class Dwelling(private var residents: Int) {
abstract val buildingMaterial: String
abstract val capacity: Int
/**
* 计算住宅的建筑面积。
* 由确定子类的形状实现。
*
* @return 楼层面积
*/
abstract fun floorArea(): Double
/**
* 检查是否有其他居民的空间。
*
* @return true 如果还有房间的话, false 如果没有房间的话
*/
fun hasRoom(): Boolean {
return residents < capacity
}
/**
* 将容量与居民人数进行比较
* 如果容量大于居民人数
* 通过增加居民人数来增加居民。
* 打印结果
*/
fun getRoom() {
if (capacity > residents) {
residents++
println("You got a room!")
} else {
println("Sorry, at capacity and no rooms left.")
}
}
}
/**
* 一个方形的小屋住宅。
*
* @param residents 目前居民人数
* @param length 长度
*/
class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {
override val buildingMaterial = "Wood"
override val capacity = 6
/**
* 计算方形住宅的面积。
*
* @return 楼层面积
*/
override fun floorArea(): Double {
return length * length
}
}
/**
* 圆楼面面积住宅
*
* @param residents 目前居民的数量
* @param radius 半径
*/
open class RoundHut(
residents: Int, val radius: Double) : Dwelling(residents) {
override val buildingMaterial = "Straw"
override val capacity = 4
/**
* 计算圆形住宅的面积。
*
* @return 楼层面积
*/
override fun floorArea(): Double {
return PI * radius * radius
}
/**
* 计算方形地毯的最大长度
* 适合圆形的地板
*
* @return 方形地毯的长度
*/
fun calculateMaxCarpetLength(): Double {
return sqrt(2.0) * radius
}
}
/**
* 多层圆塔
* * @param residents 目前居民的数量
* @param radius 半径
* @param floors 楼层数
*/
class RoundTower(
residents: Int,
radius: Double,
val floors: Int = 2) : RoundHut(residents, radius) {
override val buildingMaterial = "Stone"
// 容量取决于层数。
override val capacity = floors * 4
/**
* 计算塔式住宅的总建筑面积
*多层面积和
*
* @return 楼面面积
*/
override fun floorArea(): Double {
return super.floorArea() * floors
}
}
在此文章中,您学习了如何执行以下操作:
创建一个类层次结构,这是一种包含类的树形结构,其中子级会继承父类的函数。子类继承的有属性和函数。
创建一个 abstract
类,在这种类中,部分函数会留给其子类来实现。因此,abstract
类无法被实例化。
创建 abstract
类的子类。
使用 override
关键字,在子类中替换属性和函数。
使用 super
关键字,引用父类中的函数和属性。
将一个类标记为 open
,使其能够被子类化。
将一个属性标记为 private
,使其只能在相应类中使用。
使用 with
构造函数,在同一对象实例上进行多次调用。
从 kotlin.math
库导入函数