Skip to content

Files

Latest commit

8814ef5 · Oct 11, 2021

History

History
805 lines (637 loc) · 40.6 KB

File metadata and controls

805 lines (637 loc) · 40.6 KB

十、使用泛型实现代码重用的最大化

在本章中,我们将学习参数多态性,以及 Java9 如何通过编写泛型代码来实现这个面向对象的概念。我们将开始创建使用一个受约束泛型类型的类。我们将:

  • 理解参数多态性
  • 了解参数多态性和 duck 类型之间的差异
  • 了解 Java 9 泛型和泛型代码
  • 声明要用作类型约束的接口
  • 声明一个符合多个接口的类
  • 声明继承接口实现的子类
  • 创建异常类
  • 声明一个使用受约束泛型类型的类
  • 对多个兼容类型使用泛型类

了解参数多态性、Java 9 泛型和泛型代码

想象一下我们开发了一个 Web 服务,该服务必须与特定野生动物组织的表示一起工作。我们绝对不想把狮子和鬣狗混在一起,因为聚会最终会让鬣狗恐吓一只孤独的狮子。我们想要一个组织良好的聚会,我们不希望在只有狮子参加的聚会上有龙或猫之类的闯入者。

我们想描述一下启动、欢迎成员、组织聚会以及向不同成员告别的程序。然后,我们想用一群天鹅中的天鹅来复制这些程序。因此,我们希望对一群狮子和一群天鹅重复使用我们的程序。将来,我们将需要对其他野生和家养动物,如狐狸、短吻鳄、猫、老虎和狗的聚会使用相同的程序。显然,我们不想成为一群鳄鱼的入侵者。我们也不想参加老虎党。

在前面的章节第 8 章带接口的契约式编程第 9 章带接口的高级契约式编程中,我们学习了使用 Java 9 处理接口。我们可以声明一个接口来指定一个动物的需求,该动物可以参与一个聚会,然后利用 Java 9 的特性编写通用代码,与实现该接口的任何类一起工作。

提示

参数多态性允许我们编写通用的、可重用的代码,可以在不依赖类型的情况下处理值,同时保持完全的静态类型安全。

我们可以通过泛型(也称为泛型编程)利用 Java9 中的参数多态性。在我们声明了一个接口,该接口指示了可以参与一方的动物的需求之后,我们可以创建一个类,该类可以与实现该接口的任何实例一起工作。通过这种方式,我们可以重用生成一组狮子的代码,并创建一组天鹅、鬣狗或任何其他动物。具体地说,我们可以重用生成类的任何实例的一方的代码,该类实现了接口,该接口指定了可以参与一方的动物的需求。

为了参加聚会,我们要求动物要善于交际,因此,我们可以创建一个名为Sociable的接口来指定对能够参加聚会的动物的要求。然而,考虑到我们将要使用的许多野生动物不是很好交际的。

提示

许多现代强类型编程语言允许我们通过泛型处理参数多态性。如果您使用过 C#或 Swift,您会发现 Java 9 中的语法与这些编程语言中使用的语法非常相似。C#也可用于接口,但 Swift 使用协议而不是接口。

其他编程语言,如 Python、JavaScript 和 Ruby,采用了一种称为duck typing的不同理念,其中某些字段和方法的存在使对象适合作为特定的社交动物使用。用鸭子打字,如果我们需要有交际能力的动物,就可以用任何方法来考虑任何对象,只要它提供了所需的方法。因此,在 duck 类型中,任何提供所需方法的类型的任何实例都可以用作社交动物。

让我们转到一个真实的场景来理解鸭子打字的哲学。我们想象一只鸭子像鸭子一样走路。我们完全可以称这种鸟为鸭子,因为它满足了这种鸟成为鸭子所需的所有条件。与鸟和鸭子相关的类似示例生成鸭子类型名称。我们不需要额外的信息来处理这只鸭子。Python、JavaScript 和 Ruby 都是 duck 类型非常流行的语言示例。

在 Java9 中使用 duck 类型是可能的,但在这种编程语言中这并不是一种自然的方式。在 Java9 中实现 duck 类型需要许多复杂的变通方法。因此,我们将重点学习通过泛型编写具有参数多态性的泛型代码。

声明要用作类型约束的接口

首先,我们将创建一个Sociable接口,指定一个类型必须满足的要求,才能被视为一方的潜在成员,即我们应用领域中的社交动物。然后,我们将创建一个实现该接口的SociableAnimal抽象基类,然后将该类特化为三个具体的子类:SocialLionSocialParrotSocialSwan。然后,我们将创建一个Party类,该类将能够处理通过泛型实现Sociable接口的任何类的实例。我们将创建两个表示特定异常的新类。我们将与一群善于交际的狮子、一只善于交际的鹦鹉和另一只善于交际的天鹅合作。

下面的 UML 图显示了接口、实现它的抽象类以及我们将创建的具体子类,包括所有字段和方法:

Declaring an interface to be used as a type constraint

以下行显示Sociable接口的代码。样本的代码文件包含在java_9_oop_chapter_10_01文件夹中的example10_01.java文件中。

public interface Sociable {
    String getName();
    int getAge();
    void actAlone();
    void danceAlone();
    void danceWith(Sociable partner);
    void singALyric(String lyric);
    void speak(String message);
    void welcome(Sociable other);
    void sayGoodbyeTo(Sociable other);
}

接口声明了以下九种方法要求:

  • getName:此方法必须返回一个名称为SociableString
  • getAge:此方法必须返回一个带有Sociable年龄的int
  • actAlone:此方法必须使Sociable单独动作。
  • danceAlone:此方法必须使Sociable单独跳舞。
  • danceWith:此方法必须使Sociable与伙伴参数中接收到的另一Sociable跳舞。
  • singALyric:此方法必须使Sociable唱作为参数接收的歌词。
  • speak:此方法使Sociable说出一条消息。
  • welcome:此方法使Sociable向另一个参数中收到的Sociable发出欢迎信息。
  • sayGoodbyeTo:此方法使Sociable与另一个参数中收到的Sociable告别。

我们在接口声明中没有包含任何默认方法,因此,实现Sociable接口的类负责实现前面列举的九个方法。

声明一个符合多个接口的类

现在,我们将声明一个名为SocialAnimal的抽象类,它实现了前面定义的Sociable接口和Comparable<Sociable>接口。后者使得比较两个Sociable类型的对象成为可能。为了实现Comparable<Sociable>接口,我们必须实现compareTo方法,该方法接收Sociable实例,并返回比较其年龄值的结果。此外,该类将重写从java.lang.Object类继承的equals方法。稍后我们将在查看该类的代码时解释这个复杂方法的代码。我们可以将该类声明理解为“该SocialAnimal类实现了SociableComparableSociable接口。”

SocialAnimal抽象类的代码行太多,因此,我们将使用三个代码段而不是一个。以下几行显示了SocialAnimal抽象类的第一个代码段。样本的代码文件包含在example10_01.java文件的java_9_oop_chapter_10_01文件夹中。

public abstract class SocialAnimal implements Sociable, Comparable<Sociable> {
    public final String name;
    public final int age;

    public SocialAnimal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    protected void printMessageWithNameAsPrefix(String message) {
        System.out.println(
            String.format("%s %s", 
                getName(), 
                message));
    }

    public abstract String getDanceRepresentation();

    public abstract String getFirstSoundInWords();

    public abstract String getSecondSoundInWords();

    public abstract String getThirdSoundInWords();

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getAge() {
        return age;
    }

age类的agename类的【不可变参数】分别为agename类的【不可变参数】赋值。然后该类声明一个受保护的printMessageWithNameAsPrefix方法,该方法接收一条消息,并打印SocialAnimal的名称,后跟一个空格和该消息。许多方法将调用此方法以轻松地将名称添加为许多消息的前缀。

该类声明了以下四个抽象方法,它们必须返回一个String。每个具体的子类必须提供这些方法的实现,并根据社交动物返回适当的String值:

  • getDanceRepresentation
  • getFirstSoundInWords
  • getSecondSoundInWords
  • getThirdSoundInWords

我们将在下一个代码段中声明的方法将调用前面枚举的抽象方法。该类实现了返回nameage受保护字段值的getNamegetAge方法。Sociable接口需要这些方法。

以下几行显示了SocialAnimal抽象类的第二个代码段。样本的代码文件包含在java_9_oop_chapter_10_01文件夹中的example10_01.java文件中。

    @Override
    public void actAlone() {
        printMessageWithNameAsPrefix("to be or not to be");
    }

    @Override
    public void danceAlone() {
        printMessageWithNameAsPrefix(
            String.format("dances alone %s", 
                getDanceRepresentation()));
    }

    @Override
    public void danceWith(Sociable partner) {
        printMessageWithNameAsPrefix(
            String.format("dances with %s %s", 
                partner.getName(),
                getDanceRepresentation()));
    }

    @Override
    public void singALyric(String lyric) {
        printMessageWithNameAsPrefix(
            String.format("sings %s %s %s %s", 
                lyric,
                getFirstSoundInWords(),
                getSecondSoundInWords(),
                getThirdSoundInWords()));
    }

    @Override
    public void speak(String message) {
        printMessageWithNameAsPrefix(
            String.format("says: %s %s", 
                message,
                getDanceRepresentation()));
    }

    @Override
    public void welcome(Sociable other) {
        printMessageWithNameAsPrefix(
            String.format("welcomes %s", 
                other.getName()));
    }

    @Override
    public void sayGoodbyeTo(Sociable other) {
        printMessageWithNameAsPrefix(
            String.format("says goodbye to %s%s%s%s", 
                other.getName(),
                getFirstSoundInWords(),
                getSecondSoundInWords(),
                getThirdSoundInWords()));
    }

SocialAnimal类的第二个代码段实现了Sociable接口所需的其他方法:

  • actAlone:此方法打印名称后跟"to be or not to be"
  • danceAlone:此方法使用通过调用getDanceRepresentation方法检索到的String来打印名称,后面跟着一条消息,指示社会动物正在跳舞。
  • danceWith:此方法使用通过调用getDanceRepresentation方法检索到的String打印名称,后跟一条消息,指示社交动物正在与Sociable类型的 partner 参数中指定的 partner 跳舞。该消息将打印合作伙伴的名称。
  • singALyric:此方法使用通过调用getFirstSoundInWordsgetSecondSoundInWordsgetThirdSoundInWords检索到的字符串以及接收到的歌词作为参数来打印名称,后跟一条消息,指示社会动物唱歌词。
  • speak:此方法使用通过调用getDanceRepresentation检索到的String和接收到的消息作为参数来打印名称,后面是动物说的单词,后面是它的舞蹈表示字符。
  • welcome:此方法打印一条消息,表示欢迎在另一个参数中收到的另一个Sociable。该消息包含目标的名称。
  • sayGoodbyeTo:此方法使用通过调用getFirstSoundInWordsgetSecondSoundInWordsgetThirdSoundInWords检索到的字符串,并构建和打印一条消息,与另一个参数中接收到的另一个Sociable说再见。该消息包含目标的名称。

SocialAnimal类的第三个代码段重写了compareTo方法来实现Comparable<Sociable>接口。此外,SocialAnimal类的最后一个代码段重写了equals方法。样本的代码文件包含在example10_01.java文件的java_9_oop_chapter_10_01文件夹中。

    @Override
    public boolean equals(Object other) {
        // Is other this object?
        if (this == other) {
            return true;
        }
        // Is other null?
        if (other == null) {
            return false;
        }
        // Does other have the same type?
        if (!getClass().equals(other.getClass())) {
            return false;
        }
        SocialAnimal otherSocialAnimal = (SocialAnimal) other;
        // Make sure both the name and age are equal
        return Objects.equals(getName(), otherSocialAnimal.getName())
        && Objects.equals(getAge(), otherSocialAnimal.getAge());
    }

    @Override
    public int compareTo(final Sociable otherSociable) {
        return Integer.compare(getAge(),otherSociable.getAge());
    }
}

SocialAnimal类的第三个代码段重写了从java.lang.Object继承的equals方法,该方法接收我们必须与other参数中的实际实例进行比较的实例。不幸的是,为了覆盖继承的方法,我们必须对另一个参数使用Object类型,因此,该方法的代码必须使用类型转换将接收到的实例转换为SocialAnimal类型。

首先,代码检查收到的Object是否是对实际实例的引用。在这种情况下,代码返回true,无需检查其他内容。

然后,代码检查的值是否等于null。如果方法接收到null,则返回false,因为实际实例不是null

然后,代码检查getClass方法为实际实例返回的String是否与同一方法为接收实例返回的String匹配。如果这些值不匹配,则表示接收到的Object是不同类型的实例,因此不同,代码返回false

此时,我们知道实际实例与接收到的实例具有相同的类型。因此,将另一个参数类型转换为SocialAnimal并将转换后的引用保存在SocialAnimal类型的otherSocialAnimal局部变量中是安全的。

最后,代码返回评估当前实例和otherSocialAnimalgetNamegetAgeObject.equals调用是否都是true的结果。

提示

当我们重写从java.lang.Object继承的equals方法时,遵循前面解释的步骤是一个很好的实践。如果您有使用 C#的经验,那么了解 Java 9 没有提供与IEquatable<T>接口等价的接口是很重要的。此外,考虑到 java 不支持用户定义的操作符重载,它包含在其他 C++面向对象编程语言中,如 C++、C 语言和 SWIFT。

SocialAnimal抽象类还实现了Comparable<Sociable>接口所需的compareTo方法。在这种情况下,代码非常简单,因为该方法在otherSociable参数中接收Sociable实例,并返回调用Integer.compare方法的结果,即java.lang.Integer类的compare类方法。代码使用getAge为当前实例返回的int值和otherSociable作为两个参数来调用此方法。Integer.compare方法返回以下内容:

  • 0如果第一个参数等于第二个参数。
  • 如果第一个参数低于第二个参数,则小于0
  • 如果第一个参数大于第二个参数,则大于0

继承自SocialAnimal的所有具体子类都将能够使用SocialAnimal抽象类中实现的equalscompareTo方法。

声明继承接口实现的子类

我们有SocialAnimal抽象类,它实现了SociableComparable<Sociable>接口。我们无法创建此抽象类的实例。现在,我们将创建一个名为SocialLionSocialAnimal的具体子类。该类声明了一个构造函数,该构造函数最终调用在超类中定义的构造函数。该类实现在其超类中声明的四个抽象方法,以便为将参与一方的 lion 返回适当的值。样本的代码文件包含在example10_01.java文件的java_9_oop_chapter_10_01文件夹中。

public class SocialLion extends SocialAnimal {
 public SocialLion(String name, int age) {
        super(name, age);
    }

    @Override
 public String getDanceRepresentation() {
        return "*-* ^\\/^ (-)";
    }

    @Override
 public String getFirstSoundInWords() {
        return "Roar";
    }

    @Override
 public String getSecondSoundInWords() {
        return "Rrooaarr";
    }

    @Override
 public String getThirdSoundInWords() {
        return "Rrrrrrrroooooaaarrrr";
    }
}

我们将创建另一个名为SocialParrotSocialAnimal的具体子类。这个新的子类还实现了SocialAnimal超类中定义的抽象方法,但在本例中,为 parrot 返回适当的值。样本的代码文件包含在example10_01.java文件的java_9_oop_chapter_10_01文件夹中。

public class SocialParrot extends SocialAnimal {
    public SocialParrot(String name, int age) {
        super(name, age);
    }

    @Override
 public String getDanceRepresentation() {
        return "/|\\ -=- % % +=+";
    }

    @Override
 public String getFirstSoundInWords() {
        return "Yeah";
    }

    @Override
 public String getSecondSoundInWords() {
        return "Yeeaah";
    }

    @Override
 public String getThirdSoundInWords() {
        return "Yeeeaaaah";
    }
}

最后,我们将创建另一个名为SocialSwanSocialAnimal的具体子类。这个新的子类还实现了SocialAnimal超类中定义的抽象方法,但在本例中,它为 swan 返回适当的值。样本的代码文件包含在example10_01.java文件的java_9_oop_chapter_10_01文件夹中。

public class SocialSwan extends SocialAnimal {
    public SocialSwan(String name, int age) {
        super(name, age);
    }

    @Override
 public String getDanceRepresentation() {
        return "^- ^- ^- -^ -^ -^";
    }

    @Override
 public String getFirstSoundInWords() {
        return "OO-OO-OO";
    }

    @Override
 public String getSecondSoundInWords() {
        return "WHO-HO WHO-HO";
    }

    @Override
 public String getThirdSoundInWords() {
        return "WHO-WHO WHO-WHO";
    }
}

我们有三个具体的类,它们从抽象超类继承了两个接口的实现:SociableAnimal。以下三个具体类实现了SociableComparable<Sociable>接口,它们可以使用继承的重写equals方法来比较它们的实例:

  • SocialLion
  • SocialParrot
  • SocialSwan

创建异常类

我们将创建两个异常类,因为我们需要抛出不由 Java9 平台中包含的任何类型表示的异常类型。具体来说,我们将创建java.lang.Exception类的两个子类。

以下几行声明继承自ExceptionInsufficientMembersException类。当一方的成员数量不足,无法执行需要执行更多成员的操作时,我们将抛出此异常。该类定义了一个不可变的numberOfMembers私有字段,其类型为int类型,并使用构造函数中接收到的值进行初始化。此外,该类声明了一个返回该字段值的getNumberOfMembers方法。样本的代码文件包含在example10_01.java文件的java_9_oop_chapter_10_01文件夹中。

public class InsufficientMembersException extends Exception {
    private final int numberOfMembers;

    public InsufficientMembersException(int numberOfMembers) {
        this.numberOfMembers = numberOfMembers;
    }

    public int getNumberOfMembers() {
        return numberOfMembers;
    }
}

以下几行声明继承自ExceptionCannotRemovePartyLeaderException类。当一个方法试图从党的成员列表中删除当前党的领导人时,我们将抛出此异常。在本例中,我们只声明一个继承自Exception的空类,因为我们不需要额外的特性,我们只需要新类型。样本的代码文件包含在example10_01.java文件的java_9_oop_chapter_10_01文件夹中。

public class CannotRemovePartyLeaderException extends Exception {
}

声明使用受约束泛型类型的类

以下几行声明了一个Party类,该类利用泛型处理许多类型。我们导入java.util.concurrent.ThreadLocalRandom是因为它是一个非常有用的类,可以轻松生成一个范围内的伪随机数。类名Party后接小于号(<)、标识泛型类型参数的Textends关键字和T泛型类型参数必须实现的接口名Sociable、符号(&)和T必须实现的另一个接口名泛型类型还必须实现Comparable<Sociable>。大于号(>结束尖括号(<>中包含的类型约束声明)。因此,T泛型类型参数必须是同时实现SociableComparable<Sociable>接口的类型。下面的代码突出显示了使用T泛型类型参数的行。样本的代码文件包含在java_9_oop_chapter_10_01文件夹中的example10_01.java文件中。

import java.util.concurrent.ThreadLocalRandom;

public class Party<T extends Sociable & Comparable<Sociable>> {
 protected final List<T> members;
 protected T partyLeader;

 public Party(T partyLeader) {
        this.partyLeader = partyLeader;
 members = new ArrayList<>();
        members.add(partyLeader);
    }

 public T getPartyLeader() {
        return partyLeader;
    }
 public void addMember(T newMember) {
        members.add(newMember);
        partyLeader.welcome(newMember);
    }

 public T removeMember(T memberToRemove) throws CannotRemovePartyLeaderException {
        if (memberToRemove.equals(partyLeader)) {
            throw new CannotRemovePartyLeaderException();
        }
        int memberIndex = members.indexOf(memberToRemove);
        if (memberIndex >= 0) {
            members.remove(memberToRemove);
            memberToRemove.sayGoodbyeTo(partyLeader);
            return memberToRemove;
        } else {
            return null;
        }
    }

    public void makeMembersAct() {
 for (T member : members) {
            member.actAlone();
        }
    }

    public void makeMembersDance() {
 for (T member : members) {
            member.danceAlone();
        }
    }

    public void makeMembersSingALyric(String lyric) {
 for (T member : members) {
            member.singALyric(lyric);
        }
    }

    public void declareNewPartyLeader() throws InsufficientMembersException {
        if (members.size() == 1) {
            throw new InsufficientMembersException(members.size());
        }
 T newPartyLeader = partyLeader;
        while (newPartyLeader.equals(partyLeader)) {
            int pseudoRandomIndex = 
                ThreadLocalRandom.current().nextInt(
                    0, 
                    members.size());
            newPartyLeader = members.get(pseudoRandomIndex);
        }
        partyLeader.speak(
            String.format("%s is our new party leader.", 
                newPartyLeader.getName()));
        newPartyLeader.danceWith(partyLeader);
        if (newPartyLeader.compareTo(partyLeader) < 0) {
            // The new party leader is younger
            newPartyLeader.danceAlone();
        }
        partyLeader = newPartyLeader;
    }
}

现在我们将分析许多代码片段,以了解Party<T>类中包含的代码是如何工作的。下一行启动类主体,声明一个受保护的List<T>,即类型为T的元素的List或实现T接口。List使用泛型指定将被接受并添加到列表中的元素的类型。

protected final List<T> members;

下一行声明了一个受保护的partyLeader字段,其类型为T

protected T partyLeader;

以下几行声明一个构造函数,该构造函数接收类型为TpartyLeader参数。该论点指定了第一个党的领导人和第一个党员,即添加到membersList<T>的第一个元素。创建新的ArrayList<T>的代码利用了 Java 7 引入的类型推断,在 Java 8 中得到了改进,并在 Java 9 中得到了保留。我们指定了new ArrayList<>()而不是new``ArrayList<T>(),因为 Java 9 可以使用空的类型参数集(<>)从上下文推断类型参数。members受保护字段具有List<T>类型,因此,Java 的类型推断可以确定T是类型,ArrayList<>()表示ArrayList<T>()。最后一行将partyLeader添加到members列表中。

public Party(T partyLeader) {
    this.partyLeader = partyLeader;
    members = new ArrayList<>();
    members.add(partyLeader);
}

提示

当我们用一组空的类型参数调用泛型类的构造函数时,一对尖括号(<>称为菱形,符号为名为菱形符号

以下几行声明了将T指定为返回类型的getPartyLeader方法。方法返回partyLeader

public T getPartyLeader() {
    return partyLeader;
}

下面的行声明了接收类型为TnewMember参数的addMember方法。代码将收到的新成员作为参数添加到members列表中,并使用newMember作为参数调用partyLeader.sayWelcomeTo方法,以使党的领导人欢迎新成员:

public void addMember(T newMember) {
    members.add(newMember);
    partyLeader.welcome(newMember);
}

以下几行声明了接收类型为TmemberToRemove参数的removeMember方法,该方法返回T,并可以抛出CannotRemovePartyLeaderException异常。方法参数后面的throws关键字后跟异常名称,表示该方法可以抛出指定的异常。该代码通过equals方法检查要删除的成员是否与党的领导人匹配。如果该成员是党的领导人,该方法将抛出一个CannotRemovePartyLeaderException异常。代码检索列表中memberToRemove的索引,如果members.remove是列表的成员,则以memberToRemove作为参数调用members.remove方法。然后,代码为成功删除的成员调用sayGoodbyeTo方法,并将partyLeader作为参数。这样,离开党的成员就向党的领导人告别。如果删除了该成员,该方法将返回已删除的成员。否则,该方法返回null

public T removeMember(T memberToRemove) throws CannotRemovePartyLeaderException {
    if (memberToRemove.equals(partyLeader)) {
        throw new CannotRemovePartyLeaderException();
    }
    int memberIndex = members.indexOf(memberToRemove);
    if (memberIndex >= 0) {
        members.remove(memberToRemove);
        memberToRemove.sayGoodbyeTo(partyLeader);
        return memberToRemove;
    } else {
        return null;
    }
}

以下几行声明了为members列表中的每个成员调用actAlone方法的makeMembersAct方法:

public void makeMembersAct() {
    for (T member : members) {
        member.actAlone();
    }
}

在接下来的章节中,我们将学习为列表中的每个成员执行操作的相同方法编码的其他方法,因为我们将在 Java9 中混合面向对象编程和函数编程。

以下几行声明了为members列表中的每个成员调用danceAlone方法的makeMembersDance方法:

public void makeMembersDance() {
    for (T member : members) {
        member.danceAlone();
    }
}

以下几行声明接收一个lyricStringmakeMembersSingALyric方法,并使用接收到的lyric作为members列表中每个成员的参数调用singALyric方法:

public void makeMembersSingALyric(String lyric) {
    for (T member : members) {
        member.singALyric(lyric);
    }
}

提示

请注意,方法没有标记为 final,因此,我们将能够在将来的子类中重写这些方法。

最后,下面几行声明了可以抛出一个InsufficientMembersExceptiondeclareNewPartyLeader方法。与removeMember方法一样,方法参数后面的throws关键字后跟InsufficientMembersException表示该方法可以抛出InsufficientMembersException异常。如果成员列表中只有一个成员,代码会抛出一个InsufficientMembersException异常,并使用members.size()返回的值创建继承自Exception的类的实例。请记住,此异常类使用此值初始化字段,调用此方法的代码将能够检索不足的成员数。如果我们至少有两名成员,代码将生成一个新的伪随机政党领导人,该领导人与现有领导人不同。代码使用ThreadLocalRandom.current().nextInt生成一个范围内的伪随机int数。该代码调用speak方法,让实际领导人向其他党员解释他们有了新的党魁。该代码为新领导人调用danceWith方法,并将前一位政党领导人作为参数。如果以前任党魁为参数调用newPartyLeader.compareTo方法的结果小于0,则表示新党魁比前任党魁年轻,代码调用newPartyLeader.danceAlone方法。最后,代码将新值设置到partyLeader字段。

public void declareNewPartyLeader() throws InsufficientMembersException {
    if (members.size() == 1) {
        throw new InsufficientMembersException(members.size());
    }
    T newPartyLeader = partyLeader;
    while (newPartyLeader.equals(partyLeader)) {
        int pseudoRandomIndex = 
            ThreadLocalRandom.current().nextInt(
                0, 
                members.size());
        newPartyLeader = members.get(pseudoRandomIndex);
    }
    partyLeader.speak(
        String.format("%s is our new party leader.", 
            newPartyLeader.getName()));
    newPartyLeader.danceWith(partyLeader);
    if (newPartyLeader.compareTo(partyLeader) < 0) {
        // The new party leader is younger
        newPartyLeader.danceAlone();
    }
    partyLeader = newPartyLeader;
}

对多个兼容类型使用泛型类

我们可以通过将T泛型类型参数替换为适应Party<T>类声明中指定的类型约束的任何类型名称来创建Party<T>类的实例。到目前为止,我们有三个具体的类来实现SociableComparable<Sociable>接口:SocialLionSocialParrotSocialSwan。因此,我们可以使用SocialLion创建Party<SocialLion>的实例,即SocialLionParty。我们利用类型推断,并使用前面解释的菱形符号。这样,我们将创建一个狮子党,Simba是党的领袖。样本的代码文件包含在java_9_oop_chapter_10_01文件夹中的example10_01.java文件中。

SocialLion simba = new SocialLion("Simba", 10);
SocialLion mufasa = new SocialLion("Mufasa", 5);
SocialLion scar = new SocialLion("Scar", 9);
SocialLion nala = new SocialLion("Nala", 7);
Party<SocialLion> lionsParty = new Party<>(simba);

对于类定义使用名为T的泛型类型参数的所有参数,lionsParty实例将只接受一个SocialLion实例。以下几行通过为每个实例调用addMember方法,将先前创建的SocialLion的三个实例添加到狮子党。样本的代码文件包含在example10_01.java文件的java_9_oop_chapter_10_01文件夹中。

lionsParty.addMember(mufasa);
lionsParty.addMember(scar);
lionsParty.addMember(nala);

下面的行调用makeMembersAct方法来让所有的狮子行动,调用makeMembersDance方法来让所有的狮子跳舞,使用removeMember方法来移除一个不是党的领导人的成员,使用declareNewPartyLeader方法来宣布一个新的领导人,最后调用makeMembersSingALyric方法来让所有的狮子唱歌。我们将在调用removeMemberdeclareNewPartyLeader之前添加try关键字,因为这些方法可能引发异常。在这种情况下,我们不检查removeMember返回的结果。样本的代码文件包含在example10_01.java文件的java_9_oop_chapter_10_01文件夹中。

lionsParty.makeMembersAct();
lionsParty.makeMembersDance();
try {
    lionsParty.removeMember(nala);
} catch (CannotRemovePartyLeaderException e) {
    System.out.println(
        "We cannot remove the party leader.");
}
try {
    lionsParty.declareNewPartyLeader();
} catch (InsufficientMembersException e) {
    System.out.println(
        String.format("We just have %s member",
            e.getNumberOfMembers()));
}
lionsParty.makeMembersSingALyric("Welcome to the jungle");

下面几行显示了在 JShell 中运行前面的代码段后的输出。然而,我们必须考虑到,新党领导人的选择是伪随机的,因此,每次执行的结果都会有所不同:

Simba welcomes Mufasa
Simba welcomes Scar
Simba welcomes Nala
Simba to be or not to be
Mufasa to be or not to be
Scar to be or not to be
Nala to be or not to be
Simba dances alone *-* ^\/^ (-)
Mufasa dances alone *-* ^\/^ (-)
Scar dances alone *-* ^\/^ (-)
Nala dances alone *-* ^\/^ (-)
Nala says goodbye to Simba RoarRrooaarrRrrrrrrroooooaaarrrr
Simba says: Scar is our new party leader. *-* ^\/^ (-)
Scar dances with Simba *-* ^\/^ (-)
Scar dances alone *-* ^\/^ (-)
Simba sings Welcome to the jungle Roar Rrooaarr Rrrrrrrroooooaaarrrr
Mufasa sings Welcome to the jungle Roar Rrooaarr Rrrrrrrroooooaaarrrr
Scar sings Welcome to the jungle Roar Rrooaarr Rrrrrrrroooooaaarrrr

我们可以使用SocialParrot来创建Party<SocialParrot>的实例,即SocialParrotParty。我们使用前面解释的菱形符号。这样,我们将创建一个鹦鹉党,Rio是党的领袖。样本的代码文件包含在example10_01.java文件的java_9_oop_chapter_10_01文件夹中。

SocialParrot rio = new SocialParrot("Rio", 3);
SocialParrot thor = new SocialParrot("Thor", 6);
SocialParrot rambo = new SocialParrot("Rambo", 4);
SocialParrot woody = new SocialParrot("Woody", 5);
Party<SocialParrot> parrotsParty = new Party<>(rio);

对于类定义使用名为T的泛型类型参数的所有参数,parrotsParty实例将只接受一个SocialParrot实例。下面几行通过为每个实例调用addMember方法,将先前创建的SocialParrot的三个实例添加到 parrots 的 party 中。样本的代码文件包含在example10_01.java文件的java_9_oop_chapter_10_01文件夹中。

parrotsParty.addMember(thor);
parrotsParty.addMember(rambo);
parrotsParty.addMember(woody);

下面几行调用makeMembersDance方法让所有的鹦鹉跳舞,使用removeMember方法移除一个非党魁的成员,使用declareNewPartyLeader方法宣布新的领导人,最后调用makeMembersSingALyric方法让所有的鹦鹉唱歌。样本的代码文件包含在的java_9_oop_chapter_10_01文件夹中,的example10_01.java文件中。

parrotsParty.makeMembersDance();
try {
    parrotsParty.removeMember(rambo);
} catch (CannotRemovePartyLeaderException e) {
    System.out.println(
        "We cannot remove the party leader.");
}
try {
    parrotsParty.declareNewPartyLeader();
} catch (InsufficientMembersException e) {
    System.out.println(
        String.format("We just have %s member",
            e.getNumberOfMembers()));
}
parrotsParty.makeMembersSingALyric("Fly like a bird");

下面几行显示了在 JShell 中运行前面的代码段后的输出。同样,我们必须考虑到,新党领导人的选择是伪随机的,因此,每次执行的结果都会有所不同:

Rio welcomes Thor
Rio welcomes Rambo
Rio welcomes Woody
Rio dances alone /|\ -=- % % +=+
Thor dances alone /|\ -=- % % +=+
Rambo dances alone /|\ -=- % % +=+
Woody dances alone /|\ -=- % % +=+
Rambo says goodbye to Rio YeahYeeaahYeeeaaaah
Rio says: Woody is our new party leader. /|\ -=- % % +=+
Woody dances with Rio /|\ -=- % % +=+
Rio sings Fly like a bird Yeah Yeeaah Yeeeaaaah
Thor sings Fly like a bird Yeah Yeeaah Yeeeaaaah
Woody sings Fly like a bird Yeah Yeeaah Yeeeaaaah

由于使用了不兼容的类型,以下行将无法编译。首先,我们尝试将一个SocialParrot实例rio添加到Party<SocialLion>``lionsParty中。然后,我们尝试将一个SocialLion实例simba添加到Party<SocialParrot>``parrotsParty中。这两行都将无法编译,JShell 将显示一条消息,指示类型不兼容,无法转换为各方所需的必要类型。样本的代码文件包含在example10_02.java文件的java_9_oop_chapter_10_01文件夹中。

// The following lines won't compile
// and will generate errors in JShell
lionsParty.addMember(rio);
parrotsParty.addMember(simba);

下面的屏幕截图显示了我们尝试执行前几行时 JShell 中显示的错误:

Using a generic class for multiple compatible types

我们可以使用SocialSwan创建Party<SocialSwan>的实例,即SocialSwanParty。这样,我们将创建一个天鹅党,Kevin是党的领袖。样本的代码文件包含在example10_03.java文件的java_9_oop_chapter_10_01文件夹中。

SocialSwan kevin = new SocialSwan("Kevin", 3);
SocialSwan brandon = new SocialSwan("Brandon", 5);
SocialSwan nicholas = new SocialSwan("Nicholas", 6);
Party<SocialSwan> swansParty = new Party<>(kevin);

对于类定义使用名为T的泛型类型参数的所有参数,swansParty实例将只接受一个SocialSwan实例。下面几行通过为每个实例调用addMember方法,将先前创建的SocialSwan的两个实例添加到 swans’party 中。样本的代码文件包含在example10_03.java文件的java_9_oop_chapter_10_01文件夹中。

swansParty.addMember(brandon);
swansParty.addMember(nicholas);

下面的行调用makeMembersDance方法让所有的鹦鹉跳舞,使用removeMember方法尝试移除党的领导人,使用declareNewPartyLeader方法宣布新的领导人,最后调用makeMembersSingALyric方法让所有的天鹅唱歌。样本的代码文件包含在java_9_oop_chapter_10_01文件夹中的example10_03.java文件中。

swansParty.makeMembersDance();
try {
    swansParty.removeMember(kevin);
} catch (CannotRemovePartyLeaderException e) {
    System.out.println(
        "We cannot remove the party leader.");
}
try {
    swansParty.declareNewPartyLeader();
} catch (InsufficientMembersException e) {
    System.out.println(
        String.format("We just have %s member",
            e.getNumberOfMembers()));
}
swansParty.makeMembersSingALyric("It will be our swan song");

下面几行显示了在 JShell 中运行前面的代码段后的输出。同样,我们必须考虑到,新党领导人的选择是伪随机的,因此,每次执行的结果都会有所不同:

Kevin welcomes Brandon
Kevin welcomes Nicholas
Kevin dances alone ^- ^- ^- -^ -^ -^
Brandon dances alone ^- ^- ^- -^ -^ -^
Nicholas dances alone ^- ^- ^- -^ -^ -^
We cannot remove the party leader.
Kevin says: Brandon is our new party leader. ^- ^- ^- -^ -^ -^
Brandon dances with Kevin ^- ^- ^- -^ -^ -^
Kevin sings It will be our swan song OO-OO-OO WHO-HO WHO-HO WHO-WHO WHO-WHO
Brandon sings It will be our swan song OO-OO-OO WHO-HO WHO-HO WHO-WHO WHO-WHO
Nicholas sings It will be our swan song OO-OO-OO WHO-HO WHO-HO WHO-WHO WHO-WHO

测试你的知识

  1. public class Party<T extends Sociable & Comparable<Sociable>>行表示:
    1. 泛型类型约束指定T必须实现SociableComparable<Sociable>接口。
    2. 泛型类型约束指定T必须同时实现SociableComparable<Sociable>接口。
    3. 该类是Sociable类和Comparable<Sociable>类的子类。
  2. 以下哪一行相当于 Java 9 中的List<SocialLion> lionsList = new ArrayList<SocialLion>();
    1. List<SocialLion> lionsList = new ArrayList();
    2. List<SocialLion> lionsList = new ArrayList<>();
    3. var lionsList = new ArrayList<SocialLion>();
  3. 以下哪一行使用菱形符号来利用 Java 9 类型推断:
    1. List<SocialLion> lionsList = new ArrayList<>();
    2. List<SocialLion> lionsList = new ArrayList();
    3. var lionsList = new ArrayList<SocialLion>();
  4. Java 9 允许我们通过以下方式使用参数多态性:
    1. 鸭子打字。
    2. 兔子打字。
    3. 多态。
  5. 以下哪个代码段声明了一个类,其泛型类型约束指定T必须同时实现SociableConvertible接口:
    1. public class Game<T extends Sociable & Convertible>
    2. public class Game<T: where T is Sociable & Convertible>
    3. public class Game<T extends Sociable> where T: Convertible

总结

在本章中,您学习了如何通过编写能够处理不同类型对象的代码来最大化代码重用,这些对象是实现特定接口或类层次结构包含特定超类的类的实例。我们使用接口、泛型和受约束的泛型类型。

我们创建了能够处理一个受约束泛型类型的类。我们将类继承和接口结合起来,以最大限度地提高代码的可重用性。我们可以使类与许多不同的类型一起工作,并且我们能够对一个团队的行为进行编码,然后可以重用这些行为来创建狮子、鹦鹉和天鹅的团队。

现在,您已经了解了参数多态性和泛型的基本知识,我们已经准备好使用更高级的场景来最大化 Java 9 中泛型的代码重用,这是我们将在下一章中讨论的主题。