文章导读
【多态是面向对象编程的一大特征,利用多态特征编程,可以让应用程序具有良好的扩展性。通过子类对父类方法的重写和类方法的重载,可以在不改变原有代码的情况下扩展程序的功能。本文通过图书类案例讲述了什么是类的多态概念,并介绍了Java所有类的祖先Object类以及父类与子类之间类型的转换。】
本文分成三个小节用来理解Java类的多态概念。第一小节通过一个图书类案例,展示了如何用子类扩展父类的功能;第二小节介绍了Java所有类的祖先Object类,在Java中定义的所有的类都继承于Object类;第三小节介绍了父类与子类之间的类型转换及动态绑定功能。
第一小节 用多态思想扩展父类的行为
前面了解了对象的继承思想,继承思想的核心理念是代码可复用性和程序功能的可扩展性。功能的可扩展性是指继承后的子类在父类的基础上增加新的行为,或者对父类的行为进行扩展,实现同一个行为具有不同的实现代码。
例1:在父类的基础上增加新的行为
在『深入了解面向对象的继承思想』一文中,给出了一个案例,案例内容及案例代码参见『深入了解面向对象的继承思想』一文,案例中类继承结构如下图。

图1 类继承结构图
图中的纸质图书类,电子图书类、视频类和音频类均继承于Product类,被继承的Product称为父类,继承的纸质图书类,电子图书类、视频类和音频类称为子类,子类继承父类的所有属性和方法。现在要求EBook类除了输出Product类的公有属性外,还要输出EBook类的私有属性。从案例代码中可以看出,EBook类的父类提供了输出公有属性的行为,代码如下:
//输出产品属性
public void outProduct()
{
System.out.println("图书名称:" + name);
System.out.println("图书价格:" + String.valueOf(price));
System.out.println("图书作者:" + author);
System.out.println("图书摘要:" + summary);
System.out.println("图书字数:" + words);
System.out.println("图书页数:" + pageNumbers);
}EBook类虽然可以调用父类的outProduct()方法输出公有属性,但无法输出EBook类的私有属性。这种情况下,可以在EBook类增加outEBook()方法,用于输出EBook类的私有属性,代码如下:
//输出电子图书属性
public void outEBook()
{
//调用父类的outProduct()输出公有属性
outProduct();
System.out.println("电子图书的格式:" + formation);
System.out.println("电子图书的文件大小:" + String.valueOf(filesize));
}代码首先调用父类的outProduct()方法输出公有属性,然后再输出该类的私有属性。
例2:扩展父类的行为
例1要求EBook类除了输出父类Product类的公有属性外,还要求输出EBook类的私有属性,例1给出的解决方案是在EBook类增加outEBook()方法,该方法首先调用父类的outProduct()方法输出父类的公有属性,然后再输出EBook类的私有属性。
其实,还有一种解决方案,在EBook类中重写父类的outProduct(),这样当EBook对象调用outProduct()方法时,其父类的outProduct()方法被忽略,而执行EBook类的outProduct()方法。代码如下:
//输出电子图书类产品属性
public void outProduct()
{
//输出父类的公有属性
System.out.println("产品名称:" + name);
System.out.println("产品价格:" + String.valueOf(price));
System.out.println("产品作者:" + author);
System.out.println("产品摘要:" + summary);
//输出EBook类的私有属性
System.out.println("电子图书的格式:" + formation);
System.out.println("电子图书的文件大小:" + String.valueOf(filesize));
}
public static void main(String[] args) {
// TODO Auto-generated method stub
//声明和创建EBook对象
EBook ebook = new EBook("三国演义",21.2,"罗贯中","展现三国历史风云");
//调用outProduct()方法
ebook.outProduct();
}
例2给出的子类重写父类的方法,就是面向对象的多态概念。在程序运行过程中,子类的行为代替了父类的行为。父类Product类有输出属性的方法outProduct(),而它的子类EBook类、Video类、Audio类、PaperBook类也都有这个方法。换言之,对于不同的出版物产品,都有自己输出属性的方法,这就构成了对象的多态概念。
多态也就是多种表现形态,前面讲的方法重载和本文中方法的重写是Java多态性的不同表现。方法重载是一个类中多态性的表现,方法重写是父类与子类之间多态性的一种表现。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载。如果在子类中定义某方法与其父类有相同的名称和参数,就称为方法的重写。
第二小节 认识Java所有类的祖先Object类
Java提供了一个Object类,Java所有的类都继承于Objec类。此类是所有Java类的父类,如果在类的声明中未使用extends关键字指定父类,则默认为继承自Object类,任何继承Object的类,都可以调用Object类的方法。
Object类包含在java.lang包,下面是Object类几个实用的方法。
String toStirng()方法
用于返回当前对象的有关信息,返回类型为String类型。其作用是当需要对外输出对象信息时,这个对象自动调用toString()方法,通过这个方法返回一个表示对象自身信息的字符串,对象信息默认的内容是“类名称+哈希编码”。如果需要输出自己定义的类信息,则需要重写toStirng()方法。
例如:
public class FruitManager {
public static void main(String[] args) {
// TODO Auto-generated method stub
//声明和创建Fruits对象apple
Fruits apple = new Fruits("80%","60%","30%");
//输出Fruits类的信息
System.out.println("Fruits类的信息" + apple.toString());
//调用appleshowFruit方法输出对象属性
apple. showFruit();
}
}在FruitManager类main方法中,创建apple对象后,输出apple对象的自身信息和对象属性。执行FruitManager,输出结果如下图所示:

图2 FruitManager输出结果
boolean equals(Object obj) 方法
Object类的equals方法主要用于判断传入的对象obj与自身是否一致,即判断自身与obj是否是同一个对象。equals默认的判断方法是判断两个对象的内存地址是否一致,如果一致则返回true,否则返回false。在实际编程中,有时需要判断两个对象的内容是否相等,此时就需要重写Object类的equals方法,替换掉默认的判断方法。
例如:
public class Fruits {
//汁液含量
private String water;
//糖分含量
private String sugar;
//芳香度
private String fragrance;
Fruits(String inwater,String sugar,String fragrance)
{
this.water = inwater;
this.sugar = sugar;
this.fragrance = fragrance;
}
// 重写equal方法
@Override
public boolean equals(Object obj) {
boolean bEquals = true;
Fruits tempFruits = (Fruits)obj;
if( !this.water.equals(tempFruits.getWater()) )
bEquals = false;
if( !this.sugar.equals(tempFruits.getSugar()) )
bEquals = false;
if( !this.fragrance.equals(tempFruits.getFragrance()) )
bEquals = false;
return bEquals;
}
/**
* Function showFruit
* Description: 输出水果的 水分含量、糖分含量、芳香度
* input: 无输入参数
* return: 无返回
*/
public void showFruit()
{
System.out.println("水分含量:" + getWater());
System.out.println("糖分含量:" + getSugar());
System.out.println("芳香度:" + getFragrance());
}
public String getWater() {
return water;
}
public void setWater(String water) {
this.water = water;
}
public String getSugar() {
return sugar;
}
public void setSugar(String sugar) {
this.sugar = sugar;
}
public String getFragrance() {
return fragrance;
}
public void setFragrance(String fragrance) {
this.fragrance = fragrance;
}
}Fruits类重写了Object类的equals方法,替换默认的equals方法,用于判断两个对象内容是否相等,而不是内存地址是否相等。
Object clone()方法
Object类的clone方法主要用于快速创建一个已有对象的副本,将原有对象的数据导入到新创建的对象中。
使用clone方法注意事项:
(1)为了使用clone方法,类必须实现java.lang.Cloneable接口重写protected方法clone,如果没有实现Clonebale接口会抛出CloneNotSupportedException。
(2)clone方法创建新对象后,将原对象的内容拷贝复制到新对象中。假如原对象属性包含引用类型,那么原始对象和新创建的对象都将指向相同的引用内容,这点需特别注意,因为引用类型数据的任何改变将反应到他们所引用的共同内容上,这将会产生不可预知的结果。
第三小节 父类与子类之间的类型转换和动态绑定
1、类型转换
前面了解了父类和子类的关系,子类是对父类的扩展,父类是子类的抽象。这里面就有一个问题,如果在程序中声明了一个父类变量,该父类变量能不能指向其子类对象呢?或者声明了一个子类变量,该子类变量能不能指向其父类对象呢?答案是肯定的。
例如下面的代码:
public class Product {
//产品名称
String name;
//产品价格
double price;
//产品作者
String author;
//产品摘要
String summary;
//构造方法
public Product()
{
}
}
public class EBook extends Product {
//电子图书的格式
String formation;
//电子图书的文件大小
long filesize;
public EBook() {
super();
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
//父类的变量指向其子类的对象
Product temp = new EBook();
//输出Fruits类的信息
}代码中Product类是父类,EBook是Product类的子类,在main方法中,声明了Product类变量,该变量指向实例化的子类对象,也就是父类变量指向其子类对象。因此,在继承关系中,子类的对象可以直接当作父类的对象使用,称为“向上转型”。
注意的是,子类对象当作父类对象使用时,不能访问其子类对象新增加的成员变量和方法。例如,在上面的例子代码中,父类变量temp指向其子类对象,因此,不能访问EBook类的formation和filesize属性。
既然父类变量可以指向子类对象。那么,父类对象也可以转换为子类对象,该转换称为“向下转型”,“向下转型”需要使用强制转换。将父类对象强制转换为子类对象时,必须确保该父类对象是子类的一个实例。
例如下面的代码:
public static void main(String[] args) {
// TODO Auto-generated method stub
//父类的变量指向其子类的对象
Product temp = new EBook();
// temp是EBook的实例,可以强制转换为子类对象
EBook ebook = (EBook) temp;
//父类的paper变量指向实例化的PaperBook对象
Product paper = new PaperBook();
//强制paper对象转换为EBook时,会发生异常
EBook ebook = (EBook) paper;
}语句“EBook ebook = (EBook) paper”在运行时,会抛出一个运行异常ClassCastException,表示类转换异常。因此,在进行父类向子类的转换时,一个好的习惯是通过instanceof运算符来判断父类变量是否是该子类的一个实例:
EBook book = null; if( paper instanceof EBook ) book = (EBook) paper;
2、动态绑定
动态绑定是Java多态的一种表现形式,指的是代码在执行期间而不是编译时判断所引用对象的实际类型,并根据其实际的类型,调用其相应的方法。
举个例子,家用电器包含电视、电脑、冰箱等电器设备,我们可以把家用电器抽象为类(Equipment),电视(ColorTv)、电脑(Computer)、冰箱(Icebox)作为家用电器类的子类。家用电器类提供support()方法,表示家用电器已通电,但通电后,电视、电脑、冰箱有不同的表现形式,可以用重写父类support()方法的技术来解决这个问题,这就是java 的多态和动态绑定。
类对象的动态绑定需要满足如下条件:
(1)类与类之间有继承关系;
(2)子类重写了父类的方法;
(3)要有父类变量指向子类对象;
(4)要有父类变量调用重写的方法。
例如,下面的代码首先定义了一个电器类,然后分别定义它的两个子类:电视机和电脑,再定义Discriminate类,Discriminate类提供judge方法,该方法传入Equipment类实例,并调用实例的support方法。代码如下:
//电器类
public class Equipment {
public void support()
{
System.out.println("通电功能!");
}
}
//电视机类
class ColorTv extends Equipment
{
public void support()
{
System.out.println("电视机通电,看电视");
}
}
class Computer extends Equipment
{
public void support()
{
System.out.println("电脑通电,编写程序");
}
}
class Discriminate
{
public void judge(Equipment equi)
{
equi.support();
}
}
public class TestEquipment
{
public static void main(String[] args) {
// TODO Auto-generated method stub
Discriminate dis = new Discriminate();
//多态调用
dis.judge(new Computer() );
//多态调用
dis.judge(new ColorTv() );
}
}程序输出结果为:
电脑通电,编写程序
电视机通电,看电视
动态绑定的优点是当需要扩展程序功能,添加新的类时,不需要修改原来已经存在的类结构,只需要增加新的类即可。例如,如果要增加冰箱类,只需要重写电器类的support方法即可,代码如下:
class Icebox extends Equipment
{
public void support()
{
System.out.println("冰箱通电,可以冰冻事物");
}
}在上面的例子代码中,程序又增加了Icebox类,Icebox类重写了父类的support()方法。重修修改TestEquipment代码如下:
public class TestEquipment
{
public static void main(String[] args) {
// TODO Auto-generated method stub
Discriminate dis = new Discriminate();
//多态调用计算机
dis.judge(new Computer() );
//多态调用电视
dis.judge(new ColorTv() );
//多态调用冰箱
dis.judge(new Icebox () );
}
}由上面的代码可以看出,动态绑定可以很容易扩展程序功能,而无需改动原有的代码。
文章小结
1、多态是面向对象编程的一大特征,利用多态特征编程,可以让应用程序具有良好的扩展性。通过子类对父类方法的重写和类方法的重载,可以在不改变原有代码的情况下扩展程序的功能。
2、 Object 是类层次结构的根类,每个类都使用 Object 作为超类,所有Java类都继承Object类,一般的类都需要覆盖Object中的三个方法,toString(),hashCode(),equals()。
3、多态和动态绑定实际上是让“做什么”和“怎么做”分离了,本文中的家用电器类(Equipment)只负责基础通电功能(support),而(support)的具体实现由电视(ColorTv)、电脑(Computer)、冰箱(Icebox)子类负责。因此,只要电器类通电,则其电视、电脑、冰箱就开始运行,至于运行的内容,则有电视、电脑、冰箱内部负责。
思考与练习
1、应用eclipse开发工具建立ProductManage项目,实现文中的Product类和EBook类,并实现例1和例2的内容,Product类和EBook类的属性可以参考『深入了解面向对象的继承思想』一文。
2、请编程实现文中FruitManager类和Fruits类,并运行程序,查看运行结果。
3、应用eclipse开发工具建立Equipment项目,实现文中的代码案例,体会动态绑定的用法。