子类型描述了类型关系,而子类型多态性使为超类型定义的操作得以实现 安全地 用亚型替换。具体来说,想象一下“猫”类和“动物”类之间的关系。(记住:类在JS++中创建数据类型。)在这种情况下,在类型关系的上下文中,“Cat”是“Animal”的子类型,“Animal”是“Cat”的超类型。简单地说,“猫”是“动物”,但“动物”是 不 “猫”。理论上,这意味着所有适用于“动物”数据类型的操作都应该接受对“猫”类型数据的操作;然而,情况并非如此:为数据类型“Cat”定义的操作将无法安全地对类型“Animal”的数据进行操作。
如果你还记得上一节的代码,猫和狗是有名字的家养动物。然而,并不是所有的动物都应该被命名,所以我们的“Animal”类没有使用name参数。因此,虽然我们可以在“Cat”实例上定义并调用“name”getter,但我们不能 安全地 将“Cat”实例替换为“Animal”实例。在JS++中,如果你尝试这样做,你会得到一个错误。
子类型多态性允许我们以更抽象的方式编写代码。例如,在基本类型的上下文中,“字节”表示0到255之间的数字。同时,“int”代表更大范围内的数字:-2147483648到2147483647。因此,我们可以将“字节”类型的数字替换为“int”类型的数字:
int add(int a, int b) { return a + b; } byte a = 1; byte b = 1; add(a, b);
因此,我们能够更“一般”地表达算法和函数,因为我们可以接受任何给定算法或函数的更广泛种类的数据(按数据类型分类)。
重要的是不要混淆子类型和继承,即使这些概念在面向对象编程中密切相关。子类型描述类型关系,而继承则与实现有关(例如用派生类扩展基类的实现)。子类型可以应用于接口,而“继承”不能。在后面的章节中,当我们讨论接口时,这个概念将变得更加清晰。
作为实际理解子类型的起点,我们可以改变main。jspp使 数据类型 所有变量中的一个都变成了“动物”,但我们将类的实例化保留为子类型:
import Animals; Animal cat1 = new Cat("Kitty"); cat1.render(); Animal cat2 = new Cat("Kat"); cat2.render(); Animal dog = new Dog("Fido"); dog.render(); Animal panda = new Panda(); panda.render(); Animal rhino = new Rhino(); rhino.render();
编译你的代码。它应该能够成功编译,因为在实例化过程中,作为“Animal”子类型的所有数据都可以分配给“Animal”类型的变量。在这种情况下,“猫”、“狗”、“熊猫”和“犀牛”数据都可以安全地分配给“动物”变量。
然而,有一个小“抓到你了。”开放索引。html。您将再次看到所有动物的渲染,但是,如果将鼠标悬停在任何动物图标上,您将看不到任何名称!为了理解为什么会发生这种情况,我们必须理解静态多态性和动态多态性,这将在下一节中解释。(简而言之,我们实际上通过在“render”方法上使用“overwrite”关键字指定了我们想要的“静态”或编译时多态性。因此,我们得到了指定的行为。)