一、多态的概念
——是指不同类型的对象可以响应相同的消息
——从相同的基类派生出来的多个类型可被当作同一种类型对待,可对这些不同的类型进行同样的处理,由于多态性,这些不同派生类对象响应同一方法时的行为是有所差别的
——例如
所有的Object类的对象都响应toString()方法
所有的BankAccount类的对象都响应deposit()方法
二、多态的目的
——所有的对象都可被塑型为相同的类型,响应相同的消息
——使代码变得简单且容易理解
——使程序具有很好的“扩展性”
三、绑定的概念
——指将一个方法调用同一个方法主体连接到一起
——根据绑定时期的不同,可分为
①早期绑定
程序运行之前执行绑定
②晚期绑定
也叫作“动态绑定”或“运行期绑定
基于对象的类别,在程序运行时执行绑定
class Shape { //父类,图形类
void draw() {}
void erase() {}
}
class Circle extends Shape { 子类,圆类
void draw()
{ System.out.println("Circle.draw()"); }
void erase()
{ System.out.println("Circle.erase()"); }
}
class Square extends Shape { //子类,正方形类
void draw()
{ System.out.println("Square.draw()"); }
void erase()
{ System.out.println("Square.erase()"); }
}
class Triangle extends Shape { //子类,三角形类
void draw()
{ System.out.println("Triangle.draw()"); }
void erase()
{ System.out.println("Triangle.erase()"); }
}
public class BindingTester{ //测试类,对动态绑定进行测试
public static void main(String[] args) {
Shape[] s = new Shape[9];
int n;
for(int i = 0; i < s.length; i++) {
n = (int)(Math.random() * 3);
switch(n) {
case 0: s[i] = new Circle(); break;
case 1: s[i] = new Square(); break;
case 2: s[i] = new Triangle();
}
}
for(int i = 0; i < s.length; i++) s[i].draw();
}
}
运行结果
Square.draw()
Triangle.draw()
Circle.draw()
Triangle.draw()
Triangle.draw()
Circle.draw()
Square.draw()
Circle.draw()
Triangle.draw()
说明
编译时无法知道s数组元素的具体类型,运行时才能确定类型,所以是动态绑定;
在主方法的循环体中,每次随机生成指向一个Circle、Square或者Triangle的引用;
四、多态的应用
——技术基础
①向上塑型技术:一个父类的引用变量可以指向不同的子类对象
②动态绑定技术:运行时根据父类引用变量所指对象的实际类型执行相应的子类方法,从而实现多态性
五、构造方法与多态
——构造方法与其他方法是有区别的
构造方法并不具有多态性,但仍然非常有必要理解构造方法如何在复杂的分级结构中随同多态性一同使用的情况
——构造方法的调用顺序
①调用基类的构造方法。这个步骤会不断重复下去,首先得到构建的是分级结构的根部,然后是下一个派生类,等等。直到抵达最深一层的派生类
②按声明顺序调用成员初始化模块
③调用派生构造方法
class Meal { //饭类
Meal() { System.out.println("Meal()"); }
}
class Bread { //面包类
Bread() { System.out.println("Bread()"); }
}
class Cheese { //奶酪类
Cheese() { System.out.println("Cheese()"); }
}
class Lettuce { //莴苣类
Lettuce() { System.out.println("Lettuce()"); }
}
class Lunch extends Meal { //午餐类继承自饭类
Lunch() {System.out.println("Lunch()");}
}
class PortableLunch extends Lunch {
PortableLunch() { System.out.println("PortableLunch()"); }
}
public class Sandwich extends PortableLunch {
Bread b = new Bread();
Cheese c = new Cheese();
Lettuce l = new Lettuce();
Sandwich(){System.out.println("Sandwich()");}
public static void main(String[] args) { new Sandwich(); }
}
输出结果
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()
说明
当我们在构造派生类的时候,必须能假定基类的所有成员都是有效的。在构造方法内部,必须保证使用的所有成员都已初始化。因此唯一的办法就是首先调用基类构造方法,然后在进入派生类构造方法之前,初始化所有能够访问的成员
六、构造方法中的多态方法
——在构造方法内调用准备构造的那个对象的动态绑定方法
①会调用位于派生类里的一个方法
②被调用方法要操纵的成员可能尚未得到正确的初始化
③可能造成一些难于发现的程序错误
abstract class Glyph {
abstract void draw();
Glyph() {
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph {
int radius = 1;
RoundGlyph(int r) {
radius = r;
System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);
}
void draw() {
System.out.println("RoundGlyph.draw(), radius = " + radius);
}
}
public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
}
运行结果
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
说明
在Glyph中,draw()方法是抽象方法,在子类RoundGlyph中对此方法进行了覆盖。Glyph的构造方法调用了这个方法
从运行的结果可以看到:当Glyph的构造方法调用draw()时,radius的值甚至不是默认的初始值1,而是0
定义构造方法的注意事项
——用尽可能少的动作把对象的状态设置好
——如果可以避免,不要调用任何方法
——在构造方法内唯一能够安全调用的是在基类中具有final属性的那些方法(也适用于private方法,它们自动具有final属性)。这些方法不能被覆盖,所以不会出现上述潜在的问题