好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Java多态

Java多态

上溯造型

  类可以被当做他的基类来使用,这种将对象当做基类进行引用并且进行使用被称之为Upcasting,因为他是沿着对象继成树向上走的。我们先看一下下面的例子:

 //  : polymorphism/music/Note.java
  //   Notes to play on musical instruments. 
 package   polymorphism.music;
  public   enum   Note {
  MIDDLE_C, C_SHARP, B_FLAT;   //   Etc. 
}  //  /:~

  //  : polymorphism/music/Instrument.java 
 package   polymorphism.music;
  import   static  net.mindview.util.Print.* ;
  class   Instrument {
    public   void   play(Note n) {
    print( "Instrument.play()" );
  }
}
  //  /:~
  //  : polymorphism/music/Wind.java 
 package   polymorphism.music;
  //   Wind objects are instruments
  //   because they have the same interface: 
 public   class  Wind  extends   Instrument {
  //   Redefine interface method: 
   public   void   play(Note n) {
    System.out.println( "Wind.play() " +  n);
  }
}   //  /:~
  //  : polymorphism/music/Music.java
  //   Inheritance & upcasting. 
 package   polymorphism.music;
  public   class   Music {
    public   static   void   tune(Instrument i) {
      //   ... 
     i.play(Note.MIDDLE_C);
  }
    public   static   void   main(String[] args) {
    Wind flute  =  new   Wind();
    tune(flute);   //   Upcasting 
   }
}   /*   Output:
Wind.play() MIDDLE_C
  *///  :~ 

  在Music的tune中应该接收的是Instrument引用,但是其实传递任何继承自Instrument的对象都可以,而且不需要进行类型转换。Wind中包含了Instrument的借口,虽然从Wind到Instrument的转换会缩小Wind的借口,但是对Instrument的使用并不会有影响。

  那么我们为什么不将tune方法的参数设置为Wind类型的呢?在tune方法仅仅是需要Instrument的接口的情况下,如果将参数的类型设置为具体的Wind类型,那么当我们新添加一个乐器的时候,就需要多书写一个以这个具体乐器类型为参数的tune方法。这无疑增加了代码的耦合性和复杂性。而这也正是多态解决的问题。

捻度控制

  在Music中的tune方法中调用的是Instrument的play方法,而多态需要控制的一点就是根据传入的子类型的来调用对应派生类型的play方法,编译器是如何做到这一点的呢?编译器是做不到这一点的,为了理解如何实现这一点,我们需要了解一下对象绑定的概念

  在调用方法的时候涉及到一个改变,叫做绑定。有两种形式的方法绑定,预绑定和即使绑定,预绑定就是在编译的时候进行方法的绑定,如C语言中只有预绑定,但是即时绑定是在运行的时候,根据对象的类型,来动态的决定应该绑定的方法,不同的语言有不同的动态绑定方法的机制,但是有一点是肯定的,那就是对象中肯定存在一些信息用来帮助在运行的时候确定对象的类型。

  在Java中,出了static和final修饰的方法之外,其余的都是使用即时绑定的方法来调用方法。因此你不用手动使用即时绑定,它会自动为你实现。正式因为Java的这种即时绑定的机制,允许了我们直接与基类进行交互,而编译器会根据传入的派生类的具体类型来执行正确的方法。

  这样的一种设计方法使得程序具有很好的扩展性,因为随着从基类派生出更多的类实现更多功能的时候,与基类进行交互的代码几乎是不需要进行任何修改的。

  但是下面的代码需要注意:

 //  : polymorphism/PrivateOverride.java
  //   Trying to override a private method. 
 package   polymorphism;
  import   static  net.mindview.util.Print.* ;
  public   class   PrivateOverride {
    private   void  f() { print("private f()" ); }
    public   static   void   main(String[] args) {
    PrivateOverride po  =  new   Derived();
    po.f();
  }
}
  class  Derived  extends   PrivateOverride {
    public   void  f() { print("public f()" ); }
}   /*   Output:
private f()
  *///  :~ 

  这里的基类中的f方法并没有在派生类中重写,因为他是私有的,对派生类是不可见的,所以派生类中的f是一个全新的方法。这里在这种情况下需要将这两个方法设置为不同的名称。

  需要记住的一点是,多态只能针对一般的方法进行使用,如果使用多态去读取字段的话,那么读到的将会是基类的字段,如下面的代码:

 //  : polymorphism/FieldAccess.java
  //   Direct field access is determined at compile time. 
 class   Super {
    public   int  field = 0 ;
    public   int  getField() {  return   field; }
}
  class  Sub  extends   Super {
    public   int  field = 1 ;
    public   int  getField() {  return   field; }
    public   int  getSuperField() {  return   super  .field; }
}
  public   class   FieldAccess {
    public   static   void   main(String[] args) {
    Super sup  =  new  Sub();  //   Upcast 
    System.out.println("sup.field = " + sup.field + ", sup.getField() = " +  sup.getField());
    Sub sub  =  new   Sub();
    System.out.println( "sub.field = " +  sub.field  + ", sub.getField() = " +  sub.getField()  + ", sub.getSuperField() = " +  sub.getSuperField());
  }
}   /*   Output:
sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
  *///  :~ 

  另外,对于静态方法,也不具有多态特性,如下面的代码:

 //  : polymorphism/StaticPolymorphism.java
  //   Static methods are not polymorphic. 
 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()" ;
  }
}
  public   class   StaticPolymorphism {
    public   static   void   main(String[] args) {
    StaticSuper sup  =  new  StaticSub();  //   Upcast 
     System.out.println(sup.staticGet());
    System.out.println(sup.dynamicGet());
  }
}   /*   Output:
Base staticGet()
Derived dynamicGet()
  *///  :~ 

构造器和多态

  通常来说,构造器也是不具有多态特性的,他们实际上是静态方法,只不过没有明确使用static关键字而已。下面我们来看一下构造器在多太中起到的作用。

  在构造派生类的时候总是需要在派生类的构造器中调用基类的构造器,这样才能够保证类的正确构建。因为派生类无法读取基类中的私有字段,只有在基类的构造函数中才能对基类中的私有字段进行正确的初始化。如果在派生类的构造器中没有显示调用基类中的构造器,那么将会调用默认的构造器,如果基类没有默认的构造器,那么编译器将会报错。下面的代码中可以看到复杂的层级关系中的实例化顺序:

 //  : polymorphism/Sandwich.java
  //   Order of constructor calls. 
 package   polymorphism;
  import   static  net.mindview.util.Print.* ;
  class   Meal {
  Meal() { print( "Meal()" ); }
}
  class   Bread {
  Bread() { print( "Bread()" ); }
}
  class   Cheese {
  Cheese() { print( "Cheese()" ); }
}
  class   Lettuce {
  Lettuce() { print( "Lettuce()" ); }
}
  class  Lunch  extends   Meal {
  Lunch() { print( "Lunch()" ); }
}
  class  PortableLunch  extends   Lunch {
  PortableLunch() { print( "PortableLunch()" );}
}
  public   class  Sandwich  extends   PortableLunch {
    private  Bread b =  new   Bread();
    private  Cheese c =  new   Cheese();
    private  Lettuce l =  new   Lettuce();
    public  Sandwich() { print("Sandwich()" ); }
    public   static   void   main(String[] args) {
      new   Sandwich();
  }
}   /*   Output:
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()
  *///  :~ 

  由上面的代码中可以看到派生类的构造顺序是首先为要生成的类进行内存分配,然后先调用基类的构造函数,然后是调用派生类中成员的构造函数,这个顺序是按照成员声明的顺序,最后是调用派生类的构造函数。知道这个顺序是非常重要的,这样在派生类的构造器中你就可以基类中的成员,因为这个时候基类已经初始化完毕了,而且在派生类的构造函数中开始执行的时候,成员也已经初始化结束了。

   与初始化相反的是对象的清理工作,虽然可以留给垃圾收集器进行清理,但是有些涉及到非内存的清理工作仍然需要手动进行,可以在基类中添加一个dispose函数进行清理,然后每个派生类都对这个函数进行重写。重写的时候要首先进行派生类的清理工作,最后调用基类的dispose函数,因为在派生类进行清理的时候有可能需要用到基类中的某些函数,如西面的代码:

 //  : polymorphism/Frog.java
  //   Cleanup and inheritance. 
 package   polymorphism;
  import   static  net.mindview.util.Print.* ;
  class   Characteristic {
    private   String s;
  Characteristic(String s) {
      this .s =  s;
    print( "Creating Characteristic " +  s);
  }
    protected   void   dispose() {
    print( "disposing Characteristic " +  s);
  }
}
  class   Description {
    private   String s;
  Description(String s) {
      this .s =  s;
    print( "Creating Description " +  s);
  }
    protected   void   dispose() {
    print( "disposing Description " +  s);
  }
}  class   LivingCreature {
    private  Characteristic p =  new  Characteristic("is alive" );
    private  Description t =  new  Description("Basic Living Creature" );
  LivingCreature() {
    print( "LivingCreature()" );
  }
    protected   void   dispose() {
    print( "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() { print( "Animal()" ); }
    protected   void   dispose() {
    print( "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 land" );
  Amphibian() {
    print( "Amphibian()" );
  }
    protected   void   dispose() {
    print( "Amphibian dispose" );
    t.dispose();
    p.dispose();
      super  .dispose();
  }
}
  public   class  Frog  extends   Amphibian {
    private  Characteristic p =  new  Characteristic("Croaks" );
    private  Description t =  new  Description("Eats Bugs" );
    public  Frog() { print("Frog()" ); }
    protected   void   dispose() {
    print( "Frog dispose" );
    t.dispose();
    p.dispose();
      super  .dispose();
  }
    public   static   void   main(String[] args) {
    Frog frog  =  new   Frog();
    print( "Bye!" );
    frog.dispose();
  }
}   /*   Output:
Creating Characteristic is alive
Creating Description Basic Living Creature
Polymorphism 207
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
  *///  :~ 

  但是需要注意到另外一个问题,那就是如果某个实例的引用被多个对象引用的时候,那么就不能直接对其清理,要确定没有其他类型的实例对他还有引用之后才能进行清理,下面的例子中添加了一个专门用来计数对象实例化个数,用id进行表示,以及被引用个数的成员refcounter,在确定没有对象对其实例有引用之后才对其进行清理:

 //  : polymorphism/ReferenceCounting.java
  //   Cleaning up shared member objects. 
 import   static  net.mindview.util.Print.* ;
  class   Shared {
    private   int  refcount = 0 ;
    private   static   long  counter = 0 ;
    private   final   long  id = counter++ ;
    public   Shared() {
    print( "Creating " +  this  );
  }  
    public   void  addRef() { refcount++ ; }
      protected   void   dispose() {
      if (--refcount == 0 )
      print( "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) {
    print( "Creating " +  this  );
      this .shared =  shared;
      this  .shared.addRef();
  }
    protected   void   dispose() {
    print( "disposing " +  this  );
    shared.dispose();
  }
    public  String toString() {  return  "Composing " +  id; }
}
  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();
  }
}   /*   Output:
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
  *///  :~ 

  但是这里还有一个问题,就是如果在派生类的构造函数中调用派生类的多态函数呢?来看下面的代码先:

 //  : polymorphism/PolyConstructors.java
  //   Constructors and polymorphism
  //   don’t produce what you might expect. 
 import   static  net.mindview.util.Print.* ;
  class   Glyph {
    void  draw() { print("Glyph.draw()" ); }
  Glyph() {
    print( "Glyph() before draw()" );
    draw();
    print( "Glyph() after draw()" );
  }
}
  class  RoundGlyph  extends   Glyph {
    private   int  radius = 1 ;
  RoundGlyph(  int   r) {
    radius  =  r;
    print( "RoundGlyph.RoundGlyph(), radius = " +  radius);
  }
    void   draw() {
    print( "RoundGlyph.draw(), radius = " +  radius);
  }
}
  public   class   PolyConstructors {
    public   static   void   main(String[] args) {  
      new  RoundGlyph(5 );
  }
}   /*   Output:
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
  *///  :~ 

   这里我们可以看到在派生类的构造函数中调用多态函数的时候确实是会执行派生类中的函数,但是由于派生类还没有完全构造完毕,因此非常容易产生不是我们需要的效果。

协变返回类型

  Java SE5中增加了协变返回类型,意思是一个在派生类中重写的方法可以返回一种类型,这种类型是基类方法中返回的类型的派生类型,看下面的代码:

 //  : polymorphism/CovariantReturn.java 
 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);
    m  =  new   WheatMill();
    g  =  m.process();
    System.out.println(g);
  }
}   /*   Output:
Grain
Wheat
  *///  :~ 

  在Java SE5之前的版本中,虽然Wheat是Grain的派生类型,但是在重写process的时候会强制返回Grain类型,而且这样的返回时合法的。协变返回类型允许在重写函数中返回更加具体的类型

下塑造性

     多态中的上溯造型能够保证类型的安装转换,因为任何可以对基类调用的方法都可以对其派生类进行使用,但是如果在上溯之后我们需要还原到起始的类型的时候,就需要进行强制类型转换,而在强制类型转换的过程中有可能出现错误,因为一个基类可以派生出多个基类,而上塑之后我们就不知道当初他的具体类型了,因此我们需要进行判断,如下面的代码:

 //  : polymorphism/RTTI.java
  //   Downcasting & Runtime type information (RTTI).
  //   {ThrowsException} 
 class   Useful {
    public   void   f() {}
    public   void   g() {}
}
  class  MoreUseful  extends   Useful {
    public   void   f() {}
    public   void   g() {}
    public   void   u() {}
    public   void   v() {}
    public   void   w() {}
}
  public   class   RTTI {
    public   static   void   main(String[] args) {
    Useful[] x  =  {  new   Useful(),   new   MoreUseful()};
    x[ 0 ].f();
    x[ 1 ].g();
      //   Compile time: method not found in Useful:
      //  ! x[1].u(); 
    ((MoreUseful)x[1]).u();  //   Downcast/RTTI 
    ((MoreUseful)x[0]).u();  //   Exception thrown 
   }
}   //  /:~ 

  在运行的时候确定对象的具体类型称之为运行时类型识别runtime type identification,RTTI,关于RTTI我们还有比强制类型转换更多的方法,我们可以在下塑之前就获得对象的具体类型的信息,这点留到以后再讲

 

分类:  Java

作者: Leo_wl

    

出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于Java多态的详细内容...

  阅读:45次