Skip to content

Files

Latest commit

 

History

History
857 lines (656 loc) · 38.5 KB

File metadata and controls

857 lines (656 loc) · 38.5 KB

七、成员继承与多态性

在本章中,我们将学习 Java9 中面向对象编程最令人兴奋的特性之一:多态性。我们将编写许多类的代码,然后在 JShell 中使用它们的实例,以了解对象如何具有多种不同的形式。我们将:

  • 创建从抽象超类继承的具体类
  • 使用子类的实例
  • 理解多态性
  • 控制子类是否可以覆盖成员
  • 控制类是否可以子类化
  • 使用对不同子类的实例执行操作的方法

创建从抽象超类继承的具体类

在上一章的中,我们创建了一个名为VirtualAnimal的抽象基类,然后编码了以下三个抽象子类:VirtualMammalVirtualDomesticMammalVirtualHorse。现在,我们将编写以下三个具体类。每个类代表不同的马品种,是VirtualHorse抽象类的一个子类。

  • AmericanQuarterHorse:该类代表一匹属于美国四分之一马品种的虚拟马。
  • ShireHorse:该类表示属于夏尔马品种的虚拟马。
  • Thoroughbred:该类代表纯种品种的虚拟马。

三个具体类将实现从抽象超类继承的以下三个抽象方法:

  • String getAsciiArt():此抽象方法继承自VirtualAnimal抽象类。
  • String getBaby():此抽象方法继承自VirtualAnimal抽象类。
  • String getBreed():此抽象方法继承自VirtualHorse抽象类。

下面的 UML 图显示了我们将编码的三个具体类的成员:AmericanQuarterHorseShireHorseThoroughbred。我们不为每个具体类将声明的三个方法使用粗体文本格式,因为它们没有覆盖这些方法;他们正在实现类继承的抽象方法。

Creating concrete classes that inherit from abstract superclasses

首先,我们将创建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参数的值。

getBabygetBreed方法的实现中,每个类返回不同的String。此外,在getAsciiArt方法的实现中,每个类为虚拟马返回不同的 ASCII 艺术表示。

理解多态性

我们可以使用相同的方法,即具有相同名称和参数的方法,根据调用该方法的类导致不同的事情发生。在面向对象编程中,此功能称为多态性。多态性是一个对象具有多种形式的能力,我们将通过使用先前编码的具体类的实例来了解它的实际作用。

以下几行创建名为americanAmericanQuarterHorse类的新实例,并使用其不需要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属于VirtualAnimalVirtualMammalVirtualDomesticMammalVirtualHorseAmericanQuarterHorse类。样本的代码文件包含在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变量中)可以采用以下任何类的实例的形式:

  • VirtualAnimal
  • VirtualMammal
  • VirtualDomesticMammal
  • VirtualHorse
  • AmericanQuarterHorse

以下截图显示了在 JShell 中执行前几行的结果:

Understanding polymorphism

我们在VirtualHorse类中对方法进行了编码,并且没有在任何子类中重写该方法。以下是printBreed方法的代码:

public void printBreed() {
    System.out.println(getBreed());
}

代码打印由getBreed方法返回的String,该方法与抽象方法在同一个类中声明。继承自VirtualHorse的三个具体类实现了getBreed方法,每个类返回不同的String。当我们调用american.printBreed方法时,JShell 显示American Quarter Horse

以下几行创建了名为zeldaShireHorse类的实例。注意,在本例中,我们使用需要isPregnant参数的构造函数。正如我们创建AmericanQuarterHorse类的实例时所发生的那样,JShell 将为每个构造函数显示一条消息,这些构造函数是我们编码的链式构造函数的结果。样本的代码文件包含在java_9_oop_chapter_07_01文件夹中的example07_01.java文件中。

ShireHorse zelda =
    new ShireHorse(9, true, 
        "Zelda", "Tennis Ball");

接下来的几行分别调用americanprintAverageNumberOfBabiesprintAsciiArt实例方法AmericanQuarterHorse的实例和zelda的实例方法ShireHorse的实例。样本的代码文件包含在example07_01.java文件的java_9_oop_chapter_07_01文件夹中。

american.printAverageNumberOfBabies();
american.printAsciiArt();
zelda.printAverageNumberOfBabies();
zelda.printAsciiArt();

我们将printAverageNumberOfBabiesprintAsciiArt方法编码在VirtualAnimal类中,并且我们没有在其任何子类中重写它们。因此,当我们为americanZelda调用这些方法时,Java 将执行VirtualAnimal类中定义的代码。

printAverageNumberOfBabies方法使用getAverageNumberOfBabies返回的int值和getBaby方法返回的String值生成一个String,表示虚拟动物的平均婴儿数量。VirtualHorse类使用返回1的代码实现继承的getAverageNumberOfBabies抽象方法。AmericanQuarterHorseShireHorse类实现了继承的getBaby抽象方法,其代码返回一个String,表示虚拟马品种的婴儿:"AQH baby""ShireHorse baby"。因此,我们对printAverageNumberOfBabies方法的调用将在每个实例中产生不同的结果,因为它们属于不同的类。

printAsciiArt方法使用getAsciiArt方法返回的String打印代表虚拟马的 ASCII 艺术。AmericanQuarterHorseShireHorse类实现了继承的getAsciiArt抽象方法,其代码返回一个带有 ASCII 艺术的String,该艺术适合于类所代表的每个虚拟马。因此,我们对printAsciiArt方法的调用将在每个实例中产生不同的结果,因为它们属于不同的类。

下面的屏幕截图显示了在 JShell 中执行前几行的结果。这两个实例为VirtualAnimal抽象类中编码的两个方法运行相同的代码。但是,每个类为方法提供了不同的实现,这些方法最终被调用以生成结果并导致输出中的差异。

Understanding polymorphism

下面的行创建名为willowThoroughbred类实例,然后调用其printAsciiArt方法。正如前面所发生的那样,JShell 将为每个构造函数显示一条消息,这些构造函数是我们编码的链式构造函数的结果。样本的代码文件包含在example07_01.java文件的java_9_oop_chapter_07_01文件夹中。

Thoroughbred willow = 
    new Thoroughbred(5,
        "Willow", "Jolly Ball");
willow.printAsciiArt();

下面的屏幕截图显示了在 JShell 中执行前几行的结果。新实例来自一个类,该类提供了getAsciiArt方法的不同实现,因此,我们将看到不同的 ASCII 艺术,这与我们在前两次对其他实例的相同方法调用中看到的不同。

Understanding polymorphism

下面的行使用不同数量的参数调用名为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 中使用不同参数调用neighnicker方法的结果:

Understanding polymorphism

我们为名为willowThoroughbred实例调用了VirtualHorse类中定义的neigh方法的四个版本。调用neigh方法的第三行和第四行为VirtualDomesticMammal类型的otherDomesticMammal参数指定一个值。第三行指定americanotherDomesticMammal的值,第四行指定zelda为同一参数的值。AmericanQuarterHorseShireHorse混凝土类都是VirtualHorse的子类,VirtualHorseVirtualDomesticMammal的子类。因此,我们可以使用americanzelda作为需要VirtualDomesticMammal实例的参数。

然后,我们为名为americanAmericanQuarterHorse实例调用了VirtualHorse类中定义的nicker方法的四个版本。调用nicker方法的第三行和第四行指定willow作为类型为VirtualDomesticMammalotherDomesticMammal参数的值。Thoroughbred混凝土类也是VirtualHorse的一个子类,VirtualHorseVirtualDomesticMammal的一个子类。因此,我们可以使用willow作为需要VirtualDomesticMammal实例的参数。

控制子类中成员的可重写性

我们将对VirtualDomesticCat抽象类及其具体子类MaineCoon进行编码。然后,我们将对VirtualBird抽象类、其VirtualDomesticBird抽象子类和Cockatiel具体子类进行编码。最后,我们将对VirtualDomesticRabbit具体类进行编码。在编码这些类时,我们将使用 Java9 特性来决定子类是否可以覆盖特定的成员。

所有的虚拟家猫都必须能够说话,因此,我们将覆盖继承自VirtualDomesticMammaltalk方法来打印表示猫喵喵叫的单词:"Meow"。我们还想提供一种打印"Meow"特定次数的方法。因此,在这一点上,我们意识到我们可以利用我们在VirtualHorse类中声明的printSoundInWords方法。

我们无法在VirtualDomesticCat抽象类中访问此实例方法,因为它不是从VirtualHorse继承的。因此,我们将把这个方法从VirtualHorse类移到它的超类:VirtualDomesticMammal

提示

对于不希望在子类中重写的方法,我们将在返回类型之前使用final关键字。当一个方法被标记为 final 方法时,子类无法重写该方法,如果它们试图重写,Java9 编译器将显示错误。

并不是所有的鸟都能在现实生活中飞翔。然而,我们所有的虚拟鸟都能飞,因此,我们将实现继承的isAbleToFly抽象方法作为返回true的最终方法。通过这种方式,我们确保继承自VirtualBird抽象类的所有类都将始终为isAbleToFly方法运行此代码,并且它们将无法重写它。

下面的 UML 图显示了我们将要编码的新抽象和具体类的成员。另外,图中显示了printSoundInWords方法从VirtualHorse抽象类移动到VirtualDomesticMammal抽象类。

Controlling overridability of members in subclasses

首先,我们将创建一个新版本的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 VirtualDomesticMammal

JShell 指出,在我们纠正该类的错误之前,不能引用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方法以返回trueVirtualDomesticCat子类。我们不会有能飞的虚拟猫。

以下几行显示了继承自VirtualDomesticCatMaineCoon具体类的代码。我们将MaineCoon声明为最终类,它重写继承的getAverageNumberOfBabies方法以返回6。此外,最后一个类实现了以下继承的抽象方法:getBabygetAsciiArt。样本的代码文件包含在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 代码时,最终的类将被创建,我们将无法对其进行子类化。

现在,我们将创建继承自VirtualAnimalVirtualBird抽象类。下面几行显示了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可变字段。新的抽象类声明了一个构造函数,该构造函数需要agefeathersColor的初始值来创建该类的实例。构造函数使用super关键字从基类或超类调用构造函数,即在VirtualAnimal类中定义的需要age参数的构造函数。在超类中定义的构造函数完成其执行后,代码设置feathersColor可变字段的值,并打印一条消息,指示已创建虚拟鸟。

VirtualBird抽象类将继承的isAbleToFly方法实现为返回true的最终方法。我们希望确保我们应用领域中的所有虚拟鸟类都能够飞行。

现在,我们将创建继承自VirtualBirdVirtualDomesticBird抽象类。下面几行显示了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不可变字段。新的抽象类声明了一个构造函数,该构造函数需要agefeathersColorname的初始值来创建该类的实例。构造函数使用super关键字从超类调用构造函数,即VirtualBird类中定义的需要agefeathersColor参数的构造函数。在超类中定义的构造函数完成其执行后,代码设置name不可变字段的值,并打印一条消息,指示已创建虚拟家鸟。

以下几行显示继承自VirtualDomesticBirdCockatiel具体类的代码。我们将Cockatiel声明为最终类,它实现了以下继承的抽象方法:isRideableisHerbivoreisCarnivoregetAverageNumberOfBabiesgetBabygetAsciiArt。如前所述,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";
    }
}

以下几行显示继承自VirtualDomesticMammalVirtualDomesticRabbit具体类的代码。我们将VirtualDomesticRabbit声明为最终类,因为我们不需要额外的子类。在我们的应用领域中,我们将只有一种类型的虚拟家养兔子。最后一个类实现了以下继承的抽象方法:isAbleToFlyisRideableisHerbivoreisCarnivoregetAverageNumberOfBabiesgetBabygetAsciiArt。样本的代码文件包含在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类中定义的不同实例方法:printAverageNumberOfBabiesprintAsciiArg。样本的代码文件包含在example07_02.java文件的java_9_oop_chapter_07_01文件夹中。

void printBabies(VirtualAnimal animal) {
    animal.printAverageNumberOfBabies();
}

void printAsciiArt(VirtualAnimal animal) {
    animal.printAsciiArt();
}

然后以下几行创建下一个类的实例:CockatielVirtualDomesticRabbitMaineCoon。样本的代码文件包含在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 创建每个实例时调用的所有链式构造函数。

Creating methods that work with instances of different subclasses

然后,以下几行使用前面创建的实例作为参数调用printBabiesprintAsciiArt 方法。样本的代码文件包含在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类中声明的值。对printAverageNumberOfBabiesprintAsciiArt 实例方法的调用会考虑子类中声明的所有成员,因为每个实例都是VirtualAnimal子类的实例:

提示

VirtualAnimal实例作为参数接收的printBabiesprintAsciiArt方法只能访问VirtualAnimal类中定义的成员,因为参数类型为VirtualAnimal。如果需要,我们可以打开在animal参数中接收的CockatielVirtualDomesticRabbitMaineCoon 实例。但是,我们将在稍后讨论更高级的主题时处理这些场景。

下面的屏幕截图显示了在 JShell 中为名为tweetyCockatiel实例执行前面几行的结果。

Creating methods that work with instances of different subclasses

下面的屏幕截图显示了对 JShell 中名为bunnyVirtualDomesticRabbit实例执行前几行的结果。

Creating methods that work with instances of different subclasses

下面的屏幕截图显示了对 JShell 中名为garfieldMaineCoon实例执行前几行的结果。

Creating methods that work with instances of different subclasses

现在我们将创建另一个方法,该方法接收一个VirtualDomesticMammal实例作为参数,即VirtualDomesticMammal实例或VirtualDomesticMammal的任何子类的实例。下面的函数调用VirtualDomesticMammal类中定义的talk实例方法。样本的代码文件包含在example07_02.java文件的java_9_oop_chapter_07_01文件夹中。

void makeItTalk(VirtualDomesticMammal domestic) {
    domestic.talk();
}

然后,以下两行以VirtualDomesticRabbitMaineCoon实例作为参数调用makeItTalk方法:bunnygarfield。样本的代码文件包含在example07_02.java文件的java_9_oop_chapter_07_01文件夹中。

makeItTalk(bunny);
makeItTalk(garfield);

对作为参数接收的VirtualDomesticMammal实例调用相同的方法会产生不同的结果。VirtualDomesticRabbit没有重写继承的talk方法,MaineCoon类继承了VirtualDomesticCat抽象类中重写的talk方法,让家猫喵喵叫。以下屏幕截图显示了 JShell 中两个方法调用的结果:

Creating methods that work with instances of different subclasses

VirtualAnimal抽象类声明了两个实例方法,允许我们确定一个虚拟动物比另一个虚拟动物年轻还是年长:isYoungerThanisOlderThan。这两种方法接收一个VirtualAnimal参数,并返回在实例的age值和接收实例的age值之间应用运算符的结果。

下面的行为这三个实例调用了printAge 方法:tweetybunnygarfield。此方法在VirtualAnimal类中声明。然后,接下来的几行调用isOlderThanisYoungerThan方法,将这些实例作为参数,以显示比较不同实例年龄的结果。样本的代码文件包含在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 中执行前几行的结果:

Creating methods that work with instances of different subclasses

测试你的知识

  1. 以下哪一行声明了在任何子类中都不能重写的实例方法:
    1. public void talk(): final {
    2. public final void talk() {
    3. public notOverrideable void talk() {
  2. 我们有一个名为Shape的抽象超类。Circle类是Shape的一个子类,是一个具体类。如果我们创建一个名为circleCircle实例,该实例也将是:
    1. Shape的一个实例。
    2. Circle的一个子类。
    3. Circle的抽象超类。
  3. 在 UML 图中,使用斜体文本格式的类名表示:
    1. 具体课程。
    2. 重写从其超类继承的至少一个成员的类。
    3. 抽象类。
  4. 以下哪一行声明了不能子类化的类:
    1. public final class Dog extends VirtualAnimal {
    2. public final class Dog subclasses VirtualAnimal {
    3. public final Dog subclasses VirtualAnimal {
  5. 以下哪一行声明了一个名为Circle的具体类,该类可以被子类化,其超类是Shape抽象类:
    1. public final class Shape extends Circle {
    2. public class Shape extends Circle {
    3. public concrete class Shape extends Circle {

总结

在本章中,我们创建了许多抽象和具体的类。我们学会了控制子类是否可以覆盖成员,以及类是否可以被子类化。

我们使用了许多子类的实例,我们了解到对象可以有多种形式。我们在 JShell 中使用了许多实例及其方法,以了解我们编写的类和方法是如何执行的。我们使用的方法对具有公共超类的不同类的实例执行操作。

现在,您已经了解了成员继承和多态性,我们已经准备好使用 Java 9 中接口的契约式编程,这是我们将在下一章中讨论的主题。