|
![]() |
名片设计 CorelDRAW Illustrator AuotoCAD Painter 其他软件 Photoshop Fireworks Flash |
|
Polymorphism 多态(polymorphism)也被称为动态绑定『dynamic binding』、后绑定『late binding』或运行时绑定『run-time bingding』。 它的意思是要在程序运行的时候,根据对象的类型来决定该绑定哪个方式。多态性是继数据抽象和继续之后的,面向对象的编程语言的第三个基本特性。 绑定(binding): 将方式的调用连到方式本身 前绑定(early binding): 当绑定发生在程序运行之前时(就是由编译器或连接器负责) 后绑定(late binding): 在程序运行的时候,根据对象的类型来决定该绑定哪个方式。 “封装”(encapsulation)通过将数据的特征(characteristics)与行为(behavior)结合在一起,创建了一种新的数据类型。 “隐藏实现”(Implementation hiding)通过将细节设成private,完成了接口与实现的分离。 “多态性”是站在“class”的角度来处理这种逻辑上的分离的。 -------------------------------------------------------------------------------- Shape s = new Circle(); //upcasting: 将Circle对象upcast为Shape类型 s.draw(); Upcast(上传)就是把对象的reference 当作基类的reference 来用.(注:java内部是知道对象属于什么类型的) 因为Derived class is a type of base class, 所以基类的reference (Shape s)能接受派生类(circle)的对象 upcast以后,基类reference 调用的都是基类自己的method (late binding), 除非这个method是late-bound的,也就是派生类覆写(override)了这个method, 才会根据对象类型选择相应的method(多态性). 以上面的代码为例:s是Shape类型的reference, 除非draw()是一个动态绑定的method(派生类circle覆写了这个draw()), s.draw()才会调用cicle的draw(), 否则调用的都是基类Shape自己的method private和final的method都会采用early-binding, 因为他们是不能被override的。(注:private 方式自动就是final 的) 建议别用基类的private method的名字去命名派生类的method。因为这样会让人误以为会override这个method, 实际上private自动就是final的,不能被override。 -------------------------------------------------------------------------------- override(覆写) 表示在派生类里写一个"同样"的method。 就是重新写一遍这个method (注:1."同样"表示:同名且参数列表和返回值也一样。 2.method必须是非final, 非private的(private 方式自动就是final 的) ) overload:除了"同样"的method(即override) 以外的同名method. -------------------------------------------------------------------------------- 当你想要通过一个公共的接口来操控一组类的时候,就可以使用抽象类了。通过动态绑定机制,那些符合方式特征的派生类方式将会得到调用。 abstract class {} 抽象类的方式可以使abstract的,也可以是非abstract。派生类继续了一个abstract类,那他要么实现所有的abstract的method,要么把自己也变成abstract的。 -------------------------------------------------------------------------------- //: c07:PolyConstructors.java // Constructors and polymorphism // don\\\'t produce what you might expect. import com.bruceeckel.simpletest.*; abstract class Glyph { abstract void draw(); Glyph() { System.out.println("Glyph() before draw()"); draw();//假如你new一个派生类对象,并且在基类构造函数里面调用了动态绑定的方式, //那么它会使用那个派生类覆写后的版本。 System.out.println("Glyph() after draw()"); } } class RoundGlyph extends Glyph { private 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 { private static Test monitor = new Test(); public static void main(String[] args) { new RoundGlyph(5); monitor.expect(new String[] { "Glyph() before draw()", "RoundGlyph.draw(), radius = 0", "Glyph() after draw()", "RoundGlyph.RoundGlyph(), radius = 5" }); } } ///:~ 假如你new一个派生类对象,并且在基类构造函数里面调用了动态绑定的方式,那么它会使用那个派生类覆写后的版本。 真正的初始化过程是这样的: 1. 在进行其它工作之前,分配给这个对象的内存会先被初始化为两进制的零。 2. 正如前面一直在所说的,先调用基类的构造函数。这时会调用被覆写的draw( )方式(是的,在调用RoundGlyph 的构造函数之前调用), 这时它发现,由于受第一步的影响,radius 的值还是零。 3. 数据成员按照它们声明的顺序进行初始化。 4. 调用派生类的构造函数的正文。 一个好的构造函数应该: 用最少的工作量把对象的状态(fields)设置好,而且要尽可能地避免去调用方式。 构造函数唯一能安全调用的方式,就是基类的final/private 方式。它们不会被覆写,因此也不会产生这种意外的行为。 -------------------------------------------------------------------------------- 一旦理解了多态性,你就会觉得所有东西应该都是继续下来的,因为多态性实在是太智慧了。但是这样做会加重设计的负担。 实际上,假如你一碰到“要用已有的类来创建新类”的情况就想到要用继续的话,事情就会毫无必要地变得复杂起来了。 较好的办法还是先考虑合成,特殊是当你不知道该继续哪个类的时候。合成并不强求你把设计搞成一个类系。 此外它还更灵活,因为使用合成的时候,你可以动态地选择成员的类型(以及它们的行为),而使用继续的话, 就得在编译时指明对象的确切类型。下面这段程序就演示了这一点: //: c07:Transmogrify.java // Dynamically changing the behavior of an object // via composition (the "State" design pattern). import com.bruceeckel.simpletest.*; abstract class Actor { public abstract void act(); } class HappyActor extends Actor { public void act() { System.out.println("HappyActor"); } } class SadActor extends Actor { public void act() { System.out.println("SadActor"); } } class Stage { private Actor actor = new HappyActor(); public void change() { actor = new SadActor(); } public void performPlay() { actor.act(); } } public class Transmogrify { private static Test monitor = new Test(); public static void main(String[] args) { Stage stage = new Stage(); stage.performPlay(); stage.change(); stage.performPlay(); monitor.expect(new String[] { "HappyActor", "SadActor" }); } } ///:~ 有一条一般准则“使用继续来表示行为的不同,而用成员数据来表示状态的不同”。上述例程同时体现这两者;两个派生类用来表示act()方式的不同, 而Stage则使用合成来表示状态的变化。在这种情况下,状态的不同会导致行为的不同。 -------------------------------------------------------------------------------- 下传(Downcast):把基类重新转回详细的派生类类型,使得reference可以调用派生类的extended接口 Java 类型传递都要经过检查!所以,尽管看上去只是用一对括号作了些普通的的类型转变,但是运行的时候,系统会对这些转变作检查, 以确保 它确实是你想要转变的类型。假如不是,你就会得到一个ClassCastException。这种运行时的类型检查被称为“运行时的类型 鉴别(run-time type identification 缩写为RTTI)”。 返回类别: 教程 上一教程: 新一代Java技术即将出现 下一教程: 关于JAVA中连接各类数据库的代码及其补充说明 您可以阅读与"java学习笔记7--Polymorphism"相关的教程: · java学习笔记7--Polymorphism · javaCC学习笔记 · Java软件开发学习笔记(二) · java学习笔记 · Java 网络编程---I/O部分学习笔记整理1 |
![]() ![]() |
快精灵印艺坊 版权所有 |
首页![]() ![]() ![]() ![]() ![]() ![]() ![]() |