在本章中,我们将学习 Java9 中面向对象编程最令人兴奋的特性之一:多态性。我们将编写许多类的代码,然后在 JShell 中使用它们的实例,以了解对象如何具有多种不同的形式。我们将:
- 创建从抽象超类继承的具体类
- 使用子类的实例
- 理解多态性
- 控制子类是否可以覆盖成员
- 控制类是否可以子类化
- 使用对不同子类的实例执行操作的方法
在上一章的中,我们创建了一个名为VirtualAnimal的抽象基类,然后编码了以下三个抽象子类:VirtualMammal、VirtualDomesticMammal和VirtualHorse。现在,我们将编写以下三个具体类。每个类代表不同的马品种,是VirtualHorse抽象类的一个子类。
AmericanQuarterHorse:该类代表一匹属于美国四分之一马品种的虚拟马。ShireHorse:该类表示属于夏尔马品种的虚拟马。Thoroughbred:该类代表纯种品种的虚拟马。
三个具体类将实现从抽象超类继承的以下三个抽象方法:
String getAsciiArt():此抽象方法继承自VirtualAnimal抽象类。String getBaby():此抽象方法继承自VirtualAnimal抽象类。String getBreed():此抽象方法继承自VirtualHorse抽象类。
下面的 UML 图显示了我们将编码的三个具体类的成员:AmericanQuarterHorse、ShireHorse和Thoroughbred。我们不为每个具体类将声明的三个方法使用粗体文本格式,因为它们没有覆盖这些方法;他们正在实现类继承的抽象方法。
首先,我们将创建AmericanQuarterHorse具体类。以下几行显示了 Java 9 中该类的代码。请注意,class之前没有abstract关键字,因此,我们的类必须确保它实现了所有继承的抽象方法。样本的代码文件包含在example07_01.java文件的java_9_oop_chapter_07_01文件夹中。
public class AmericanQuarterHorse extends VirtualHorse {
public AmericanQuarterHorse(
int age,
boolean isPregnant,
String name,
String favoriteToy) {
super(age, isPregnant, name, favoriteToy);
System.out.println("AmericanQuarterHorse created.");
}
public AmericanQuarterHorse(
int age, String name, String favoriteToy) {
this(age, false, name, favoriteToy);
}
public String getBaby() {
return "AQH baby ";
}
public String getBreed() {
return "American Quarter Horse";
}
public String getAsciiArt() {
return
" >>\\.\n" +
" /* )`.\n" +
" // _)`^)`. _.---. _\n" +
" (_,' \\ `^-)'' `.\\\n" +
" | | \\\n" +
" \\ / |\n" +
" / \\ /.___.'\\ (\\ (_\n" +
" < ,'|| \\ |`. \\`-'\n" +
" \\\\ () )| )/\n" +
" |_>|> /_] //\n" +
" /_] /_]\n";
}
}现在我们将创建ShireHorse具体类。以下几行显示了 Java 9 中该类的代码。样本的代码文件包含在java_9_oop_chapter_07_01文件夹中的example07_01.java文件中。
public class ShireHorse extends VirtualHorse {
public ShireHorse(
int age,
boolean isPregnant,
String name,
String favoriteToy) {
super(age, isPregnant, name, favoriteToy);
System.out.println("ShireHorse created.");
}
public ShireHorse(
int age, String name, String favoriteToy) {
this(age, false, name, favoriteToy);
}
public String getBaby() {
return "ShireHorse baby ";
}
public String getBreed() {
return "Shire Horse";
}
public String getAsciiArt() {
return
" ;;\n" +
" .;;'*\\\n" +
" __ .;;' ' \\\n" +
" /' '\\.~~.~' \\ /'\\.)\n" +
" ,;( ) / |\n" +
" ,;' \\ /-.,,( )\n" +
" ) /| ) /|\n" +
" ||(_\\ ||(_\\\n" +
" (_\\ (_\\\n";
}
}最后,我们将创建Thoroughbred具体类。以下几行显示了 Java 9 中这个类的代码。样本的代码文件包含在java_9_oop_chapter_07_01文件夹中的example07_01.java文件中。
public class Thoroughbred extends VirtualHorse {
public Thoroughbred(
int age,
boolean isPregnant,
String name,
String favoriteToy) {
super(age, isPregnant, name, favoriteToy);
System.out.println("Thoroughbred created.");
}
public Thoroughbred(
int age, String name, String favoriteToy) {
this(age, false, name, favoriteToy);
}
public String getBaby() {
return "Thoroughbred baby ";
}
public String getBreed() {
return "Thoroughbred";
}
public String getAsciiArt() {
return
" })\\-=--.\n" +
" // *._.-'\n" +
" _.-=-...-' /\n" +
" {{| , |\n" +
" {{\\ | \\ /_\n" +
" }} \\ ,'---'\\___\\\n" +
" / )/\\\\ \\\\ >\\\n" +
" // >\\ >\\`-\n" +
" `- `- `-\n";
}
}正如我们编写的其他子类一样,我们为三个具体类定义了多个构造函数。第一个需要四个参数的构造函数使用super关键字从基类或超类调用构造函数,即在VirtualHorse类中定义的构造函数。在超类中定义的构造函数完成其执行后,代码将打印一条消息,指示已创建每个特定具体类的实例。每个类中定义的构造函数打印不同的消息。
第二个构造函数使用this关键字调用前面解释过的构造函数,并使用收到的参数和false作为isPregnant参数的值。
在getBaby和getBreed方法的实现中,每个类返回不同的String。此外,在getAsciiArt方法的实现中,每个类为虚拟马返回不同的 ASCII 艺术表示。
我们可以使用相同的方法,即具有相同名称和参数的方法,根据调用该方法的类导致不同的事情发生。在面向对象编程中,此功能称为多态性。多态性是一个对象具有多种形式的能力,我们将通过使用先前编码的具体类的实例来了解它的实际作用。
以下几行创建名为american的AmericanQuarterHorse类的新实例,并使用其不需要isPregnant参数的构造函数之一。样本的代码文件包含在example07_01.java文件的java_9_oop_chapter_07_01文件夹中。
AmericanQuarterHorse american =
new AmericanQuarterHorse(
8, "American", "Equi-Spirit Ball");
american.printBreed();以下几行显示了在输入前面的代码后,不同构造函数在 JShell 中显示的消息:
VirtualAnimal created.
VirtualMammal created.
VirtualDomesticMammal created.
VirtualHorse created.
AmericanQuarterHorse created.AmericanQuarterHorse中定义的构造函数从其超类(即VirtualHorse类)调用构造函数。请记住,每个构造函数调用其超类构造函数并打印一条消息,指示已创建类的实例。我们没有五个不同的例子;我们只有一个实例,它调用五个不同类的链式构造函数来执行所有必要的初始化,以创建一个AmericanQuarterHorse实例。
如果我们在 JShell 中执行下面的行,结果都会显示true,因为american属于VirtualAnimal、VirtualMammal、VirtualDomesticMammal、VirtualHorse和AmericanQuarterHorse类。样本的代码文件包含在java_9_oop_chapter_07_01文件夹中的example07_01.java文件中。
System.out.println(american instanceof VirtualAnimal);
System.out.println(american instanceof VirtualMammal);
System.out.println(american instanceof VirtualDomesticMammal);
System.out.println(american instanceof VirtualHorse);
System.out.println(american instanceof AmericanQuarterHorse);前面几行的结果意味着AmericanQuarterHorse类的实例(其引用保存在AmericanQuarterHorse类型的american变量中)可以采用以下任何类的实例的形式:
VirtualAnimalVirtualMammalVirtualDomesticMammalVirtualHorseAmericanQuarterHorse
以下截图显示了在 JShell 中执行前几行的结果:
我们在VirtualHorse类中对方法进行了编码,并且没有在任何子类中重写该方法。以下是printBreed方法的代码:
public void printBreed() {
System.out.println(getBreed());
}代码打印由getBreed方法返回的String,该方法与抽象方法在同一个类中声明。继承自VirtualHorse的三个具体类实现了getBreed方法,每个类返回不同的String。当我们调用american.printBreed方法时,JShell 显示American Quarter Horse。
以下几行创建了名为zelda的ShireHorse类的实例。注意,在本例中,我们使用需要isPregnant参数的构造函数。正如我们创建AmericanQuarterHorse类的实例时所发生的那样,JShell 将为每个构造函数显示一条消息,这些构造函数是我们编码的链式构造函数的结果。样本的代码文件包含在java_9_oop_chapter_07_01文件夹中的example07_01.java文件中。
ShireHorse zelda =
new ShireHorse(9, true,
"Zelda", "Tennis Ball");接下来的几行分别调用american的printAverageNumberOfBabies和printAsciiArt实例方法AmericanQuarterHorse的实例和zelda的实例方法ShireHorse的实例。样本的代码文件包含在example07_01.java文件的java_9_oop_chapter_07_01文件夹中。
american.printAverageNumberOfBabies();
american.printAsciiArt();
zelda.printAverageNumberOfBabies();
zelda.printAsciiArt();我们将printAverageNumberOfBabies和printAsciiArt方法编码在VirtualAnimal类中,并且我们没有在其任何子类中重写它们。因此,当我们为american或Zelda调用这些方法时,Java 将执行VirtualAnimal类中定义的代码。
printAverageNumberOfBabies方法使用getAverageNumberOfBabies返回的int值和getBaby方法返回的String值生成一个String,表示虚拟动物的平均婴儿数量。VirtualHorse类使用返回1的代码实现继承的getAverageNumberOfBabies抽象方法。AmericanQuarterHorse和ShireHorse类实现了继承的getBaby抽象方法,其代码返回一个String,表示虚拟马品种的婴儿:"AQH baby"和"ShireHorse baby"。因此,我们对printAverageNumberOfBabies方法的调用将在每个实例中产生不同的结果,因为它们属于不同的类。
printAsciiArt方法使用getAsciiArt方法返回的String打印代表虚拟马的 ASCII 艺术。AmericanQuarterHorse和ShireHorse类实现了继承的getAsciiArt抽象方法,其代码返回一个带有 ASCII 艺术的String,该艺术适合于类所代表的每个虚拟马。因此,我们对printAsciiArt方法的调用将在每个实例中产生不同的结果,因为它们属于不同的类。
下面的屏幕截图显示了在 JShell 中执行前几行的结果。这两个实例为VirtualAnimal抽象类中编码的两个方法运行相同的代码。但是,每个类为方法提供了不同的实现,这些方法最终被调用以生成结果并导致输出中的差异。
下面的行创建名为willow的Thoroughbred类实例,然后调用其printAsciiArt方法。正如前面所发生的那样,JShell 将为每个构造函数显示一条消息,这些构造函数是我们编码的链式构造函数的结果。样本的代码文件包含在example07_01.java文件的java_9_oop_chapter_07_01文件夹中。
Thoroughbred willow =
new Thoroughbred(5,
"Willow", "Jolly Ball");
willow.printAsciiArt();下面的屏幕截图显示了在 JShell 中执行前几行的结果。新实例来自一个类,该类提供了getAsciiArt方法的不同实现,因此,我们将看到不同的 ASCII 艺术,这与我们在前两次对其他实例的相同方法调用中看到的不同。
下面的行使用不同数量的参数调用名为willow的实例的neigh方法。这样,我们就利用了neigh方法,我们用不同的参数重载了四次。请记住,我们在VirtualHorse类中编码了四个neigh方法,Thoroughbred类通过其层次结构树从该超类继承重载方法。样本的代码文件包含在example07_01.java文件的java_9_oop_chapter_07_01文件夹中。
willow.neigh();
willow.neigh(2);
willow.neigh(2, american);
willow.neigh(3, zelda, true);
american.nicker();
american.nicker(2);
american.nicker(2, willow);
american.nicker(3, willow, true);下面的屏幕截图显示了在 JShell 中使用不同参数调用neigh和nicker方法的结果:
我们为名为willow的Thoroughbred实例调用了VirtualHorse类中定义的neigh方法的四个版本。调用neigh方法的第三行和第四行为VirtualDomesticMammal类型的otherDomesticMammal参数指定一个值。第三行指定american为otherDomesticMammal的值,第四行指定zelda为同一参数的值。AmericanQuarterHorse和ShireHorse混凝土类都是VirtualHorse的子类,VirtualHorse是VirtualDomesticMammal的子类。因此,我们可以使用american和zelda作为需要VirtualDomesticMammal实例的参数。
然后,我们为名为american的AmericanQuarterHorse实例调用了VirtualHorse类中定义的nicker方法的四个版本。调用nicker方法的第三行和第四行指定willow作为类型为VirtualDomesticMammal的otherDomesticMammal参数的值。Thoroughbred混凝土类也是VirtualHorse的一个子类,VirtualHorse是VirtualDomesticMammal的一个子类。因此,我们可以使用willow作为需要VirtualDomesticMammal实例的参数。
我们将对VirtualDomesticCat抽象类及其具体子类MaineCoon进行编码。然后,我们将对VirtualBird抽象类、其VirtualDomesticBird抽象子类和Cockatiel具体子类进行编码。最后,我们将对VirtualDomesticRabbit具体类进行编码。在编码这些类时,我们将使用 Java9 特性来决定子类是否可以覆盖特定的成员。
所有的虚拟家猫都必须能够说话,因此,我们将覆盖继承自VirtualDomesticMammal的talk方法来打印表示猫喵喵叫的单词:"Meow"。我们还想提供一种打印"Meow"特定次数的方法。因此,在这一点上,我们意识到我们可以利用我们在VirtualHorse类中声明的printSoundInWords方法。
我们无法在VirtualDomesticCat抽象类中访问此实例方法,因为它不是从VirtualHorse继承的。因此,我们将把这个方法从VirtualHorse类移到它的超类:VirtualDomesticMammal。
对于不希望在子类中重写的方法,我们将在返回类型之前使用final关键字。当一个方法被标记为 final 方法时,子类无法重写该方法,如果它们试图重写,Java9 编译器将显示错误。
并不是所有的鸟都能在现实生活中飞翔。然而,我们所有的虚拟鸟都能飞,因此,我们将实现继承的isAbleToFly抽象方法作为返回true的最终方法。通过这种方式,我们确保继承自VirtualBird抽象类的所有类都将始终为isAbleToFly方法运行此代码,并且它们将无法重写它。
下面的 UML 图显示了我们将要编码的新抽象和具体类的成员。另外,图中显示了printSoundInWords方法从VirtualHorse抽象类移动到VirtualDomesticMammal抽象类。
首先,我们将创建一个新版本的VirtualDomesticMammal抽象类。我们将在VirtualHorse抽象类中添加printSoundInWords方法,并使用final关键字表示我们不想让子类重写此方法。以下几行显示了VirtualDomesticMammal类的新代码。样本的代码文件包含在example07_02.java文件的java_9_oop_chapter_07_01文件夹中。
public abstract class VirtualDomesticMammal extends VirtualMammal {
public final String name;
public String favoriteToy;
public VirtualDomesticMammal(
int age,
boolean isPregnant,
String name,
String favoriteToy) {
super(age, isPregnant);
this.name = name;
this.favoriteToy = favoriteToy;
System.out.println("VirtualDomesticMammal created.");
}
public VirtualDomesticMammal(
int age, String name, String favoriteToy) {
this(age, false, name, favoriteToy);
}
protected final void printSoundInWords(
String soundInWords,
int times,
VirtualDomesticMammal otherDomesticMammal,
boolean isAngry) {
String message = String.format("%s%s: %s%s",
name,
otherDomesticMammal == null ?
"" : String.format(" to %s ", otherDomesticMammal.name),
isAngry ?
"Angry " : "",
new String(new char[times]).replace("\0", soundInWords));
System.out.println(message);
}
public void talk() {
System.out.println(
String.format("%s: says something", name));
}
}在我们输入前面的行后,JShell 将显示以下消息:
| update replaced class VirtualHorse which cannot be referenced until this error is corrected:
| printSoundInWords(java.lang.String,int,VirtualDomesticMammal,boolean) in VirtualHorse cannot override printSoundInWords(java.lang.String,int,VirtualDomesticMammal,boolean) in VirtualDomesticMammal
| overridden method is final
| protected void printSoundInWords(String soundInWords, int times,
| ^---------------------------------------------------------------...
| update replaced class AmericanQuarterHorse which cannot be referenced until class VirtualHorse is declared
| update replaced class ShireHorse which cannot be referenced until class VirtualHorse is declared
| update replaced class Thoroughbred which cannot be referenced until class VirtualHorse is declared
| update replaced variable american which cannot be referenced until class AmericanQuarterHorse is declared
| update replaced variable zelda which cannot be referenced until class ShireHorse is declared
| update replaced variable willow which cannot be referenced until class Thoroughbred is declared
| update overwrote class VirtualDomesticMammalJShell 指出,在我们纠正该类的错误之前,不能引用VirtualHorse类及其子类。该类声明了printSoundInWords方法,并用VirtualDomesticMammal类中相同的名称和参数重写了最近添加的方法。我们在新声明中使用了final关键字,以确保任何子类都不能覆盖它,因此,Java 编译器生成 JShell 显示的错误消息。
现在,我们将创建一个新版本的VirtualHorse抽象类。以下几行显示了删除printSoundInWords方法并使用final关键字确保许多方法不能被任何子类重写的新版本。使用final关键字来避免要重写的方法的声明在下一行中突出显示。样本的代码文件包含在java_9_oop_chapter_07_01文件夹中的example07_02.java文件中。
public abstract class VirtualHorse extends VirtualDomesticMammal {
public VirtualHorse(
int age,
boolean isPregnant,
String name,
String favoriteToy) {
super(age, isPregnant, name, favoriteToy);
System.out.println("VirtualHorse created.");
}
public VirtualHorse(
int age, String name, String favoriteToy) {
this(age, false, name, favoriteToy);
}
public final boolean isAbleToFly() {
return false;
}
public final boolean isRideable() {
return true;
}
public final boolean isHerbivore() {
return true;
}
public final boolean isCarnivore() {
return false;
}
public int getAverageNumberOfBabies() {
return 1;
}
public abstract String getBreed();
public final void printBreed() {
System.out.println(getBreed());
}
public final void printNeigh(
int times,
VirtualDomesticMammal otherDomesticMammal,
boolean isAngry) {
printSoundInWords("Neigh ", times, otherDomesticMammal, isAngry);
}
public final void neigh() {
printNeigh(1, null, false);
}
public final void neigh(int times) {
printNeigh(times, null, false);
}
public final void neigh(int times,
VirtualDomesticMammal otherDomesticMammal) {
printNeigh(times, otherDomesticMammal, false);
}
public final void neigh(int times,
VirtualDomesticMammal otherDomesticMammal,
boolean isAngry) {
printNeigh(times, otherDomesticMammal, isAngry);
}
public final void printNicker(int times,
VirtualDomesticMammal otherDomesticMammal,
boolean isAngry) {
printSoundInWords("Nicker ", times, otherDomesticMammal, isAngry);
}
public final void nicker() {
printNicker(1, null, false);
}
public final void nicker(int times) {
printNicker(times, null, false);
}
public final void nicker(int times,
VirtualDomesticMammal otherDomesticMammal) {
printNicker(times, otherDomesticMammal, false);
}
public final void nicker(int times,
VirtualDomesticMammal otherDomesticMammal,
boolean isAngry) {
printNicker(times, otherDomesticMammal, isAngry);
}
@Override
public final void talk() {
nicker();
}
}进入前几行后,JShell 将显示以下消息:
| update replaced class AmericanQuarterHorse
| update replaced class ShireHorse
| update replaced class Thoroughbred
| update replaced variable american, reset to null
| update replaced variable zelda, reset to null
| update replaced variable willow, reset to null
| update overwrote class VirtualHorse我们替换了VirtualHorse类的定义,子类也进行了更新。重要的是要知道,我们在 JShell 中声明的包含对VirtualHorse子类实例引用的变量被设置为 null。
final关键字还有一个附加用法。我们可以在类声明中使用final作为class关键字之前的修饰符,以指示 Java 我们想要生成一个最终类,即一个不能扩展或子类化的类。Java9 不允许我们为最终类创建子类。
现在,我们将创建VirtualDomesticCat抽象类,然后我们将声明一个名为MaineCoon的具体子类作为最终类。这样,我们将确保没有人能够创建MaineCoon的子类。下面几行显示了VirtualDomesticCat抽象类的代码。样本的代码文件包含在java_9_oop_chapter_07_01文件夹的example07_02.java文件中。
public abstract class VirtualDomesticCat extends VirtualDomesticMammal {
public VirtualDomesticCat(
int age,
boolean isPregnant,
String name,
String favoriteToy) {
super(age, isPregnant, name, favoriteToy);
System.out.println("VirtualDomesticCat created.");
}
public VirtualDomesticCat(
int age, String name, String favoriteToy) {
this(age, false, name, favoriteToy);
}
public final boolean isAbleToFly() {
return false;
}
public final boolean isRideable() {
return false;
}
public final boolean isHerbivore() {
return false;
}
public final boolean isCarnivore() {
return true;
}
public int getAverageNumberOfBabies() {
return 5;
}
public final void printMeow(int times) {
printSoundInWords("Meow ", times, null, false);
}
@Override
public final void talk() {
printMeow(1);
}
}VirtualDomesticCat抽象类将继承自VirtualDomesticMammal超类的许多抽象方法实现为 final 方法,并用 final 方法重写talk方法。因此,我们将无法创建覆盖isAbleToFly方法以返回true的VirtualDomesticCat子类。我们不会有能飞的虚拟猫。
以下几行显示了继承自VirtualDomesticCat的MaineCoon具体类的代码。我们将MaineCoon声明为最终类,它重写继承的getAverageNumberOfBabies方法以返回6。此外,最后一个类实现了以下继承的抽象方法:getBaby和getAsciiArt。样本的代码文件包含在java_9_oop_chapter_07_01文件夹中的example07_02.java文件中。
public final class MaineCoon extends VirtualDomesticCat {
public MaineCoon(
int age,
boolean isPregnant,
String name,
String favoriteToy) {
super(age, isPregnant, name, favoriteToy);
System.out.println("MaineCoon created.");
}
public MaineCoon(
int age, String name, String favoriteToy) {
this(age, false, name, favoriteToy);
}
public String getBaby() {
return "Maine Coon baby ";
}
@Override
public int getAverageNumberOfBabies() {
return 6;
}
public String getAsciiArt() {
return
" ^_^\n" +
" (*.*)\n" +
" |-|\n" +
" / \\\n";
}
}我们没有将任何方法标记为final,因为 final 类中的所有方法都是隐式 final 的。
然而,当我们在 JShell 之外运行 Java 代码时,最终的类将被创建,我们将无法对其进行子类化。
现在,我们将创建继承自VirtualAnimal的VirtualBird抽象类。下面几行显示了VirtualBird抽象类的代码。样本的代码文件包含在example07_02.java文件的java_9_oop_chapter_07_01文件夹中。
public abstract class VirtualBird extends VirtualAnimal {
public String feathersColor;
public VirtualBird(int age, String feathersColor) {
super(age);
this.feathersColor = feathersColor;
System.out.println("VirtualBird created.");
}
public final boolean isAbleToFly() {
// Not all birds are able to fly in real-life
// However, all our virtual birds are able to fly
return true;
}
}VirtualBird抽象类继承先前声明的VirtualAnimal抽象类的成员,并添加一个名为feathersColor的新String可变字段。新的抽象类声明了一个构造函数,该构造函数需要age和feathersColor的初始值来创建该类的实例。构造函数使用super关键字从基类或超类调用构造函数,即在VirtualAnimal类中定义的需要age参数的构造函数。在超类中定义的构造函数完成其执行后,代码设置feathersColor可变字段的值,并打印一条消息,指示已创建虚拟鸟。
VirtualBird抽象类将继承的isAbleToFly方法实现为返回true的最终方法。我们希望确保我们应用领域中的所有虚拟鸟类都能够飞行。
现在,我们将创建继承自VirtualBird的VirtualDomesticBird抽象类。下面几行显示了VirtualDomesticBird抽象类的代码。样本的代码文件包含在example07_02.java文件的java_9_oop_chapter_07_01文件夹中。
public abstract class VirtualDomesticBird extends VirtualBird {
public final String name;
public VirtualDomesticBird(int age,
String feathersColor,
String name) {
super(age, feathersColor);
this.name = name;
System.out.println("VirtualDomesticBird created.");
}
}VirtualDomesticBird抽象类继承先前声明的VirtualBird抽象类的成员,并添加一个名为name的新String不可变字段。新的抽象类声明了一个构造函数,该构造函数需要age、feathersColor和name的初始值来创建该类的实例。构造函数使用super关键字从超类调用构造函数,即VirtualBird类中定义的需要age和feathersColor参数的构造函数。在超类中定义的构造函数完成其执行后,代码设置name不可变字段的值,并打印一条消息,指示已创建虚拟家鸟。
以下几行显示继承自VirtualDomesticBird的Cockatiel具体类的代码。我们将Cockatiel声明为最终类,它实现了以下继承的抽象方法:isRideable、isHerbivore、isCarnivore、getAverageNumberOfBabies、getBaby和getAsciiArt。如前所述,final 类中的所有方法都是隐式 final 的。样本的代码文件包含在example07_02.java文件的java_9_oop_chapter_07_01文件夹中。
public final class Cockatiel extends VirtualDomesticBird {
public Cockatiel(int age,
String feathersColor, String name) {
super(age, feathersColor, name);
System.out.println("Cockatiel created.");
}
public boolean isRideable() {
return true;
}
public boolean isHerbivore() {
return true;
}
public boolean isCarnivore() {
return true;
}
public int getAverageNumberOfBabies() {
return 4;
}
public String getBaby() {
return "Cockatiel baby ";
}
public String getAsciiArt() {
return
" ///\n" +
" .////.\n" +
" // //\n" +
" \\ (*)\\\n" +
" (/ \\\n" +
" /\\ \\\n" +
" /// \\\\\n" +
" ///| |\n" +
" ////| |\n" +
" ////// /\n" +
" //// \\ \\\n" +
" \\\\ ^ ^\n" +
" \\\n" +
" \\\n";
}
}以下几行显示继承自VirtualDomesticMammal的VirtualDomesticRabbit具体类的代码。我们将VirtualDomesticRabbit声明为最终类,因为我们不需要额外的子类。在我们的应用领域中,我们将只有一种类型的虚拟家养兔子。最后一个类实现了以下继承的抽象方法:isAbleToFly、isRideable、isHerbivore、isCarnivore、getAverageNumberOfBabies、getBaby和getAsciiArt。样本的代码文件包含在java_9_oop_chapter_07_01文件夹中的example07_02.java文件中。
public final class VirtualDomesticRabbit extends VirtualDomesticMammal {
public VirtualDomesticRabbit(
int age,
boolean isPregnant,
String name,
String favoriteToy) {
super(age, isPregnant, name, favoriteToy);
System.out.println("VirtualDomesticRabbit created.");
}
public VirtualDomesticRabbit(
int age, String name, String favoriteToy) {
this(age, false, name, favoriteToy);
}
public final boolean isAbleToFly() {
return false;
}
public final boolean isRideable() {
return false;
}
public final boolean isHerbivore() {
return true;
}
public final boolean isCarnivore() {
return false;
}
public int getAverageNumberOfBabies() {
return 6;
}
public String getBaby() {
return "Rabbit baby ";
}
public String getAsciiArt() {
return
" /\\ /\\\n" +
" \\ V /\n" +
" | **)\n" +
" / /\n" +
" / \\_\\_\n" +
"*(__\\_\\\n";
}
}JShell 忽略了final修饰符,因此,使用final修饰符声明的类将允许 JShell 中的子类。
在我们声明了所有的新类之后,我们将创建以下两个方法,它们接收VirtualAnimal实例作为参数,即VirtualAnimal实例或VirtualAnimal的任何子类的实例。每个方法调用VirtualAnimal类中定义的不同实例方法:printAverageNumberOfBabies和printAsciiArg。样本的代码文件包含在example07_02.java文件的java_9_oop_chapter_07_01文件夹中。
void printBabies(VirtualAnimal animal) {
animal.printAverageNumberOfBabies();
}
void printAsciiArt(VirtualAnimal animal) {
animal.printAsciiArt();
}然后以下几行创建下一个类的实例:Cockatiel、VirtualDomesticRabbit和MaineCoon。样本的代码文件包含在example07_02.java文件的java_9_oop_chapter_07_01文件夹中。
Cockatiel tweety =
new Cockatiel(3, "White", "Tweety");
VirtualDomesticRabbit bunny =
new VirtualDomesticRabbit(2, "Bunny", "Sneakers");
MaineCoon garfield =
new MaineCoon(3, "Garfield", "Lassagna");下面的屏幕截图显示了在 JShell 中执行前几行的结果。在输入创建每个实例的代码后,我们将看到不同构造函数在 JShell 中显示的消息。这些消息将使我们能够轻松理解 Java 创建每个实例时调用的所有链式构造函数。
然后,以下几行使用前面创建的实例作为参数调用printBabies和printAsciiArt 方法。样本的代码文件包含在example07_02.java文件的java_9_oop_chapter_07_01文件夹中。
System.out.println(tweety.name);
printBabies(tweety);
printAsciiArt(tweety);
System.out.println(bunny.name);
printBabies(bunny);
printAsciiArt(bunny);
System.out.println(garfield.name);
printBabies(garfield);
printAsciiArt(garfield);这三个实例成为不同方法的VirtualAnimal参数,即它们采用VirtualAnimal实例的形式。但是,用于字段和方法的值不是在VirtualAnimal类中声明的值。对printAverageNumberOfBabies和printAsciiArt 实例方法的调用会考虑子类中声明的所有成员,因为每个实例都是VirtualAnimal子类的实例:
将VirtualAnimal实例作为参数接收的printBabies和printAsciiArt方法只能访问VirtualAnimal类中定义的成员,因为参数类型为VirtualAnimal。如果需要,我们可以打开在animal参数中接收的Cockatiel、VirtualDomesticRabbit和MaineCoon 实例。但是,我们将在稍后讨论更高级的主题时处理这些场景。
下面的屏幕截图显示了在 JShell 中为名为tweety 的Cockatiel实例执行前面几行的结果。
下面的屏幕截图显示了对 JShell 中名为bunny的VirtualDomesticRabbit实例执行前几行的结果。
下面的屏幕截图显示了对 JShell 中名为garfield的MaineCoon实例执行前几行的结果。
现在我们将创建另一个方法,该方法接收一个VirtualDomesticMammal实例作为参数,即VirtualDomesticMammal实例或VirtualDomesticMammal的任何子类的实例。下面的函数调用VirtualDomesticMammal类中定义的talk实例方法。样本的代码文件包含在example07_02.java文件的java_9_oop_chapter_07_01文件夹中。
void makeItTalk(VirtualDomesticMammal domestic) {
domestic.talk();
}然后,以下两行以VirtualDomesticRabbit和MaineCoon实例作为参数调用makeItTalk方法:bunny和garfield。样本的代码文件包含在example07_02.java文件的java_9_oop_chapter_07_01文件夹中。
makeItTalk(bunny);
makeItTalk(garfield);对作为参数接收的VirtualDomesticMammal实例调用相同的方法会产生不同的结果。VirtualDomesticRabbit没有重写继承的talk方法,MaineCoon类继承了VirtualDomesticCat抽象类中重写的talk方法,让家猫喵喵叫。以下屏幕截图显示了 JShell 中两个方法调用的结果:
VirtualAnimal抽象类声明了两个实例方法,允许我们确定一个虚拟动物比另一个虚拟动物年轻还是年长:isYoungerThan和isOlderThan。这两种方法接收一个VirtualAnimal参数,并返回在实例的age值和接收实例的age值之间应用运算符的结果。
下面的行为这三个实例调用了printAge 方法:tweety、bunny和garfield。此方法在VirtualAnimal类中声明。然后,接下来的几行调用isOlderThan和isYoungerThan方法,将这些实例作为参数,以显示比较不同实例年龄的结果。样本的代码文件包含在java_9_oop_chapter_07_01文件夹中的example07_02.java文件中。
tweety.printAge();
bunny.printAge();
garfield.printAge();
tweety.isOlderThan(bunny);
garfield.isYoungerThan(tweety);
bunny.isYoungerThan(garfield);下面的屏幕截图显示了在 JShell 中执行前几行的结果:
- 以下哪一行声明了在任何子类中都不能重写的实例方法:
public void talk(): final {public final void talk() {public notOverrideable void talk() {
- 我们有一个名为
Shape的抽象超类。Circle类是Shape的一个子类,是一个具体类。如果我们创建一个名为circle的Circle实例,该实例也将是:Shape的一个实例。Circle的一个子类。Circle的抽象超类。
- 在 UML 图中,使用斜体文本格式的类名表示:
- 具体课程。
- 重写从其超类继承的至少一个成员的类。
- 抽象类。
- 以下哪一行声明了不能子类化的类:
public final class Dog extends VirtualAnimal {public final class Dog subclasses VirtualAnimal {public final Dog subclasses VirtualAnimal {
- 以下哪一行声明了一个名为
Circle的具体类,该类可以被子类化,其超类是Shape抽象类:public final class Shape extends Circle {public class Shape extends Circle {public concrete class Shape extends Circle {
在本章中,我们创建了许多抽象和具体的类。我们学会了控制子类是否可以覆盖成员,以及类是否可以被子类化。
我们使用了许多子类的实例,我们了解到对象可以有多种形式。我们在 JShell 中使用了许多实例及其方法,以了解我们编写的类和方法是如何执行的。我们使用的方法对具有公共超类的不同类的实例执行操作。
现在,您已经了解了成员继承和多态性,我们已经准备好使用 Java 9 中接口的契约式编程,这是我们将在下一章中讨论的主题。











