栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【Kotlin 中的类和继承】

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【Kotlin 中的类和继承】

上一篇:【向 Dice Roller 应用添加图片】

一、准备工作

前提条件

  • 熟悉如何使用 Kotlin Playground 修改 Kotlin 程序。
  • 了解本课程第 1 单元中(前几篇文章)介绍的 Kotlin 编程基本概念,尤其是 main()程序、能够返回值的带参数的函数、变量、数据类型和操作,以及 if/else 语句。
  • 能够在 Kotlin 中定义类、通过该类创建对象实例以及访问其属性和方法。

学习内容

  • 创建一个利用继承来实现类层次结构的 Kotlin 程序。
  • 扩展类、替换其现有函数并添加新函数。
  • 为变量选择正确的可见性修饰符。

构建内容

  • 一个 Kotlin 程序,包含以类层次结构形式实现的不同类型的住房。

所需条件

  • 一台能够连接互联网以访问 Kotlin Playground 的计算机
二、什么是类层次结构?

人们很自然地会将具有相似属性和行为的事物划分为组,甚至会在这些组之间建立某种类型的层次结构。例如,您可以有一个比较宽泛的类别(例如蔬菜),在该类别内,您可以设置更具体的类型(例如豆类)。在豆类中,您可以划分更加具体的类型,例如豌豆、黄豆、扁豆、鹰嘴豆和大豆。

这可以表示为层次结构,因为豆类包含或继承了蔬菜的所有属性(例如,它们属于植物,且可以食用)。同样地,豌豆、黄豆和扁豆均具有豆类的属性,同时也具有自己独特的属性。

我们来看看,如何用编程术语表示这种关系。如果您将 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 一样,也会是一个抽象类。它将包含很多类住房通用的属性和函数,但属性的确切值和函数的实现细节未知。

  1. 访问 Kotlin Playground,网址为:https://developer.android.com/training/kotlinplayground。

  2. 在编辑器中,删除 main() 函数中的 println("Hello, world!")。

  3. 然后,在 main() 函数下方添加以下代码,以创建一个名为 Dwelling 的 abstract 类。

abstract class Dwelling(){
}

添加有关建筑材料的属性

在此 Dwelling 类中,您可以定义适用于所有住房的属性,即使对于不同住房来说,这些属性的值可能会有所不同。所有住房都是由某种建筑材料建造而成的。

  1. 在 Dwelling 中,创建一个类型为 String 的 buildingMaterial 变量来表示建筑材料。由于建筑材料不会变化,可以使用 val 使其成为不可变变量。
val buildingMaterial: String
  1. 运行程序,系统会显示以下错误消息。
Property must be initialized or be abstract

buildingMaterial 属性没有值。实际上,您不能为其提供值,因为非特定建筑并非由任何特定建筑材料建造而成。因此,如错误消息所示,您可以添加 abstract 关键字作为 buildingMaterial 声明的前缀,以表明该属性不会在此处进行定义。

  1. 将 abstract 关键字添加到变量定义中。
abstract val buildingMaterial: String
  1. 运行您的代码,虽然它不会执行任何操作,但现在能顺利编译,不会出现任何错误。
  2. 在 main() 函数中构建一个 Dwelling 实例,然后运行代码。
val dwelling = Dwelling()
  1. 您将收到一条错误消息,因为您无法创建 Dwelling 抽象类的实例。
Cannot create an instance of an abstract class
  1. 删除此错误代码。

到目前为止,您已完成的代码如下所示:

abstract class Dwelling(){
    abstract val buildingMaterial: String
}

添加有关可容纳人数的属性。

住房的另一个属性是可容纳人数,即屋内可以住多少人。

所有住房的可容纳人数都不会变。但是,不能在 Dwelling 父类中设置可容纳人数,而应在特定类型住房对应的子类中进行定义。

  1. 在 Dwelling 中,添加一个名为 capacity 的 abstract 整数 val。
abstract val capacity: Int

添加有关住客人数的私有属性

所有住房都有一定数量的 residents(即住在该住房里的人数,该值可能小于或等于 capacity),因此应在 Dwelling 父类中定义 residents 属性,以便所有子类继承和使用。

  1. 您可以将 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。

  1. 将 hasRoom() 函数添加到 Dwelling 类。
fun hasRoom(): Boolean {
    return residents < capacity
}
  1. 您可以运行此代码,系统应该不会显示错误。该代码尚未执行任何可见操作。

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

abstract class Dwelling(private var residents: Int) {

   abstract val buildingMaterial: String
   abstract val capacity: Int

   fun hasRoom(): Boolean {
       return residents < capacity
   }
}
四、创建子类

创建 SquareCabin 子类

  1. 在 Dwelling 类下,创建一个名为 SquareCabin 的类。
class SquareCabin
  1. 接下来,您需要指明 SquareCabin 与 Dwelling 相关。在您的代码中,您需要表明 SquareCabin 扩展自 Dwelling(或者是 Dwelling) 的子类),因为 SquareCabin 将会提供 Dwelling 抽象部分的实现。

通过在 SquareCabin 类名称后添加一个冒号 (:),后跟用于初始化父级 Dwelling 类的调用,表明此继承关系。不要忘记在 Dwelling 类名称后添加圆括号。

class SquareCabin : Dwelling()
  1. 如果是从父类扩展,您必须传入父类所需的参数。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。在之后的 文章中,您将详细了解构造函数。

  1. 运行您的代码。
  2. 这会导致出现错误。我们来看一下:
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 关键字来表明此属性是在父类中定义的,并且在此类中将被替换掉

  1. 在 SquareCabin 类中,override buildingMaterial 属性并为其分配值 "Wood"。
  2. 针对 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

  1. 在 Dwelling 和 SquareCabin 类定义前插入一个空 main() 函数。
fun main() {

}

abstract class Dwelling(private var residents: Int) {
    ...
}

class SquareCabin(residents: Int) : Dwelling(residents) {
    ...
}
  1. 在 main() 函数中,创建一个名为 squareCabin 且住客人数为 6 的 SquareCabin 实例。针对建筑材料、可容纳人数和 hasRoom() 函数添加输出语句。
fun main() {
    val squareCabin = SquareCabin(6)

    println("nSquare Cabinn============")
    println("Capacity: ${squareCabin.capacity}")
    println("Material: ${squareCabin.buildingMaterial}")
    println("Has room? ${squareCabin.hasRoom()}")
}

请注意,hasRoom() 函数不是在 SquareCabin 类中定义的,而是在 Dwelling 类中定义的。由于 SquareCabin 是 Dwelling 类的子类,hasRoom() 函数是未经任务操作直接继承而来的。现在,可以在 SquareCabin 的所有实例上调用 hasRoom() 函数,如代码段 squareCabin.hasRoom() 所示。

  1. 运行代码,输出内容应如下所示:
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
}
  1. 在 main() 函数中,将您的输出语句改为使用 with。
  2. 删除输出语句中的“squareCabin.”。
with(squareCabin) {
    println("nSquare Cabinn============")
    println("Capacity: ${capacity}")
    println("Material: ${buildingMaterial}")
    println("Has room? ${hasRoom()}")
}
  1. 再次运行代码,以确保它能够正常运行而不会出现任何错误,并输出相同的结果。
Square Cabin
============
Capacity: 6
Material: Wood
Has room? false

完成的代码如下所示:

fun main() {
    val squareCabin = SquareCabin(6)

    with(squareCabin) {
        println("nSquare Cabinn============")
        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 子类

  1. 按照与添加 SquareCabin 相同的方式,为 Dwelling 添加另一个子类 RoundHut。
  2. 替换 buildingMaterial 并为其提供值 “Straw”。
    替换 capacity 并将其设置为 4。
class RoundHut(residents: Int) : Dwelling(residents) {
    override val buildingMaterial = "Straw"
    override val capacity = 4
}
  1. 在 main() 中,创建一个住客人数为 3 的 RoundHut 实例。
val roundHut = RoundHut(3)
  1. 添加以下代码以输出 roundHut 的相关信息。
with(roundHut) {
    println("nRound Hutn=========")
    println("Material: ${buildingMaterial}")
    println("Capacity: ${capacity}")
    println("Has room? ${hasRoom()}")
}
  1. 运行代码,整个程序的输出应如下所示:
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 的子类。

  1. 创建一个 RoundTower 类,它是 RoundHut 的子类。将 residents 参数添加到 RoundTower 的构造函数中,然后将该参数传递给 RoundHut 父类的构造函数。
  2. 将 buildingMaterial 替换为 "Stone"。
  3. 将 capacity 设置为 4。
class RoundTower(residents: Int) : RoundHut(residents) {
    override val buildingMaterial = "Stone"
    override val capacity = 4
}
  1. 运行此代码,您会收到一条错误消息。
This type is final, so it cannot be inherited from

此错误意味着,RoundHut 类不能被子类化(或被继承)。默认情况下,在 Kotlin 中,类是最终层级,无法被子类化。您只能从 abstract 类或标记有 open 关键字的类继承。因此,您需要使用 open 关键字标记 RoundHut 类,使其能够被继承。

注意: 定义抽象类时,您无需使用 open 关键字。例如,无需使用 open 关键字标记 Dwelling 类。系统已假设您需要子类化抽象类,以实现抽象属性和函数。

  1. 在 RoundHut 声明的开头添加 open 关键字。
open class RoundHut(residents: Int) : Dwelling(residents) {
   override val buildingMaterial = "Straw"
   override val capacity = 4
}
  1. 在 main() 中,创建一个 roundTower 实例并输出其相关信息。
 val roundTower = RoundTower(4)
with(roundTower) {
    println("nRound Towern==========")
    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 Cabinn============")
        println("Capacity: ${capacity}")
        println("Material: ${buildingMaterial}")
        println("Has room? ${hasRoom()}")
    }

    with(roundHut) {
        println("nRound Hutn=========")
        println("Material: ${buildingMaterial}")
        println("Capacity: ${capacity}")
        println("Has room? ${hasRoom()}")
    }

    with(roundTower) {
        println("nRound Towern==========")
        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
}
  1. 运行您的代码。它现在应该能够正常运行而不会出现任何错误,并且会生成以下输出。
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 修改为多楼层,并根据楼层数量调整可容纳人数。

  1. 更新 RoundTower 构造函数,以接受另一个整数参数 val floors 作为楼层数量。将其置于 residents 之后。请注意,您无需将此参数传递给父级 RoundHut 构造函数,因为 floors 是在 RoundTower 中定义的,而 RoundHut 没有 floors。
class RoundTower(
    residents: Int,
    val floors: Int) : RoundHut(residents) {

    ...
}

注意: 如果您有多个参数,而且类或函数定义太长,一行无法完全显示,您可以将其划分为多行,如上面的代码所示。

  1. 运行您的代码。在 main() 方法中创建 roundTower 时出现错误,因为您没有为 floors 参数提供数字。您可以添加缺少的参数。

或者,您也可以在 RoundTower 的类定义中为 floors 添加默认值,如下所示。然后,在没有 floors 值传递到构造函数的情况下,系统就可以使用默认值创建对象实例。

  1. 在您的代码中,在 floors 声明后添加 = 2,以为其分配默认值 2。
class RoundTower(
    residents: Int,
    val floors: Int = 2) : RoundHut(residents) {

    ...
}
  1. 运行您的代码。它应该能顺利编译,因为 RoundTower(4) 现在会使用 2 层楼的默认值创建 RoundTower 对象实例。
  2. 在 RoundTower 类中,更新 capacity,用之前的值乘以楼层数。
override val capacity = 4 * floors
  1. 运行代码,请注意,RoundTower 的可容纳人数现在为 8 人(2 层)。

下面是完成后的代码。

fun main() {

    val squareCabin = SquareCabin(6)
    val roundHut = RoundHut(3)
    val roundTower = RoundTower(4)

    with(squareCabin) {
        println("nSquare Cabinn============")
        println("Capacity: ${capacity}")
        println("Material: ${buildingMaterial}")
        println("Has room? ${hasRoom()}")
    }

    with(roundHut) {
        println("nRound Hutn=========")
        println("Material: ${buildingMaterial}")
        println("Capacity: ${capacity}")
        println("Has room? ${hasRoom()}")
    }

    with(roundTower) {
        println("nRound Towern==========")
        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()

  1. 首先,向 Dwelling 类中添加一个 abstract floorArea() 函数。返回 Double。Double 与 String 和 Int 一样,是一种数据类型;它可用于浮点数(即带有小数点且后跟小数部分的数字,例如 5.8793)。
abstract fun floorArea(): Double

抽象类中定义的所有抽象方法都必须在其任意子类中实现。您需要先在子类中实现 floorArea(),然后才能运行代码。

针对 SquareCabin 实现 floorArea()

与实现 buildingMaterial 和 capacity 一样,由于您要实现的 abstract 函数是在父类中定义的,您需要使用 override 关键字。

  1. 在 SquareCabin 类中,首先输入关键字 override,后跟 floorArea() 函数的实际实现,如下所示。
override fun floorArea(): Double {

}
  1. 返回计算出的建筑面积。矩形或方形的面积计算方法为,一边的长度乘以另一边的长度。函数主体为 return length * length。
override fun floorArea(): Double {
    return length * length
}

在此类中,长度是一个变量,每个实例的长度各不相同,因此您可以将其添加为 SquareCabin 类的构造函数参数。

  1. 更改 SquareCabin 的类定义,以添加 Double 类型的 length 参数。将该属性声明为 val,因为建筑的长度不会变化。
class SquareCabin(residents: Int, val length: Double) : Dwelling(residents) {

Dwelling 具有构造函数参数 residents,因此其所有子类都具有该参数。由于这是 Dwelling 构造函数中的第一个参数,所以最好也将其作为所有子类构造函数中的第一个参数,并在所有类定义中以相同的顺序排列参数。因此,在 residents 参数后插入新的 length 参数。

  1. 在 main() 中,更新创建 squareCabin 实例的代码。将 50.0 作为 length 传入 SquareCabin 构造函数。
val squareCabin = SquareCabin(6, 50.0)
  1. 在 squareCabin 的 with 语句中,添加针对建筑面积的输出语句。
println("Floor area: ${floorArea()}")

您的代码将不会运行,因为您还需要在 RoundHut 中实现 floorArea()。

针对 RoundHut 实现 floorArea()

以同样的方式针对 RoundHut 实现建筑面积。RoundHut 也是 Dwelling 的直接子类,因此您需要使用 override 关键字。

圆形住房建筑面积的计算方法为,PI * 半径 * 半径。

PI 是一个数学值。它会在数学库中进行定义。库是一个预定义的函数和值集合,在程序外部定义,但可供程序使用。为了使用某个库函数或值,您需要告知编译器您将使用该函数或值。方法是将该函数或值导入到程序中。如需在程序中使用 PI,您需要导入 kotlin.math.PI。

  1. 从 Kotlin 数学库导入 PI。将此导入语句放在文件的顶部,main() 之前。
import kotlin.math.PI
  1. 针对 RoundHut 实现 floorArea() 函数。
override fun floorArea(): Double {
    return PI * radius * radius
}

警告: 如果您不导入 kotlin.math.PI,将会遇到错误,因此请在使用之前导入此库。或者,您可以写出 PI 的完全限定版本,如“kotlin.math.PI * 半径 * 半径”中所示,然后便无需添加导入语句。

  1. 更新 RoundHut 构造函数以传入 radius。
open class RoundHut(
   residents: Int,
   val radius: Double) : Dwelling(residents) {
  1. 在 main() 中,通过向 RoundHut 构造函数传入 radius 10.0,更新 roundHut 的初始化。
val roundHut = RoundHut(3, 10.0)
  1. 在 roundHut 的 with 语句中添加输出语句。
println("Floor area: ${floorArea()}")

针对 RoundTower 实现 floorArea()

您的代码尚无法正常运行,它会失败并显示以下错误消息:

Error: No value passed for parameter 'radius'

在 RoundTower 中,为了使程序能够顺利编译,您不需要实现 floorArea(),因为它能够从 RoundHut 继承,但您需要更新 RoundTower 类定义,使其具有与其父级 RoundHut 相同的 radius 参数。

  1. 更改 RoundTower 构造函数,使其同样接受 radius。将 radius 放在 residents 之后、floors 之前。建议将具有默认值的变量列在末尾。请记得将 radius 传递给父类构造函数。
class RoundTower(
    residents: Int,
    radius: Double,
    val floors: Int = 2) : RoundHut(residents, radius) {
  1. 在 main() 中更新初始化 roundTower 的代码。
val roundTower = RoundTower(4, 15.5)
  1. 添加一个调用 floorArea() 的输出语句。
println("Floor area: ${floorArea()}")
  1. 现在,您可以运行代码了!
  2. 请注意,RoundTower 的计算方法不正确,因为它继承自 RoundHut,并未考虑 floors 数量。
  3. 在 RoundTower 中,override floorArea() 以便为其提供不同的实现,将面积乘以楼层数量。注意,您可以在抽象类 (Dwelling) 中定义一个函数,在子类 (RoundHut) 中实现它,然后在子类的子类 (RoundTower) 中再次替换它。这是一种两全其美的做法,您既可以继承自己需要的函数,又能替换掉自己不需要的函数。
override fun floorArea(): Double {
    return PI * radius * radius * floors
}

此代码能够正常运行,不过有一种方法可以避免反复使用父类 RoundHut 中已有的代码。您可以从父类 RoundHut 中调用 floorArea() 函数,后者会返回 PI * radius * radius。然后将该结果乘以 floors 数。

  1. 在 RoundTower 中,将 floorArea() 更新为使用其父类中的 floorArea() 实现。使用 super 关键字,调用父级中定义的函数。
override fun floorArea(): Double {
    return super.floorArea() * floors
}
  1. 再次运行您的代码,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 Cabinn============")
        println("Capacity: ${capacity}")
        println("Material: ${buildingMaterial}")
        println("Has room? ${hasRoom()}")
        println("Floor area: ${floorArea()}")
    }

    with(roundHut) {
        println("nRound Hutn=========")
        println("Material: ${buildingMaterial}")
        println("Capacity: ${capacity}")
        println("Has room? ${hasRoom()}")
        println("Floor area: ${floorArea()}")
    }

    with(roundTower) {
        println("nRound Towern==========")
        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。
  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() 函数会将可容纳人数与住客人数进行比较。

  1. 向 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 中,使其可用于所有圆形住房。

  1. 首先,从 kotlin.math 库导入 sqrt() 函数。
import kotlin.math.sqrt
  1. 在 RoundHut 类中实现 calculateMaxCarpetLength() 函数。用于计算适合放入圆形中的方形地毯长度的公式为 sqrt(2) * radius。上图对此进行了说明。
fun calculateMaxCarpetLength(): Double {

    return sqrt(2.0) * radius
}

将 Double 值 2.0 传入数学函数 sqrt(2.0),因为该函数的返回值类型是 Double,而不是 Integer。

  1. 现在,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 Cabinn============")
       println("Capacity: ${capacity}")
       println("Material: ${buildingMaterial}")
       println("Floor area: ${floorArea()}")
   }

   with(roundHut) {
       println("nRound Hutn=========")
       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 Towern==========")
       println("Material: ${buildingMaterial}")
       println("Capacity: ${capacity}")
       println("Floor area: ${floorArea()}")
       println("Carpet Length: ${calculateMaxCarpetLength()}")
   }
}


abstract class Dwelling(private var residents: Int) {
   abstract val buildingMaterial: String
   abstract val capacity: Int

   
   abstract fun floorArea(): Double

   
   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.")
       }
   }

   }


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
   }

   
    fun calculateMaxCarpetLength(): Double {
        return sqrt(2.0) * radius
    }

}


class RoundTower(
       residents: Int,
       radius: Double,
       val floors: Int = 2) : RoundHut(residents, radius) {

   override val buildingMaterial = "Stone"

   // 容量取决于层数。
   override val capacity = floors * 4

   
   override fun floorArea(): Double {
       return super.floorArea() * floors
   }
}
七、总结

在此文章中,您学习了如何执行以下操作:

  • 创建一个类层次结构,这是一种包含类的树形结构,其中子级会继承父类的函数。子类继承的有属性和函数。

  • 创建一个 abstract 类,在这种类中,部分函数会留给其子类来实现。因此,abstract 类无法被实例化。

  • 创建 abstract 类的子类。

  • 使用 override 关键字,在子类中替换属性和函数。

  • 使用 super 关键字,引用父类中的函数和属性。

  • 将一个类标记为 open,使其能够被子类化。

  • 将一个属性标记为 private,使其只能在相应类中使用。

  • 使用 with 构造函数,在同一对象实例上进行多次调用。

  • 从 kotlin.math 库导入函数

八、了解更多内容
  • 继承
  • 类和继承
  • 构造函数
  • 继承(抽象、公开、替换、私有)
  • with(正式定义)
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1031740.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号