Java 多态

  • 多态通过分离做什么和怎么做,从另一个角度将接口实现分离开来
  • 多态的作用是消除类型之间的耦合
  • 多态方法允许一种类型表现出与其他类型之间的区别,只要它们都是从同一个基类(父类)导出而来的。这种区别是根据方法行为的不同二表示出来的,虽然这些方法都可以通过同一个基类(父类)来调用

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 
  • 将一个方法调用同一个方法主体关联起来被称作绑定。如果在程序执行之前进行绑定(如果有的话,由编译器和连接程序实现),叫做前期绑定
  • 但是上面的例子可以看到如果是前期绑定的话,当编译器只有一个<code>Father</code>引用时,它不知道究竟调用那个方法才对。解决办法就是后期绑定。意思就是在运行时根据对象的类型进行绑定。后期绑定也叫做动态绑定运行时绑定
  • 如果一种语言想要实现后期绑定,就必须具有某种机制,以便在运行时能够判断对象的类型,从而调用恰当的方法。也就是说,编译器一直不知道对象的类型,但是方法调用机制能够找到正确的方法体。
  • 其实我们平时写类似程序时候从来没有纠结过怎么绑定的,因为在Java中除了static方法和final方法(private方法属于final方法)之外,所有的方法都是后期绑定。所以我们不用去判定是否应该进行后期绑定——它自动会发生。
    又有个问题来了,那我们为什么要用final来声明某一个方法呢?,一个解释是防止继承中这个方法被覆盖。但是知道了后期绑定之后就知道它最重要的一个作用是可以”关闭“动态绑定,告诉编译器不需要对其进行动态绑定。这样做的好处是编译器可以为final方法调用生成更有效的代码。
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>的导出类。这就是协变返回类型

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容

  • 多态,英语Polymorphism,由希腊语的两个单词polys(意为many, much)和morphē(意为f...
    法兰克胡阅读 550评论 0 2
  • 写在前面 由于找工作的原因,最近几个月都没有更新博客了。。。这篇可能是今年最后一篇总结类的博文了,希望能够写的好点...
    niaoge2016阅读 9,102评论 1 52
  • 一、多态 1. 概述 理解:多态可以理解为事物存在的多种体(表)现形态。例如:动物中的猫和狗。猫这个对象对应的是猫...
    陈凯冰阅读 333评论 0 1
  • 将一个方法调用预期方法体关联起来,称为方法调用;如果在运行前已经被绑定,则称为前期绑定。而对于一个Shapes引用...
    谁吃了我的薯条阅读 279评论 0 0
  • 作为一门面向对象语言,Java 拥有封装、继承、多态三大特性。多态就是允许不同类的对象对同一消息做出响应。基于多态...
    程序之心阅读 882评论 0 5