Java多态用法详解(附带实例)
多态是面向对象的一大特性,封装和继承是为实现多态性做准备的。
多态是多种形态能力的特性。它可以提高程序的抽象程度和简洁性,最大限度降低类和程序模块间的耦合性。
Java 语言中,多态性体现在两方面,由方法重载实现的静态多态性(编译时多态)和方法重写实现的动态多态性(也称动态联编):
【实例】定义一个代表动物的类 Animal,具有方法 eat(),再定义 Animal 的两个子类 Rabbit 和 Tiger,分别覆盖父类中的 eat() 方法,最后定义测试类进行测试。
变量 a1 引用的是 Dog 类对象,因此语句 a1.eat() 调用 Dog 类的 eat() 方法,同理,a2.eat() 调用的是 Cat 类的 eat() 方法。从运行结果可以看出,虽然都是通过 Pet 类型的变量调用 eat() 方法,但是变量引用的对象不同,执行结果也不同,这就是多态的体现。
由此可知多态的特点:将子类的对象赋值给父类变量,在运行时就会表现具体子类的特征。
针对上例,我们进行扩充,假如再定义宠物喂养者类,负责给宠物投喂食物,给狗投喂狗粮,给猫投喂猫粮,如果不使用多态,则具体程序实现如下例所示。
【实例】定义宠物喂养者类 Feeder,分别给狗和猫进行食物投喂。
如果使用多态,就可以在 Feeder 中只定义一个 feed() 方法,统一喂养所有类型的宠物,这时只需要修改 feed() 方法,变为如下代码所示:
由此可见,只使用封装和继承的 Java 程序,可以称之为基于对象编程,而只有把多态加进来才能称之为面向对象编程。
假设 B 类是 A 类子类或间接子类,当我们用子类 B 创建一个对象,并把这个对象的引用赋给 A 类的对象:
上转型对象可以操作子类继承或覆盖成员变量,也可以使用子类继承的或重写的方法。但上转型对象不能操作子类新增的成员变量,不能使用子类新增的方法。
可以将对象的上转型对象再强制转换到一个子类对象,该子类对象又具备了子类所有属性和功能。
同样地,也可以把一个父类对象赋值给一个子类变量,这叫作向下转型,但这时需要进行强制类型转换。
下面通过一个例子说明子类与父类之间类型转换的使用原则:
【实例】定义父类 Mammal 和子类 Monkey,在主方法中定义上转型对象,并测试上转型对象调用父子类中相关方法的权限。
因为 computer() 方法是子类新增的方法,无法通过上转型对象调用该方法,所以语句
总之,父类对象和子类对象的转化需要注意如下原则。
多态是多种形态能力的特性。它可以提高程序的抽象程度和简洁性,最大限度降低类和程序模块间的耦合性。
Java 语言中,多态性体现在两方面,由方法重载实现的静态多态性(编译时多态)和方法重写实现的动态多态性(也称动态联编):
- 静态多态性:在编译阶段,具体调用哪个被重载的方法,编译器会根据参数的不同来静态确定。
- 动态多态性:由于子类继承了父类所有的属性(私有的除外),所以子类对象可以作为父类对象使用。代码中凡是使用父类对象的地方,都可以用子类对象代替。一个对象可以通过引用子类的实例来调用子类的方法。
Java多态的实现
父类对象变量可指向子类对象形成多态,下面通过一个例子演示多态的实现。【实例】定义一个代表动物的类 Animal,具有方法 eat(),再定义 Animal 的两个子类 Rabbit 和 Tiger,分别覆盖父类中的 eat() 方法,最后定义测试类进行测试。
class Pet { public void eat() { System.out.println("宠物吃宠物粮"); } } class Dog extends Pet { public void eat() { System.out.println("狗吃狗粮"); } } class Cat extends Pet { public void eat() { System.out.println("猫吃猫粮"); } } public class TestAnimal { public static void main(String[] args) { Pet a1 = new Dog(); Pet a2 = new Cat(); a1.eat(); a2.eat(); } }运行该程序,结果为:
狗吃狗粮
猫吃猫粮
变量 a1 引用的是 Dog 类对象,因此语句 a1.eat() 调用 Dog 类的 eat() 方法,同理,a2.eat() 调用的是 Cat 类的 eat() 方法。从运行结果可以看出,虽然都是通过 Pet 类型的变量调用 eat() 方法,但是变量引用的对象不同,执行结果也不同,这就是多态的体现。
由此可知多态的特点:将子类的对象赋值给父类变量,在运行时就会表现具体子类的特征。
针对上例,我们进行扩充,假如再定义宠物喂养者类,负责给宠物投喂食物,给狗投喂狗粮,给猫投喂猫粮,如果不使用多态,则具体程序实现如下例所示。
【实例】定义宠物喂养者类 Feeder,分别给狗和猫进行食物投喂。
class Feeder { public void feed(Dog d) { System.out.println("开始投喂:"); d.eat(); } public void feed(Cat c) { System.out.println("开始投喂:"); c.eat(); } } public class FeedWithoutPolymorphism { public static void main(String[] args) { Feeder f = new Feeder(); // 投喂狗 f.feed(new Dog()); // 投喂猫 f.feed(new Cat()); } }程序执行结果为:
开始投喂:
狗吃狗粮
开始投喂:
猫吃猫粮
如果使用多态,就可以在 Feeder 中只定义一个 feed() 方法,统一喂养所有类型的宠物,这时只需要修改 feed() 方法,变为如下代码所示:
public void feed(Pet p) { System.out.println("开始投喂:"); p.eat(); }其他代码保持不变,再次运行程序,结果和前面的实例一致。当调用 feed() 传递参数时,形参为父类类型,可以接收任意的子类对象,这就体现了多态。在实际开发中,也可以让接口类型作为形参,实参是任何实现了该接口的实现类,同样也可以体现多态,这种方式更加常见。
由此可见,只使用封装和继承的 Java 程序,可以称之为基于对象编程,而只有把多态加进来才能称之为面向对象编程。
Java父类与子类间的类型转化
根据之前实现多态的例子可以看出,Java 语言允许某个类型的引用变量引用其子类的实例。这如同前面介绍的数据类型转换,引用数据类型也可以进行类型转换。假设 B 类是 A 类子类或间接子类,当我们用子类 B 创建一个对象,并把这个对象的引用赋给 A 类的对象:
A a; B b = new B(); a = b;称这个 A 类对象 a 是子类对象 b 的上转型对象。
上转型对象可以操作子类继承或覆盖成员变量,也可以使用子类继承的或重写的方法。但上转型对象不能操作子类新增的成员变量,不能使用子类新增的方法。
可以将对象的上转型对象再强制转换到一个子类对象,该子类对象又具备了子类所有属性和功能。
同样地,也可以把一个父类对象赋值给一个子类变量,这叫作向下转型,但这时需要进行强制类型转换。
b= (B)a;向下转型可以调用子类类型中所有的成员。不过需要注意的是,如果父类对象指向的是子类对象,那么向下转型的过程是安全的,也就是编译也运行都不会出错。但是如果父类对象是父类本身,那么向下转型的过程是不安全的,即编译不会报错,但是运行时会出现 Java 强制类型转换异常,所以一般不建议进行向下转型。
下面通过一个例子说明子类与父类之间类型转换的使用原则:
【实例】定义父类 Mammal 和子类 Monkey,在主方法中定义上转型对象,并测试上转型对象调用父子类中相关方法的权限。
class Mammal { // 哺乳动物类 private int n = 50; void crySpeak(String s) { System.out.println(s); } } public class Monkey extends Mammal { // 猴子类 void computer(int aa, int bb) { int cc = aa * bb; System.out.println(cc); } void crySpeak(String s) { System.out.println("***" + s + "***"); } public static void main(String args[]) { // mammal 是 Monkey 类对象的上转型对象 Mammal mammal = new Monkey(); Mammal mammal1 = new Mammal(); mammal.crySpeak("I love this game"); mammal.computer(10, 10); // 编译报错,因为上转型对象不能操作子类新增成员 Monkey monkey = mammal; // 编译报错,向下转型必须进行强制类型转换 // 把上转型对象强制转化为子类的对象 Monkey monkey = (Monkey)mammal; monkey.computer(10, 10); monkey = (Monkey)mammal1; monkey.computer(10, 10); } }上述代码中,如果注释掉编译报错的两条语句,再执行程序,结果为:
**I love this game** 100 Exception in thread "main" java.lang.ClassCastException: class ch4.Mammal cannot be cast to class ch4.Monkey at ch4.Monkey.main(Monkey.java:29)mammal 是个上转型对象,实际引用的是子类 Monkey 对象,所以语句
mammal.crySpeak("I love this game");
实际调用的是 Monkey 中的重写的 crySpeak() 方法。因为 computer() 方法是子类新增的方法,无法通过上转型对象调用该方法,所以语句
mammal.computer(10,10);
编译报错。Monkey monkey=(Monkey)mammal;
语句把上转型对象强制转换为子类对象后,可以操作子类新增方法,原因是 mammal 实质上指向的是子类对象,而 monkey=(Monkey)mammal1;
语句也是强制把父类对象赋给子类变量,但是 mammal1 实质上指向的是父类对象,所以通过 mammal 调用子类新增方法,运行时报错。总之,父类对象和子类对象的转化需要注意如下原则。
- 子类对象可被视为是其父类的一个对象;
- 父类对象不能被当作是其某一个子类的对象;
- 如果一个方法的形式参数定义的是父类对象,那么调用这个方法时,可以使用子类对象作为实际参数。