在本章中,我们将学习参数多态性,以及 Java9 如何通过编写泛型代码来实现这个面向对象的概念。我们将开始创建使用一个受约束泛型类型的类。我们将:
- 理解参数多态性
- 了解参数多态性和 duck 类型之间的差异
- 了解 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
抽象基类,然后将该类特化为三个具体的子类:SocialLion
、SocialParrot
和SocialSwan
。然后,我们将创建一个Party
类,该类将能够处理通过泛型实现Sociable
接口的任何类的实例。我们将创建两个表示特定异常的新类。我们将与一群善于交际的狮子、一只善于交际的鹦鹉和另一只善于交际的天鹅合作。
下面的 UML 图显示了接口、实现它的抽象类以及我们将创建的具体子类,包括所有字段和方法:
以下行显示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
:此方法必须返回一个名称为Sociable
的String
。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
类实现了Sociable
和Comparable
的Sociable
接口。”
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
类的age
和name
类的【不可变参数】分别为age
和name
类的【不可变参数】赋值。然后该类声明一个受保护的printMessageWithNameAsPrefix
方法,该方法接收一条消息,并打印SocialAnimal
的名称,后跟一个空格和该消息。许多方法将调用此方法以轻松地将名称添加为许多消息的前缀。
该类声明了以下四个抽象方法,它们必须返回一个String
。每个具体的子类必须提供这些方法的实现,并根据社交动物返回适当的String
值:
getDanceRepresentation
getFirstSoundInWords
getSecondSoundInWords
getThirdSoundInWords
我们将在下一个代码段中声明的方法将调用前面枚举的抽象方法。该类实现了返回name
和age
受保护字段值的getName
和getAge
方法。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
:此方法使用通过调用getFirstSoundInWords
、getSecondSoundInWords
和getThirdSoundInWords
检索到的字符串以及接收到的歌词作为参数来打印名称,后跟一条消息,指示社会动物唱歌词。speak
:此方法使用通过调用getDanceRepresentation
检索到的String
和接收到的消息作为参数来打印名称,后面是动物说的单词,后面是它的舞蹈表示字符。welcome
:此方法打印一条消息,表示欢迎在另一个参数中收到的另一个Sociable
。该消息包含目标的名称。sayGoodbyeTo
:此方法使用通过调用getFirstSoundInWords
、getSecondSoundInWords
和getThirdSoundInWords
检索到的字符串,并构建和打印一条消息,与另一个参数中接收到的另一个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
局部变量中是安全的。
最后,代码返回评估当前实例和otherSocialAnimal
对getName
和getAge
的Object.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
抽象类中实现的equals
和compareTo
方法。
我们有SocialAnimal
抽象类,它实现了Sociable
和Comparable<Sociable>
接口。我们无法创建此抽象类的实例。现在,我们将创建一个名为SocialLion
的SocialAnimal
的具体子类。该类声明了一个构造函数,该构造函数最终调用在超类中定义的构造函数。该类实现在其超类中声明的四个抽象方法,以便为将参与一方的 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";
}
}
我们将创建另一个名为SocialParrot
的SocialAnimal
的具体子类。这个新的子类还实现了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";
}
}
最后,我们将创建另一个名为SocialSwan
的SocialAnimal
的具体子类。这个新的子类还实现了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
。以下三个具体类实现了Sociable
和Comparable<Sociable>
接口,它们可以使用继承的重写equals
方法来比较它们的实例:
SocialLion
SocialParrot
SocialSwan
我们将创建两个异常类,因为我们需要抛出不由 Java9 平台中包含的任何类型表示的异常类型。具体来说,我们将创建java.lang.Exception
类的两个子类。
以下几行声明继承自Exception
的InsufficientMembersException
类。当一方的成员数量不足,无法执行需要执行更多成员的操作时,我们将抛出此异常。该类定义了一个不可变的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;
}
}
以下几行声明继承自Exception
的CannotRemovePartyLeaderException
类。当一个方法试图从党的成员列表中删除当前党的领导人时,我们将抛出此异常。在本例中,我们只声明一个继承自Exception
的空类,因为我们不需要额外的特性,我们只需要新类型。样本的代码文件包含在example10_01.java
文件的java_9_oop_chapter_10_01
文件夹中。
public class CannotRemovePartyLeaderException extends Exception {
}
以下几行声明了一个Party
类,该类利用泛型处理许多类型。我们导入java.util.concurrent.ThreadLocalRandom
是因为它是一个非常有用的类,可以轻松生成一个范围内的伪随机数。类名Party
后接小于号(<
)、标识泛型类型参数的T
、extends
关键字和T
泛型类型参数必须实现的接口名Sociable
、符号(&
)和T
必须实现的另一个接口名泛型类型还必须实现Comparable<Sociable>
。大于号(>
结束尖括号(<>
中包含的类型约束声明)。因此,T
泛型类型参数必须是同时实现Sociable
和Comparable<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;
以下几行声明一个构造函数,该构造函数接收类型为T
的partyLeader
参数。该论点指定了第一个党的领导人和第一个党员,即添加到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;
}
下面的行声明了接收类型为T
的newMember
参数的addMember
方法。代码将收到的新成员作为参数添加到members
列表中,并使用newMember
作为参数调用partyLeader.sayWelcomeTo
方法,以使党的领导人欢迎新成员:
public void addMember(T newMember) {
members.add(newMember);
partyLeader.welcome(newMember);
}
以下几行声明了接收类型为T
的memberToRemove
参数的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();
}
}
以下几行声明接收一个lyricString
的makeMembersSingALyric
方法,并使用接收到的lyric
作为members
列表中每个成员的参数调用singALyric
方法:
public void makeMembersSingALyric(String lyric) {
for (T member : members) {
member.singALyric(lyric);
}
}
请注意,方法没有标记为 final,因此,我们将能够在将来的子类中重写这些方法。
最后,下面几行声明了可以抛出一个InsufficientMembersException
的declareNewPartyLeader
方法。与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>
类的实例。到目前为止,我们有三个具体的类来实现Sociable
和Comparable<Sociable>
接口:SocialLion
、SocialParrot
和SocialSwan
。因此,我们可以使用SocialLion
创建Party<SocialLion>
的实例,即SocialLion
的Party
。我们利用类型推断,并使用前面解释的菱形符号。这样,我们将创建一个狮子党,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
方法来让所有的狮子唱歌。我们将在调用removeMember
和declareNewPartyLeader
之前添加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>
的实例,即SocialParrot
的Party
。我们使用前面解释的菱形符号。这样,我们将创建一个鹦鹉党,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 中显示的错误:
我们可以使用SocialSwan
创建Party<SocialSwan>
的实例,即SocialSwan
的Party
。这样,我们将创建一个天鹅党,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
public class Party<T extends Sociable & Comparable<Sociable>>
行表示:- 泛型类型约束指定
T
必须实现Sociable
或Comparable<Sociable>
接口。 - 泛型类型约束指定
T
必须同时实现Sociable
和Comparable<Sociable>
接口。 - 该类是
Sociable
类和Comparable<Sociable>
类的子类。
- 泛型类型约束指定
- 以下哪一行相当于 Java 9 中的
List<SocialLion> lionsList = new ArrayList<SocialLion>();
:List<SocialLion> lionsList = new ArrayList();
List<SocialLion> lionsList = new ArrayList<>();
var lionsList = new ArrayList<SocialLion>();
- 以下哪一行使用菱形符号来利用 Java 9 类型推断:
List<SocialLion> lionsList = new ArrayList<>();
List<SocialLion> lionsList = new ArrayList();
var lionsList = new ArrayList<SocialLion>();
- Java 9 允许我们通过以下方式使用参数多态性:
- 鸭子打字。
- 兔子打字。
- 多态。
- 以下哪个代码段声明了一个类,其泛型类型约束指定
T
必须同时实现Sociable
和Convertible
接口:public class Game<T extends Sociable & Convertible>
public class Game<T: where T is Sociable & Convertible>
public class Game<T extends Sociable> where T: Convertible
在本章中,您学习了如何通过编写能够处理不同类型对象的代码来最大化代码重用,这些对象是实现特定接口或类层次结构包含特定超类的类的实例。我们使用接口、泛型和受约束的泛型类型。
我们创建了能够处理一个受约束泛型类型的类。我们将类继承和接口结合起来,以最大限度地提高代码的可重用性。我们可以使类与许多不同的类型一起工作,并且我们能够对一个团队的行为进行编码,然后可以重用这些行为来创建狮子、鹦鹉和天鹅的团队。
现在,您已经了解了参数多态性和泛型的基本知识,我们已经准备好使用更高级的场景来最大化 Java 9 中泛型的代码重用,这是我们将在下一章中讨论的主题。