思维导图:
4.4 多态
在深入研究对象的世界后,我们已经学到了封装和继承。接下来,我们要讨论的是面向对象编程的另一个核心概念:多态。
4.4.1 多态概述
多态是面向对象的三大特性之一,它允许我们以统一的方式处理不同类的对象。简单来说,多态允许一个接口或父类引用代表其任何派生类的对象。这意味着,我们可以设计一个方法,它的输入参数是父类类型,但当我们传递该方法的子类对象时,它会根据对象的实际类型执行相应的操作。
一个经典的例子是动物和它们的叫声。考虑一个“动物”抽象类和几个从动物类派生出来的子类,如“猫”和“狗”。虽然每种动物的叫声都是不同的,但我们可以定义一个方法,让它接受一个动物对象并让动物叫。当我们传递一个猫对象时,它会“喵喵叫”;当我们传递一个狗对象时,它会“汪汪叫”。
在Java中,多态主要体现在以下两个方面:
- 方法的重载:同一个方法名但参数类型或参数数量不同。
- 对象的多态(方法的重写):子类覆盖父类的方法。
4.4.2 多态示例
考虑以下的Java代码示例:
// 定义抽象类Animal
abstract class Animal {
abstract void shout();
}
// 定义Cat类,继承自Animal
class Cat extends Animal {
@Override
public void shout() {
System.out.println("喵喵……");
}
}
// 定义Dog类,继承自Animal
class Dog extends Animal {
@Override
public void shout() {
System.out.println("汪汪……");
}
}
// 定义测试类
public class Example14 {
public static void main(String[] args) {
Animal an1 = new Cat(); // Cat对象向上转型为Animal
Animal an2 = new Dog(); // Dog对象向上转型为Animal
an1.shout(); // 输出“喵喵……”
an2.shout(); // 输出“汪汪……”
}
}
此代码展示了如何使用抽象类和继承来实现多态。当我们运行这个程序时,输出结果为:“喵喵……汪汪……”。
这是因为,尽管an1
和an2
都是被声明为Animal
类型的,但它们实际引用的对象是Cat
和Dog
,因此当我们调用shout()
方法时,它们分别执行了Cat
类和Dog
类中的实现。
这就是多态的魔力,允许我们编写更加通用、可扩展和可维护的代码。
4.4.2 对象类型的转换笔记
1. 对象类型转换概述
- 向上转型: 子类对象 → 父类对象。
- 向下转型: 父类对象 → 子类对象。
2. 对象向上转型
- 当一个子类对象被当作父类对象使用时,就发生了向上转型。
- 向上转型是自动完成的。
优点: - 增加程序的灵活性。 - 有助于隐藏具体的实现代码,从而更好地使用抽象类编写程序。
例子:
class Animal {
public void shout() {
System.out.println("喵喵……");
}
}
class Dog extends Animal {
public void shout() {
System.out.println("汪汪……");
}
public void eat() {
System.out.println("吃骨头……");
}
}
public class Example15 {
public static void main(String[] args) {
Dog dog = new Dog();
Animal an = dog; // 向上转型
an.shout(); // 输出 "汪汪……"
}
}
注意: - 一个向上转型的对象无法调用子类中特有的方法。
3. 对象向下转型
- 使用向下转型来重新获得子类特有的属性和方法。
- 向下转型必须明确指定转型的数据类型,并用强制类型转换符进行标识。
- 必须先进行向上转型才能进行向下转型,否则会出现异常。
例子:
class Animal {
public void shout() {
System.out.println("喵喵……");
}
}
class Dog extends Animal {
public void shout() {
System.out.println("汪汪……");
}
public void eat() {
System.out.println("吃骨头……");
}
}
public class Example16 {
public static void main(String[] args) {
Animal an = new Dog(); // 向上转型
Dog dog = (Dog)an; // 向下转型
dog.shout(); // 输出 "汪汪……"
dog.eat(); // 输出 "吃骨头……"
}
}
注意: - 不能直接将父类实例强制转换为子类实例,这会导致错误。例如: java Dog dog = (Dog) new Animal(); // 编译错误
通过以上的解释和示例代码,我们可以明白Java中对象的向上转型和向下转型的原理及其用法。
4.4.3 instanceof
关键字笔记
1. 简介
- Java提供了
instanceof
关键字,用于判断一个对象是否是某个特定类或接口的实例。
语法格式:
对象 instanceof 类(或接口)
- 如果“对象”是指定的类或接口的实例对象,则结果返回
true
,否则返回false
。
2. 示例
文件4-17 Example17.java:
class Animal {
public void shout() {
System.out.println("动物叫……");
}
}
class Dog extends Animal {
public void shout() {
System.out.println("汪汪……");
}
public void eat() {
System.out.println("吃骨头……");
}
}
public class Example17 {
public static void main(String[] args) {
Animal a1 = new Dog(); // 向上转型
System.out.println("Animal a1 = new Dog():" + (a1 instanceof Animal));
System.out.println("Animal a1 = new Dog():" + (a1 instanceof Dog));
Animal a2 = new Animal();
System.out.println("Animal a2 = new Animal():" + (a2 instanceof Animal));
System.out.println("Animal a2 = new Animal():" + (a2 instanceof Dog));
}
}
输出:
Animal a1 = new Dog():true
Animal a1 = new Dog():true
Animal a2 = new Animal():true
Animal a2 = new Animal():false
3. 结论和解析
- 当一个对象是一个类的实例或者是其子类的实例时,使用
instanceof
检查会返回true
。 - 在上面的示例中,
a1
是Dog
类的实例,因此它既是Dog
的实例又是Animal
的实例。 - 而
a2
仅仅是Animal
的实例,不是Dog
的实例,所以当我们检查它是否是Dog
的实例时,返回false
。
使用instanceof
关键字,我们可以在运行时判断对象的实际类型,从而做出相应的逻辑判断。这在多态的情境中尤其有用,可以让我们更加灵活地处理不同类型的对象。
4.4 多态总结
1. 重点:
- 定义: 多态是面向对象编程的三大特性之一,允许将子类的对象当作父类的对象使用。
- 实现: Java中的多态主要依赖于继承、接口和重写。
- 向上转型: 当子类对象被当作父类对象使用时,会发生自动的向上转型。例如:
Animal a = new Dog();
这里,Dog对象自动转型为Animal。 - 向下转型: 可以使用强制类型转换将父类对象转为子类对象,但在转型之前,该对象确实应该是那个特定子类的实例。例如:
Dog d = (Dog) a;
这里,a必须实际上是Dog的一个实例。 instanceof
关键字: 可以检查一个对象是否是某个特定类或接口的实例。
2. 难点:
- 理解: 对于初学者,理解不同对象之间的动态绑定可能是个挑战。
- 向下转型的风险: 若试图对不正确的类型进行向下转型,会引发运行时异常。例如,若
a
是Cat的实例,那么(Dog) a
将抛出异常。 - 方法调用: 当使用多态时,始终会调用实际对象的重写方法,而不是引用变量的类型的方法。这意味着,对于上面的例子,如果Dog类重写了shout方法,那么
a.shout()
将调用Dog类的shout方法,而不是Animal类的。
3. 易错点:
- 误解向下转型: 很多初学者可能认为,任何父类引用都可以被向下转型为任何子类,这是错误的。例如,
Animal a = new Animal(); Dog d = (Dog) a;
这将导致运行时异常。 - 丢失功能: 在向上转型后,只能调用在父类中定义的方法和属性,即使子类有更多的功能。例如,如果Dog类有一个特有的fetch方法,那么你不能使用Animal引用
a
来调用a.fetch()
。 - 混淆方法覆盖与重载: 多态涉及到方法覆盖(子类提供父类方法的特定实现),而不是方法重载(同一个类中的方法有不同的参数)。