重温经典《Thinking in java》第四版之第八章 多态(四十四)

简介: 重温经典《Thinking in java》第四版之第八章 多态(四十四)

8.3 构造器和多态

通常,构造器不同于其他种类的方法。涉及到多态时仍是如此。尽管构造器并不具有多态性(它们实际上是static方法,只不过该static声明是隐式的),但改善非常有必要理解构造器怎样通过多态在复杂的层次结构中运作,这一理解将有助于大家避免一些令人不快的困扰。

 

8.3.1 构造器的调用顺序

基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接,以使每个基类的构造器都能得到调用。这样做是有意义的,因为构造器具有一项特殊任务:检查对象是否被正确构造。导出类只能访问它自己的成员,不能访问基类的成员。只有基类构造器才具有恰当的知识和权限来对自己的元素进行初始化。因此必须令所有构造器得到调用,否则就不可能正确构造完整对象。这正是编译器为什么要强制每个导出类部分都必须调用构造器的原因。在导出类的构造器主体中,如果没有明确指定调用某个基类构造器,它就会“默默”地调用默认构造器。如果不存在默认构造器,编译器就会报错(若某个类没有构造器,编译器会自动合成出一个默认构造器)。

让我们看一个例子,它展示组合,继承以及多态在构建顺序上的作用:

//: polymorphism/Sandwich.java // Order of constructor calls. packagepolymorphism; 
importstaticnet.mindview.util.Print.*; 
classMeal { 
Meal() { print("Meal()"); } 
} 
classBread { 
Bread() { print("Bread()"); } 
} 
classCheese { 
Cheese() { print("Cheese()"); } 
} 
classLettuce { 
Lettuce() { print("Lettuce()"); } 
} 
classLunchextendsMeal { 
Lunch() { print("Lunch()"); } 
} 
classPortableLunchextendsLunch { 
PortableLunch() { print("PortableLunch()");} 
} 
publicclassSandwichextendsPortableLunch { 
privateBreadb=newBread(); 
privateCheesec=newCheese(); 
privateLettucel=newLettuce(); 
publicSandwich() { print("Sandwich()"); } 
publicstaticvoidmain(String[] args) { 
newSandwich(); 
    } 
}

/* Output:

Meal()

Lunch()

PortableLunch()

Bread()

Cheese()

Lettuce()

Sandwich()

*///:~

输出结果表明了这一复杂对象调用构造器要遵照下面的顺序:

1、调用基类构造器。这个步骤会不断地反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,等等,直到最底层的导出类。

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

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

构造器的调用顺序很重要。当进行继承时,我们已经知道基类的一切,并且可以访问基类中任何声明为public和protected的成员。这意味着导出类中,必须假定基类的所有成员都是有效的。一种标准方法是,构造动作已经发生,那么对象所有部分的全体成员都会得到构建。然而,在构造器内部,我们必须确保所要使用的成员都已经构造完毕。为确保这一目的,唯一的办法就是首先调用基类构造器。那么在进入导出类构造器时,在基类中可供我们访问的成员都已得到初始化。此外,知道构造器中的所有成员都有效是因为,当成员对象在类内进行定义的时候,只要有可能,就应该对他们进行初始化。若遵循这一规则,那么就能保证所有基类成员以及当前对象的成员对象都被初始化了。

 

8.3.2 继承与清理

通过组合和继承方法来构建新类时,永远不必担心对象的清理问题,子对象通常都会留给垃圾回收器进行处理。如果确实遇到清理问题,那么必须用心为新类创建dispose()方法。并且由于继承的缘故,如果我们有其他作为垃圾回收一部分的特殊清理动作,就必须在导出类中覆盖dispose()方法。当覆盖被继承类的dispose()方法时,务必记住调用基类版本dispose()方法;否则,基类的清理动作就不会发生。

 

//: polymorphism/Frog.java // Cleanup and inheritance. packagepolymorphism; 
importstaticnet.mindview.util.Print.*; 
classCharacteristic { 
privateStrings; 
Characteristic(Strings) { 
this.s=s; 
print("Creating Characteristic "+s); 
    } 
protectedvoiddispose() { 
print("disposing Characteristic "+s); 
    } 
} 
classDescription { 
privateStrings; 
Description(Strings) { 
this.s=s; 
print("Creating Description "+s); 
    } 
protectedvoiddispose() { 
print("disposing Description "+s); 
    } 
} 
classLivingCreature { 
privateCharacteristicp=newCharacteristic("is alive"); 
privateDescriptiont=newDescription("Basic Living Creature"); 
LivingCreature() { 
print("LivingCreature()"); 
    } 
protectedvoiddispose() { 
print("LivingCreature dispose"); 
t.dispose(); 
p.dispose(); 
    } 
} 
classAnimalextendsLivingCreature { 
privateCharacteristicp=newCharacteristic("has heart"); 
privateDescriptiont=newDescription("Animal not Vegetable"); 
Animal() { print("Animal()"); } 
protectedvoiddispose() { 
print("Animal dispose"); 
t.dispose(); 
p.dispose(); 
super.dispose(); 
    } 
} 
classAmphibianextendsAnimal { 
privateCharacteristicp=newCharacteristic("can live in water"); 
privateDescriptiont=newDescription("Both water and land"); 
Amphibian() { 
print("Amphibian()"); 
    } 
protectedvoiddispose() { 
print("Amphibian dispose"); 
t.dispose(); 
p.dispose(); 
super.dispose(); 
    } 
} 
publicclassFrogextendsAmphibian { 
privateCharacteristicp=newCharacteristic("Croaks"); 
privateDescriptiont=newDescription("Eats Bugs"); 
publicFrog() { print("Frog()"); } 
protectedvoiddispose() { 
print("Frog dispose"); 
t.dispose(); 
p.dispose(); 
super.dispose(); 
    } 
publicstaticvoidmain(String[] args) { 
Frogfrog=newFrog(); 
print("Bye!"); 
frog.dispose(); 
    } 
}

/* Output:

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 land

Amphibian()

Creating Characteristic Croaks

Creating Description Eats Bugs

Frog()

Bye!

Frog dispose

disposing Description Eats Bugs

disposing Characteristic Croaks

Amphibian dispose

disposing Description Both water and land

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

*///:~

层次结构中的每个类都包含Characteristic Description 这两种类型的成员对象,并且他们也必须被销毁。所以万一某个子对象要依赖于其他对象,销毁的顺序应该和初始化顺序相反。

 

8.3.3 构造器内部的多态方法的行为

构造器调用的层次结构带来一个有趣的两难问题。如果在一个构造器的内部调用正在构造的方法的某个动态绑定方法,那会发生什么情况呢?

在一般的方法内部,动态绑定的调用实在运行时才决定的,因为对象无法知道它属于方法所在的那个类还是属于那个类的导出类。

如果要调用构造器内部的一个动态绑定方法,就要用到那个方法的被覆盖后的定义。然后这个调用的效果可能相当难以预料,因为被覆盖的方法在对象被完全构造之前就会被调用。这可能会造成一些难于发现的隐藏错误。

从概念上讲,构造器的工作实际上是创建对象。在任何构造器内部,整个对象可能只是部分形成——我们只知道基类对象已经完成初始化。如果构造器只是在构建对象过程中的一个步骤,并且该对象所属的类是从这个构造器所属的类导出的,那么导出部分在当前构造器正在被调用的时刻仍旧是没有被初始化的。然而,一个动态绑定的方法调用却会向外深入到继承层次的结构内部,它可以调用导出类里的方法。如果我们是在构造器内部这样做,那么就可能会调用某个方法,而这个方法所操纵的成员可能还未进行初始化——这肯定会招致灾难。

通过下面的例子,我们会看到问题所在:

// Constructors and polymorphism // don’t produce what you might expect. importstaticnet.mindview.util.Print.*; 
classGlyph { 
voiddraw() { print("Glyph.draw()"); } 
Glyph() { 
print("Glyph() before draw()"); 
draw(); 
print("Glyph() after draw()"); 
    } 
} 
classRoundGlyphextendsGlyph { 
privateintradius=1; 
RoundGlyph(intr) { 
radius=r; 
print("RoundGlyph.RoundGlyph(), radius = "+radius); 
    } 
voiddraw() { 
print("RoundGlyph.draw(), radius = "+radius); 
    } 
} 
publicclassPolyConstructors { 
publicstaticvoidmain(String[] args) { 
newRoundGlyph(5); 
    } 
}

/* Output:

Glyph() before draw()

RoundGlyph.draw(), radius = 0

Glyph() after draw()

RoundGlyph.RoundGlyph(), radius = 5

*///:~

从程序输出可以看到,我们可能在构造器中调用其他多态方法时,可能由于赋值问题,导致结果令我们不是很满意。因此,编写构造器时有一条有效的准则:用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法。

目录
相关文章
|
4月前
|
算法 Java 程序员
在Java的编程世界里,多态不仅仅是一种代码层面的技术,它是思想的碰撞,是程序员对现实世界复杂性的抽象映射,是对软件设计哲学的深刻领悟。
在Java的编程世界里,多态不仅仅是一种代码层面的技术,它是思想的碰撞,是程序员对现实世界复杂性的抽象映射,是对软件设计哲学的深刻领悟。
74 9
|
2月前
|
存储 Java 测试技术
Java零基础-多态详解
【10月更文挑战第10天】Java零基础教学篇,手把手实践教学!
33 4
|
2月前
|
Java 编译器 程序员
Java多态背后的秘密:动态绑定如何工作?
本文介绍了Java中多态的实现原理,通过动态绑定和虚拟方法表,使得父类引用可以调用子类的方法,增强了代码的灵活性和可维护性。文中通过具体示例详细解析了多态的工作机制。
66 4
|
3月前
|
Java 编译器
封装,继承,多态【Java面向对象知识回顾①】
本文回顾了Java面向对象编程的三大特性:封装、继承和多态。封装通过将数据和方法结合在类中并隐藏实现细节来保护对象状态,继承允许新类扩展现有类的功能,而多态则允许对象在不同情况下表现出不同的行为,这些特性共同提高了代码的复用性、扩展性和灵活性。
封装,继承,多态【Java面向对象知识回顾①】
|
2月前
|
Java
java继承和多态详解
java继承和多态详解
52 5
|
2月前
|
存储 Java 测试技术
Java零基础-多态详解
【10月更文挑战第1天】Java零基础教学篇,手把手实践教学!
30 1
|
3月前
|
Java 编译器
Java——类与对象(继承和多态)
本文介绍了面向对象编程中的继承概念,包括如何避免重复代码、构造方法的调用规则、成员变量的访问以及权限修饰符的使用。文中详细解释了继承与组合的区别,并探讨了多态的概念,包括向上转型、向下转型和方法的重写。此外,还讨论了静态绑定和动态绑定的区别,以及多态带来的优势和弊端。
80 9
Java——类与对象(继承和多态)
|
2月前
|
安全 Java 编译器
【一步一步了解Java系列】:重磅多态
【一步一步了解Java系列】:重磅多态
24 3
|
3月前
|
Java
Java 多态趣解
在一个阳光明媚的午后,森林中的动物们举办了一场别开生面的音乐会。它们组成了一支乐队,每种动物都有独特的演奏方式。通过多态的魅力,狗、猫和青蛙分别展示了“汪汪”、“喵喵”和“呱呱”的叫声,赢得了观众的阵阵掌声。熊指挥着整个演出,每次调用 `perform()` 方法都能根据不同的动物对象唤起对应的 `makeSound()` 方法,展现了 Java 多态性的强大功能,让整场音乐会既有趣又充满表现力。
31 1
|
4月前
|
安全 Java 编译器