`

第八章 多态

阅读更多
2013年6月17日 星期一 23时34分54秒

第八章 多态
        在面向对象的程序设计语言中,多态是继续数据抽象和继承之后的第三种基本特征。
        多态通过分离做什么和怎么做,从另一角度讲接口和实现分离开来。
        ”封装“是通过合并特征和行为来创建新的数据类型。多态方法调用允许一种类型表现出与其他相似类型之间的区别,只要他们都是从同一基类导出而来的。
        多态(也成作动态绑定,后期绑定,或运行时绑定)
8.1 再论向上转型
        对象既可以作为它自己本身的类型使用,也可作为它的基类使用。把这种对某个对象的引用视为对其基类类型的引用的做法称为向上转型。
               package chapter8;
import chapter8.Wind;
/*@name Bath.java
* @describe  8.1 再论向上转型
* @since 2013-06-18 0:36
* @author 张彪
*/
public class Music {
        public static void tune(Instrument i){
                i.play(Note.C_SHARP);
        }
       
        public static void main(String[] args){
                Wind w=new Wind();
                tune(w);   // 向上转型     此处的参数应该是Instrument,但是传入的是导出类Wind
         }
}

        8.1.1 忘记对象类型
                为什么所有人故意忘记对象的类型呢???
               
8.2 转机
        8.2.1 方法调用绑定
                将一个方法的调用同一个方法的主体关联起来被称为绑定。
                前期绑定---在程序运行前进行绑定。
                后期绑定---在运行时根据对象的类型进行绑定。
                     Java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。这意味着通常情况下,我们不必判定是否应该进行后期绑                           定----它会自动发生。
                为什么要将某个方法声明为final呢???
                R:1 防止他人覆盖该方法。2 可以有效“关闭”动态绑定,或者说告诉编译器不需要对其进行动态绑定。这样编译器就可以为final方法调用生成更有效的代码。大多数情况                           下,这样做不会对程序性能有什么改观。所以,最好根据设计来决定是否使用final。
               
        8.2.2 产生正确的行为
                具体例子为chapter8\shape\Shape.java
        8.2.3 可扩展性
                多态是一项让程序员“将改变的事物与未变的事物分离开来”的重要技术。
        8.2.4 缺陷:“覆盖”私有方法
                结论:只有非private方法才可以被覆盖。
        8.2.5 缺陷:域与静态方法
                    package chapter8.super1;
/*@name Bath.java
* @describe  8.2.5 静态方法行为不具有多态性
* @since 2013-06-19 0:25
* @author 张彪
*/
class StaticSuuper{
        public static String staticGet(){
                return "Base staticGet";
        }
        public String dynamicGet(){
                return "Base dynamicGet";
        }
}

class StaticSub extends StaticSuuper{
        public static String staticGet(){
                return "Driver staticGet";
        }
        public String dynamicGet(){
                return "Driver dynamicGet";
        }
}

public class StaticFileAccess {
        //静态方法与类,而并非与单个对象相关联
        public static void main(String[] args){
                StaticSuuper sb= new StaticSub();
                System.out.println(sb.staticGet());   //Base staticGet
                System.out.println(sb.dynamicGet());  //Driver dynamicGet
        }
}
8.3 构造器与多态
                尽管构造器并不具有多态性(它们实际上是static方法,只不过该static声明是隐式的),但还是非常有必要理解构造器怎样通过多态在复杂的层次机构中运作。
        8.3.1 构造器的调用顺序
                      基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接,以使每个基类的构造器都能得到正确的调用。
                      对象调用构造器要遵循下面的顺序:
                                1.调用基类构造器
                                2.按声明顺序调用成员的初始化方法
                                3.调用导出类构造器的主体
                               
        8.3.2 继承与清理
                对象销毁的顺序应该和初始化顺序相反,对于字段则意味着与声明的顺序相反(因为字段的初始化是按照声明的顺序进行的)。对于基类,(遵循C++中析构函数的形式),应先对导出                   类进行清理,然后才是基类。

             //如果成员对象存在一个其他一个或多个对象共享的情况,问题就变的比较复杂。
             package chapter8;
/*@name Bath.java
* @describe  8.3.2 继承与清理
* @since 2013-06-19 21:36
* @author 张彪
*/
//使用引用计数器来跟踪仍旧访问着共享对象的对象数量

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 addRef(){
                refcount++;
        }
        protected void dispose(){
                if(--refcount == 0){
                        System.out.println("dispose "+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.addRef();
        }
        protected void dispose(){
                System.out.println("disping "+this);
                shared.dispose();
        }
        public String toString(){
                return "Composing "+id;
        }
}

public class ReferenceCounting {
        public static void main(String[] args){
                Shared s=new Shared();
                Composing[] composing={new Composing(s),new Composing(s),new Composing(s),new Composing(s) };
                for(Composing c:composing){
                        c.dispose();
                }
        }
}

        8.3.3 构造器内部的多态方法的行为
                构造器调用的层级结构带来了一个有趣的两难为题。如果在一个构造器的内部调用正在构造的对象的某个动态绑定方法,会发生什么情况呢?
             package chapter8;
/*@name Bath.java
* @describe  8.3.3 构造器内部的多态行为的方法
* @since 2013-06-19 21:36
* @author 张彪
*/
class Glyph{
        void draw(){
                System.out.println("Glyph.draw() ");
        }
        Glyph(){
                System.out.println("Glyph before draw() ");
                draw();
                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 {
        public static void main(String[] args){
                new RoundGlyph(5);
        }
}


/*Glyph before draw()
RoundGlyph.draw() , radius=0
Glyph after draw()
RoundGlyph.RoundGlyph(), radius=5*/
//由输出的结果可知:此时被调用的是RoundGlyph.draw()方法,但是radius的值却是0.  why?????
初始化的实际过程:
1.在其他任何事物发生前,将分配给对象的存储空间初始化为二进制的零。
2.如前所述那也调用基类构造器。此时调用被覆盖的draw()方法(要在调用RoundGlyph构造函数之前调用),由于步骤1的缘故,    我们此时会发现radius的值为0。
3.按照声明的顺序调用成员的初始化方法。
4.调用导出类的构造器主体

注意:在编码构造器时有一条有效的准则:"用尽可能简单的方法使对象进入正常状态,如果可以的话,避免调用其他方法"。在构造器内部,唯一能够安全调用的那些方法是基类中的final方法(也适用于private方法,它们自动属于final方法),这些方法不会被覆盖,因此也就不会产生令人惊讶的问题。
8.4 协变返回类型
        Java SE5中添加了协变返回类型,它表示在导出类中的被覆盖的方法可以返回基类方法的返回类型的某种导出类型。
               package chapter8;
/*@name Bath.java
* @describe  8.4 协变返回类型
* @since 2013-06-19 22:36
* @author 张彪
*/

//Java SE5中添加了协变返回类型,它表示在导出类中的被覆盖的方法可以返回基类方法的返回类型的某种导出类型。

class Grain{
        public String toString(){return "Grain";};
}
class Wheat extends Grain{
        public String toString(){return "Wheat";};
}
class Mill{
        Grain process(){return new Grain();};
}
class WheatMill extends Mill{
        Wheat process(){return new Wheat();};
}
public class CovariantReturn {
        public static void main(String[] args){
                Mill m=new Mill();
                Grain g=m.process();
                System.out.println(g);   //Grain
                m=new WheatMill();
                System.out.println(m.process());  //Wheat
        }
}

8.5 用继承进行设计
        当我们在使用现成的类来建立新类时,如果首先考虑使用继承技术,反倒会加重我们的设计负担。
        更好的方式是选择“组合”,尤其是不能十分确定应该使用哪一种方式时。组合不会强制我们的程序设计进入继承的层次结构中。
       
        8.5.1 纯继承与扩展
                纯粹的“is--a”关系:即导出类的方法和基类的方法完全一样。
                “is--like--a”关系:即导出类除了有基类的方法外,还有自己扩展的方法。
        8.5.2 向下转型与运行时类型识别
                由于向上转型(在继承层次中向上移动)会丢失具体的类型信息,所以我们就想,通过向下转型---也就是在继承层次中向下移动---应该能够获取类型的信息。
                package chapter8;
/*@name Bath.java
* @describe  8.5.2 向下转型
* @since 2013-06-19 22:54
* @author 张彪
*/
class Useful{
        public void f(){}
        public void g(){}
}
class MoreUseful extends Useful{
        public void f(){}
        public void g(){}
        public void v(){}
}

public class RTTI {
        public static void main(String[] args){
                Useful[] x={new Useful(),new MoreUseful()};
                ((MoreUseful)x[1]).v();
        }
}

8.6 总结
        多态意味着“不同的形式”。在面向对象的程序设计中,我们持有从基类继承而来的相同的接口,以及使用该接口的不同形式;不同版本的动态绑定方法。




       
                                                                                                                2013-06-19 23:04 记 @@tangxiacun.tianhequ.guanzhou
0
6
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics