Java编程思想之多态(一)

本文探讨Java中多态的概念,包括向上转型、方法绑定、构造器调用顺序及清理,通过实例说明如何利用多态机制提高代码的可扩展性和维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.再论向上转型

对象既可以作为他自己本身的类型使用,也可以作为他的基类类型使用,这种把对某个对象的引用视为对其基类类型的引用的做法被称作向上转型(因为在继承树的画法中基类是放置在上方的)。

下面是一个有关乐器的例子:

(1)建立一个乐符的枚举类

public enum Note{
MIDDLE_C, C_SHARO, B_FLAT;
}

(2)建立一个乐器类Instrument,通过play()方法来演奏乐符

class Instrument {
public void play(Note n){
System.out.println("Instrument.play()");
}
}

(3)建立一个Wind类继承自Instrument,并且重写play()方法

public class Wind extends Instrument{
public void play(Note n){
System.out.println("Wind.play()" + n);
}
}

(4)建立一个Music类,通过一个接收Instrument引用的方法tune()来演奏。当一个Wind引用传递到tune()方法时不需要任何类型转换。因为WindInstrument继承而来,所以Instrument的接口必定存在于Wind中,从Wind向上转型到Instrument可能会“缩小”接口,但不会比Instrument的全部接口更窄。

//inheritance & upcasting
public class Music {
public static void tune(Instrument i){
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind wind = new Wind();
tune(wind);//Upcasting
}
}

1.1忘记对象类型

Music.java里面的tune为什么要故意忘掉对象类型呢?似乎让tune()方法直接接受一个Wind引用作为自己的参数会更为直观。但是会引发一个重要的问题:就需要为系统内Instrument的每种类型都编写一个新的tune()方法,这个方法是用相对类型的引用作为参数。这样行得通,但是要做好多额外大量的工作。

2.转机

tune()方法接受一个Instrument引用,但是编译器怎样才能知道这个Instrument引用指向的是Wind对象而不是BrassStringed对象呢?

2.1方法调用绑定

将一个方法调用同一个方法主体关联起来被称作绑定。若在程序执行之前进行绑定(由编译器和链接程序实现),叫做前期绑定,这是面向过程语言中默认的绑定方式。

后期绑定也叫动态绑定或运行时绑定,就是在运行时根据对象的类型进行绑定。如果一种语言想实现后期绑定,就必须拥有某种机制,以便在运行时可以判断对象的类型,从而调用恰当的方法。也就是说,编译器一直不知道对象的类型,但是方法调用机制可以找到正确的方法体,并加以调用。

Java中除了staticfinalprivate属于final)之外,其他所有的方法都属于后期绑定。

为什么要将某个方法声明为final呢?可以防止其他人覆盖该方法。更重要的一点也许是:这样做可以关闭动态绑定。或者说告诉编译器不需要对其进行动态绑定,这样,编译器就可以为final方法调用生成更有效的代码。

2.2产生正确的行为

一旦知道Java中所有代码都是通过动态绑定实现多态后,就可以编写只与基类打交道的代码了。

例如“几何形状”的例子:

192339_JZbY_1415012.png

class Shape{
public void draw(){
}
public void erase(){
}
}
 
class Circle extends Shape{
public void draw(){
System.out.println("Circle.draw()");
}
public void erase(){
System.out.println("Circle.erase()");
}
}
 
class Triangle extends Shape{
public void draw(){
System.out.println("Triangle.draw()");
}
public void erase(){
System.out.println("Triangle.erase()");
}
}
 
class Square extends Shape{
public void draw(){
System.out.println("Shape.draw()");
}
public void erase(){
System.out.println("Shape.erase()");
}
}
 
class RandomShapeGenerator{
Random rand = new Random();
public Shape next(){
switch(rand.nextInt(3)){
default:
case 0: return new Circle();
case 1: return new Triangle();
case 2: return new Square();
}
}
}
 
public class TestPolymorph {
private static RandomShapeGenerator gen = new RandomShapeGenerator();
public static void main(String[] args) {
int count = 9;
Shape[] shape = new Shape[count];
for(int i=0; i<9;i++){
shape[i] = gen.next();
}
for(Shape sha: shape)
sha.draw();
}
}

运行结果为:

Circle.draw()
Triangle.draw()
Shape.draw()
Shape.draw()
Triangle.draw()
Circle.draw()
Circle.draw()
Circle.draw()
Circle.draw()

RandomShapeGenerator是一种“工厂”,在每次调用next()方法时,它可以为随机选择的Shape对象产生一个引用。

2.3可扩展性

由于有个多态机制,可以根据自己的需要对系统添加任意多的新类型,而不需要更改原有的方法。在一个设计良好的OOP程序中,大多数或者所有方法都会遵循tune()的模型,而且只与基类接口通信。这样的程序是可扩展的,因为可以从通用的基类继承出新的数据类型,从而新添加一些功能。那些操作基类接口的方法不需要任何改动就可以应用与新类。

2.4缺陷:“覆盖”私有方法

由于private方法被认为是final的,而且对导出类是屏蔽的。因此,子类中与父类中同名的私有方法被认为是一个全新的方法。既然基类中的私有方法在子类中是不可见的,因此甚至也不能被重载。

结论就是:只有非private方法才可以被覆盖;但是还需要密切注意覆盖private方法的现象,这时虽然编译器不会报错,但是也不会按照我们所期望的来执行。确切的说,在导出类中,对于积累中的private方法,最好采用不同的名字。

2.5缺陷:域与静态方法

一旦了解了多态机制,可能开始认为所有的事物都可以多态地发生。然而,只有普通的方法调用可以是多态的。例如,如果直接访问某个域,这个访问就将在编译期进行解析。

public class FieldAccess {
 
public static void main(String[] args) {
Super sup = new Sub();
System.out.println("sup.field = " + sup.field);
System.out.println("sup.getField() = " + sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = " + sub.field);
System.out.println("sub.getField = " + sub.getField());
}
 
}
 
class Super{
public int field = 0;
public int getField(){
return field;
}
}
 
class Sub extends Super{
public int field = 1;
public int getField(){
return field;
}
}
运行结果为:
sup.field = 0
sup.getField() = 1
sub.field = 1
sub.getField = 1

Sub实际上包含两个称为field的域:它自己的和它从Super处得到的,在引用Sub中的field时所产生的默认域并非Super版本的field域。

如果某个方法是静态的,它的行为就不具有多态性。

public class StaticPolymorphism {
 
public static void main(String[] args) {
StaticSuper sup = new StaticSub();
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
 
}
 
class StaticSuper{
public static String staticGet(){
return "Base staticGet()";
}
public String dynamicGet(){
return "Base dynamicGet()";
}
}
 
class StaticSub extends StaticSuper{
public static String staticGet(){
return "Derived staticGet()";
}
public String dynamicGet(){
return "Derived dynamicGet()";
}
}
运行结果为:
Base staticGet()
Derived dynamicGet()

静态方法是与类,而并非与单个的对象相关联的。

2.构造器和多态

构造器不同于其他类的方法,构造器不具有多态性(它们实际上是static方法,只不过该static的声明是隐式的)。

3.1构造器的调用顺序

基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接,以使每个构造器都能得到调用。因为构造器具有一项特殊任务:检查对象是否被正确的构造。导出类只能访问它自己的成员,不能访问基类的成员(通常是private的)。只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。因此,必须令所有构造器都得到调用,否则就不能正确的构造完整的对象。

下面的例子展示组合,继承以及多态在构建顺序上的作用:

public class Sandwich extends PortableLunch{
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
public Sandwich(){
System.out.println("Sandwich()");
}
public static void main(String[] args) {
new Sandwich();
}
}
 
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()");
}
}
运行结果为:
Meal()
Lunch
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()

可见调用构造器要遵循以下顺序:

1) 调用基类构造器。这个步骤会不断地反复递归下去,直到最低层的导出类。

2) 按声明顺序调用成员的初始化方法。

3) 调用导出类构造器的主体。

若遵循这一规则,那么就能保证所有积累成员以及当前对象的成员对象都被初始化了。

3.2继承与清理

由于继承的缘故,如果有其他作为垃圾回收一部分的特殊清理动作,就必须在导出类中覆盖dispose()方法,务必记住调用基类版的dispose()方法,否则,基类的清理动作就不会发生。

public class Frog extends Amphibian{
private Characteristic p = new Characteristic("Croaks");
private Description t = new Description("Eats Bugs");
public Frog(){
System.out.println("Frog()");
}
protected void dispose(){
System.out.println("Frog dispose");
t.dispose();
p.dispose();
super.dispose();
}
public static void main(String[] args) {
Frog frog = new Frog();
System.out.println("Bye!");
frog.dispose();
}
 
}
 
class Characteristic{
private String s;
Characteristic(String s){
this.s = s;
System.out.println("Creating Characteristic " + s);
}
protected void dispose(){
System.out.println("disposing Characteristic " + s);
}
}
 
class Description{
private String s;
Description (String s){
this.s = s;
System.out.println("Creating Description " + s);
}
protected void dispose(){
System.out.println("disposing Description " + s);
}
}
 
class LivingCreature{
private Characteristic p = new Characteristic("is alive");
protected Description t = new Description("Basic Living Creature");
LivingCreature(){
System.out.println("LivingCreature()");
}
protected void dispose(){
System.out.println("LivingCreature dispose");
t.dispose();
p.dispose();
}
}
 
class Animal extends LivingCreature{
private Characteristic p = new Characteristic("has heart");
private Description t = new Description("Animal not Vegetable");
Animal(){
System.out.println("Animal()");
}
protected void dispose(){
System.out.println("Animal dispose");
t.dispose();
p.dispose();
super.dispose();
}
}
 
class Amphibian extends Animal{
private Characteristic p = new Characteristic("can live in water");
private Description t = new Description("Both water and lang");
Amphibian(){
System.out.println("Amphibian()");
}
protected void dispose(){
System.out.println("Smphibian dispose");
t.dispose();
p.dispose();
super.dispose();
}
}
运行结果为:
Creating Characteristic is alive
Creating Description Basic Living Creature
LivingCreature()
Creating Characteristic has heart
Creating Description Animal not Vegetable
Animal()
Creating Characteristic can live in water
Creating Description Both water and lang
Amphibian()
Creating Characteristic Croaks
Creating Description Eats Bugs
Frog()
Bye!
Frog dispose
disposing Description Eats Bugs
disposing Characteristic Croaks
Smphibian dispose
disposing Description Both water and lang
disposing Characteristic can live in water
Animal dispose
disposing Description Animal not Vegetable
disposing Characteristic has heart
LivingCreature dispose
disposing Description Basic Living Creature
disposing Characteristic is alive

如果某个子对象依赖于其他对象,销毁的顺序应该和初始化顺序相反。

上例中Frog对象创建了它自己的成员对象,并且知道它们应该活多久,因此Frog对象知道何时调用dispose()去释放其成员对象。然而,如果这些成员对象中存在于其他一个或多个对象共享的情况,就必须使用引用计数来跟踪仍旧访问着共享对象的对象数量了。

public class ReferenceCounting {
 
public static void main(String[] args) {
Shared shared = new Shared();
Composing[] composing = {
new Composing(shared),
new Composing(shared),
new Composing(shared),
new Composing(shared),
new Composing(shared),
};
for(Composing c:composing)
c.dispose();
}
 
}
 
class Shared{
private int refcount = 0;
private static long counter = 0;
private final long id = counter++;
public Shared(){
System.out.println("Creating " + this);
}
public void addRed(){
refcount++;
}
protected void dispose(){
if(--refcount == 0){
System.out.println("Disposing " + this);
}
}
public String toString(){
return "Shared " + id;
}
}
 
class Composing{
private Shared shared;
private static long counter = 0;
private final long id = counter++;
public Composing(Shared shared){
System.out.println("Creating " + this);
this.shared = shared;
this.shared.addRed();
}
protected void dispose(){
System.out.println("disposing " + this);
shared.dispose();
}
public String toString(){
return "Composing " + id;
}
}
运行结果为:
Creating Shared 0
Creating Composing 0
Creating Composing 1
Creating Composing 2
Creating Composing 3
Creating Composing 4
disposing Composing 0
disposing Composing 1
disposing Composing 2
disposing Composing 3
disposing Composing 4
Disposing Shared 0


转载于:https://my.oschina.net/u/1415012/blog/306100

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值