程序员

Java 多态

2016-04-23  本文已影响271人  CrazyYong

1.向上转型

把某个对象的引用视为对其基类型的引用的作法被称为向上转型——因为继承树的画法中,基类是放置在上方的,类似一个金字塔。

2.方法动态绑定

2.1

如下代码,<code>tune()</code>方法接受一个<code>Father</code>引用,那么在这种情况下,编译器怎么才能知道这<code>Father</code>引用指向的是<code>ChildOne</code>或者<code>ChildTwo</code>对象呢?实际上,编译器无法得知。这就要引出绑定这个问题了。

  public class Father(){
    void getChild(){
    System.out.println("I am Father");
        };
    }

 public class ChildOne exends Father(){
    void getChild(){
    System.out.println("I am ChildOne");
       }
   }
 public class ChildTwo exends Father(){
    void getChild(){
    System.out.println("I am ChildTwo ");
       }
   }

 public class Test{
   public static void tune(Father object){
   object.getChild();
       }
   public void main(String[] args){
   ChildOne  childOne = new ChildOne();
   ChileTwo  childTwo = new ChildTwo();
   tune(childOne); 
   tune(childTwo); 
   
      }
  }
  out:I am ChildOne
      I am ChildTwo 
2.2

知道了Java中所有的方法都是通过动态绑定实现多态这个事实后,我们就可以编写只与基类打交道的程序代码了,并且这些代码对所有导出类都可以正确运行。或者换一种说法,发送消息给对象,让该对象去断定应该做什么事。

Paste_Image.png

上面这个图不陌生,学JAVA的基本都见过,”圆是一种几何形状“,向上转型就可以变成下面这个代码:
<code>Shape s = new Cicle();</code>
当你调用<code>s.draw()</code>方法后,你可以认为是<code>Shape</code>的<code>draw()</code>方法,因为这毕竟是一个<code>Shape</code>引用,那么编译器是怎么知道去做其他的事情呢?由于后期绑定,还是正确调用了<code>Circle.draw()</code>方法。

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

如下代码:

    public class PrivateOverride{
        private void f()  {
        Systtem.out.println("private f()");
        }
    }

    class Derived extends PrivateOverride(){
       public void f(){
           Systtem.out.println("public f()");
        }
    }
    out:private f()

本来以为会输出<code>public f()</code>,但是由于<code>private</code>方法那个发被自动认为是<code>final</code>方法,而且对导出类是屏蔽的。因此,在这种情况下,<code>Derived</code>类中的<code>f()</code>就是一个全新的方法;既然基类中的<code>f()</code>方法在<code>Derived</code>中不可见,因此甚至也不能被重载。
虽然这样写没啥问题,编译器也不报错,但是容易产生混淆,所以最好还是采用不同名字。

2.3 缺陷:域与静态方法

有个问题,多态适用于方法调用,但是适用于某个域吗?,答案是不可以的,因为只有普通的方法调用可以使多态的。下面举个例子:

    class Super{
      public int field = 0;
      public int getField(){
      return field;
      }
    }
    
    class Sub extends Super{
       public int field= 1 ;
       public int getField(){return fields;}
       public int getSuperField(){return super.field};
     }

     public class FieldAccess{
     public static void main(String[] args){
     Super sup = new Sub();
     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());
    }
      }
     out:
     sup.field=0,sup.getField()=1;
     sub.field=1,sub.getField()=1,sub.getSuperField()=0

上面的结果一目了然,证明了我们刚才的结论,但是一般情况下这种情况比较少出现,因为我们如果代码严谨的话很少会出现域是<code>public</code>的情况,所以最好把域设置成<code>private</code>的,这样保证了代码安全和防止混淆,但是你如果想访问这些<code>private</code>代码的话就必须要调用方法来访问了,类似JavaBean。
如果是静态方法,它的行为也不具有多态性,如下面的例子:

    class StaticSuper{
      public static String staticGet(){return "Base staticGet()";}
      public Sring dynamicGet(){return "Base dynamicGet()";}
       }
    
      class StaticSub extends StaticSuper{
      public static String staticGet(){return "Derived staticGet()";}
      public static String dynamicGet(){return "Derived dynammicGet()";}

      public class StaticPolymorphism{
        public static void main(String[] args){
        StaticSuper sup = new StaticSub();//向上转型
        System.out.println(sup.staticGet());
        System.out.println(sup.dynamicGet());
          }
        }
        out:
        Base staticGet()
        Derived dynamicGet()

Ok,结果很明显,类似于上一个域问题的例子,我们得出一个结论:静态方法是与类,而并非与单个的对象相关联的

3.构造器和多态

通常,构造器不同于其他种类的方法。涉及到多态时仍然如此。尽管构造器并不具有多态性(它们实际上是static方法,只不过该statuc声明是隐式的),但是还是要单独拿出来说明下构造器通过多态在复杂的层次结构中是如何运作的。

3.1构造器的调用顺序

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

    class Meal{
       Meal(){print(print("Meal()");)
    }

    class Bread{
       Bread(){print(print("Bread()"));)
    }

    class Cheese{
       Cheese(){print(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();
          }
        }
        out:
             Meal();
             Lunch();
             PortableLunch();
             Bread();
             Cheese();
             Lettuce();
             Sandwich();

通过这个例子我们看到了构造器的的调用顺序:

  1. 调用基类构造器。这个步骤会不断的反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,等等,直到最底层导出类。
  2. 按声明顺序调用成员的初始化方法。
  3. 调用导出类构造器的主体。
    构造动作一经发生,那么对象所有部分的全体成员都会得到构建。而且要确保构造器内部所要使用的成员都已经构建完。为了确保这个目的,唯一办法就是首先调用基类的构造器。

4.协变返回类型

Java SE中添加了协变返回类型,它表示在导出类中的被覆盖方法可以返回基类方法的返回类型的某种到处类型。听着非常绕口,举个例子:

    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);
      }
       }
    out:
        Grain
         Wheat

例子一目了然 <code>process()</code>方法可以返回<code>Grain</code>的导出类。这就是协变返回类型

纯手敲,总结了一下,希望对大家有用,顺便自己可以当做笔记没事回顾下,有问题可以评论,看到会回答。

上一篇下一篇

猜你喜欢

热点阅读