根据Venkat Subramaniam的传说,多态性是面向对象编程中最重要的概念。多态性 -或者对象基于其类型执行专门操作的能力 - 是使Java代码具有灵活性的原因。命令,观察者,装饰者,策略等设计模式以及Gang Of Four创建的许多其他模式都使用某种形式的多态性。掌握这一概念极大地提高了您通过解决方案来应对编程挑战的能力。
获取代码
您可以获取此挑战的源代码并在此处运行您自己的测试:https://github.com/rafadelnero/javaworld-challengers
多态中的接口和继承
有了这个Java挑战者,我们专注于多态和继承之间的关系。要记住的主要事情是多态性需要继承或接口实现。您可以在下面的示例中看到这一点,其中包括Duke和Juggy:
<span style="color:#000000"><code>
public abstract class JavaMascot {
public abstract void executeAction();
}
public class Duke extends JavaMascot {
@Override
public void executeAction() {
System.out.println("Punch!");
}
}
public class Juggy extends JavaMascot {
@Override
public void executeAction() {
System.out.println("Fly!");
}
}
public class JavaMascotTest {
public static void main(String... args) {
JavaMascot dukeMascot = new Duke();
JavaMascot juggyMascot = new Juggy();
dukeMascot.executeAction();
juggyMascot.executeAction();
}
}
</code></span>
此代码的输出将是:
<span style="color:#000000"><code>
Punch!
Fly!
</code></span>
由于其特定的实现,既Duke与Juggy人的行为将被执行。
方法是否重载多态?
许多程序员对多态与方法重写和方法重载的关系感到困惑。实际上,只有方法覆盖才是真正的多态性。重载共享相同的方法名称,但参数不同。多态性是一个广义的术语,所以总是会讨论这个主题。
多态的目的是什么?
使用多态的巨大优势和目的是将客户端类与实现代码分离。客户端类接收实现以执行必要的操作,而不是硬编码。通过这种方式,客户端类知道足以执行其操作,这是松散耦合的一个示例。
为了更好地理解多态的目的,请看一下SweetCreator:
<span style="color:#000000"><code>
public abstract class SweetProducer {
public abstract void produceSweet();
}
public class CakeProducer extends SweetProducer {
@Override
public void produceSweet() {
System.out.println("Cake produced");
}
}
public class ChocolateProducer extends SweetProducer {
@Override
public void produceSweet() {
System.out.println("Chocolate produced");
}
}
public class CookieProducer extends SweetProducer {
@Override
public void produceSweet() {
System.out.println("Cookie produced");
}
}
public class SweetCreator {
private List<SweetProducer> sweetProducer;
public SweetCreator(List<SweetProducer> sweetProducer) {
this.sweetProducer = sweetProducer;
}
public void createSweets() {
sweetProducer.forEach(sweet -> sweet.produceSweet());
}
}
public class SweetCreatorTest {
public static void main(String... args) {
SweetCreator sweetCreator = new SweetCreator(Arrays.asList(new CakeProducer(),
new ChocolateProducer(), new CookieProducer()));
sweetCreator.createSweets();
}
}
</code></span>
在此示例中,您可以看到SweetCreator该类只知道 SweetProducer 该类。它不知道每个的实现Sweet。这种分离使我们能够灵活地更新和重用我们的类,并使代码更易于维护。在设计代码时,始终寻找使其尽可能灵活和可维护的方法。多态性是一种非常强大的技术,可用于这些目的。
方法覆盖中的协变返回类型
如果它是协变类型,则可以更改重写方法的返回类型。一个协变型基本上是返回类型的子类。考虑一个例子:
<span style="color:#000000"><code>
public abstract class JavaMascot {
abstract JavaMascot getMascot();
}
public class Duke extends JavaMascot {
@Override
Duke getMascot() {
return new Duke();
}
}
</code></span>
因为Duke是a JavaMascot,我们可以在覆盖时更改返回类型。
与核心Java类的多态性
我们一直在核心Java类中使用多态。一个非常简单的例子是当我们实例化ArrayList将List接口声明 为类型的类时:
<span style="color:#000000"><code>
List<String> list = new ArrayList<>();
</code></span>
为了更进一步,请使用不带多态性的Java Collections API来考虑此代码示例:
<span style="color:#000000"><code>
public class ListActionWithoutPolymorphism {
// Example without polymorphism
void executeVectorActions(Vector<Object> vector) {/* Code repetition here*/}
void executeArrayListActions(ArrayList<Object> arrayList) {/*Code repetition here*/}
void executeLinkedListActions(LinkedList<Object> linkedList) {/* Code repetition here*/}
void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList<Object> copyOnWriteArrayList)
{ /* Code repetition here*/}
}
public class ListActionInvokerWithoutPolymorphism {
listAction.executeVectorActions(new Vector<>());
listAction.executeArrayListActions(new ArrayList<>());
listAction.executeLinkedListActions(new LinkedList<>());
listAction.executeCopyOnWriteArrayListActions(new CopyOnWriteArrayList<>());
}
</code></span>
丑陋的代码,不是吗?想象一下试图保持它!现在看看具有多态性的相同示例:
<span style="color:#000000"><code>
public static void main(String … polymorphism) {
ListAction listAction = new ListAction();
listAction.executeListActions();
}
public class ListAction {
void executeListActions(List<Object> list) {
// Execute actions with different lists
}
}
public class ListActionInvoker {
public static void main(String... masterPolymorphism) {
ListAction listAction = new ListAction();
listAction.executeListActions(new Vector<>());
listAction.executeListActions(new ArrayList<>());
listAction.executeListActions(new LinkedList<>());
listAction.executeListActions(new CopyOnWriteArrayList<>());
}
}
</code></span>
多态性的好处是灵活性和可扩展性。我们可以只声明一个接收泛型List类型的方法,而不是创建几个不同的方法。
在多态方法调用中调用特定方法
可以在多态调用中调用特定方法,但这样做是以牺牲灵活性为代价的。这是一个例子:
<span style="color:#000000"><code>
public abstract class MetalGearCharacter {
abstract void useWeapon(String weapon);
}
public class BigBoss extends MetalGearCharacter {
@Override
void useWeapon(String weapon) {
System.out.println("Big Boss is using a " + weapon);
}
void giveOrderToTheArmy(String orderMessage) {
System.out.println(orderMessage);
}
}
public class SolidSnake extends MetalGearCharacter {
void useWeapon(String weapon) {
System.out.println("Solid Snake is using a " + weapon);
}
}
public class UseSpecificMethod {
public static void executeActionWith(MetalGearCharacter metalGearCharacter) {
metalGearCharacter.useWeapon("SOCOM");
// The below line wouldn't work
// metalGearCharacter.giveOrderToTheArmy("Attack!");
if (metalGearCharacter instanceof BigBoss) {
((BigBoss) metalGearCharacter).giveOrderToTheArmy("Attack!");
}
}
public static void main(String... specificPolymorphismInvocation) {
executeActionWith(new SolidSnake());
executeActionWith(new BigBoss());
}
}
</code></span>
我们在这里使用的技术是在运行时转换或故意更改对象类型。
请注意,仅在将泛型类型转换为特定类型时才可以调用特定方法。一个很好的类比就是明确地向编译器说:“嘿,我知道我在这里做了什么,所以我要将对象转换为特定的类型并使用特定的方法。”
参考上面的例子,编译器拒绝接受特定的方法调用有一个重要原因:正在传递的类可能是SolidSnake。在这种情况下,编译器无法确保声明MetalGearCharacter了giveOrderToTheArmy方法的每个子类。
该instanceof保留的关键字
注意保留字instanceof。在调用特定方法之前,我们询问是否MetalGearCharacter为“ instanceof” BigBoss。如果它不是一个BigBoss实例,我们会收到下面的异常消息:
<span style="color:#000000"><code>
Exception in thread "main" java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake cannot be cast to com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss
</code></span>
该super保留的关键字
如果我们想从Java超类引用属性或方法怎么办?在这种情况下,我们可以使用super保留字。例如:
<span style="color:#000000"><code>
public class JavaMascot {
void executeAction() {
System.out.println("The Java Mascot is about to execute an action!");
}
}
public class Duke extends JavaMascot {
@Override
void executeAction() {
super.executeAction();
System.out.println("Duke is going to punch!");
}
public static void main(String... superReservedWord) {
new Duke().executeAction();
}
}
</code></span>
使用保留字super中Duke的executeAction方法调用超方法。然后我们执行特定的操作Duke。这就是为什么我们可以在下面的输出中看到这两条消息:
<span style="color:#000000"><code>
The Java Mascot is about to execute an action!
Duke is going to punch!
</code></span>
采取多态性挑战!
让我们试一下你所学到的关于多态和继承的知识。在这个挑战中,您将获得Matt Groening的“辛普森一家”中的一些方法,您的挑战是推断出每个类的输出结果。首先,请仔细分析以下代码:
<span style="color:#000000"><code>
public class PolymorphismChallenge {
static abstract class Simpson {
void talk() {
System.out.println("Simpson!");
}
protected void prank(String prank) {
System.out.println(prank);
}
}
static class Bart extends Simpson {
String prank;
Bart(String prank) { this.prank = prank; }
protected void talk() {
System.out.println("Eat my shorts!");
}
protected void prank() {
super.prank(prank);
System.out.println("Knock Homer down");
}
}
static class Lisa extends Simpson {
void talk(String toMe) {
System.out.println("I love Sax!");
}
}
public static void main(String... doYourBest) {
new Lisa().talk("Sax :)");
Simpson simpson = new Bart("D'oh");
simpson.talk();
Lisa lisa = new Lisa();
lisa.talk();
((Bart) simpson).prank();
}
}
</code></span>
你怎么看?最终的输出是什么?不要使用IDE来解决这个问题!关键是要提高代码分析技能,所以尽量确定自己的输出。
选择你的答案,你将能够在下面找到正确的答案。
<span style="color:#000000"><code>
A) I love Sax!
D'oh
Simpson!
D'oh
B) Sax :)
Eat my shorts!
I love Sax!
D'oh
Knock Homer down
C) Sax :)
D'oh
Simpson!
Knock Homer down
D) I love Sax!
Eat my shorts!
Simpson!
D'oh
Knock Homer down
</code></span>
刚刚发生了什么?了解多态性
对于以下方法调用:
<span style="color:#000000"><code>
new Lisa().talk("Sax :)");
</code></span>
输出将是“ I love Sax!”这是因为我们传递String给方法并Lisa具有方法。
对于下一次调用:
<span style="color:#000000"><code>
Simpson simpson = new Bart("D'oh");</code>
<code>simpson.talk();
</code></span>
输出将是“ Eat my shorts!”这是因为我们正在实例化Simpson类型Bart。
现在检查一下,这有点棘手:
<span style="color:#000000"><code>
Lisa lisa = new Lisa();
lisa.talk();
</code></span>
在这里,我们使用继承方法重载。我们没有向talk方法传递任何内容,这Simpson talk就是调用该方法的原因。在这种情况下,输出将是:
<span style="color:#000000"><code>
"Simpson!"
</code></span>
还有一个:
<span style="color:#000000"><code>
((Bart) simpson).prank();
</code></span>
在这种情况下,prank String当我们用Bart类实例化时传递了new Bart("D'oh");。在这种情况下,首先super.prank调用该方法,然后调用特定的prank方法Bart。输出将是:
<span style="color:#000000"><code>
"D'oh"
"Knock Homer down"
</code></span>
多态性常见错误
认为可以在不使用强制转换的情况下调用特定方法是一种常见错误。
另一个错误是不确定在以多态方式实例化类时将调用哪种方法。请记住,要调用的方法是创建的实例的方法。
还要记住,方法重写不是方法重载。
如果参数不同,则无法覆盖方法。这是可能的改变重载方法的返回类型如果返回类型为超类方法的子类。
什么要记住多态性
- 创建的实例将确定使用多态时将调用哪种方法。
- 的
@Override注释责成使用一个重写方法程序员; 如果没有,则会出现编译错误。 - 多态性可以与普通类,抽象类和接口一起使用。
- 大多数设计模式依赖于某种形式的多态性。
- 在多态子类中使用特定方法的唯一方法是使用强制转换。
- 可以使用多态在代码中设计一个强大的结构。
- 运行测试。这样做,您将能够掌握这个强大的概念!
回答**
这个问题的答案Java的挑战者是d。输出将是:
<span style="color:#000000"><code>
I love Sax!
Eat my shorts!
Simpson!
D'oh
Knock Homer down
</code></span>