多态

来源:互联网 发布:电脑编织机编程 编辑:程序博客网 时间:2024/06/04 01:24

1. 什么是多态

  • 面向对象的三大特征:继承、封装、多态;从一定角度看,封装和继承几乎都是为多态准备的;
  • 多态定义:允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式   
  • 多态分类:

              编译时多态:方法重载

             运行时多态:  JAVA运行时系统根据调用该方法的实例的类型来决定选择调用哪个方法,在执行期间(而非编译期间)判断所引用对象的实际类型,根据实际类型判断并调用相应的属性和方法

  • 多态作用:消除类型之间的耦合关系
  • 多态举例:鸡 a = new 火鸡();  鸡 b =new 野鸡(); 鸡 c = new 山鸡();

 

2.多态存在的三个必要条件

        要有继承(包括接口的实现);

        要有重写;

        父类引用指向子类对象;


     为什么重写是必要的,首先不重写,继承了就可以用,没什么意义。也就是说,假如父类有一个方法,子类没有重写,或者甚至连定义都没有,那么就相当于继承。仍然可以调用父类的该方法。只是没什么意义而已。

     其次,需要说明的一点是,如果使用了多态,子类调用某方法,若父类没有该方法,那么就出错,所以必须是重写才能保证父类肯定有该方法)

3.举个栗子

3.1上代码

(网上搜罗的~感觉很经典)

public class A { public String show(D obj){     return ("A and D"); } public String show(A obj){     return ("A and A"); }   public static void main(String args[]){     A a1 = new A();     A a2 = new B();     B b  = new B();     C c  = new C();     D d  = new D();      System.out.println("1: "+ a1.show(b));    System.out.println("2: "+ a1.show(c));    System.out.println("3: "+a1.show(d));    System.out.println("4: "+a2.show(b));    System.out.println("5: "+a2.show(c));    System.out.println("6: "+a2.show(d));    System.out.println("7: "+b.show(b));      System.out.println("8: "+b.show(c));      System.out.println("9: "+b.show(d));      }}class B extends A{public String show(B obj){          return ("B and B");  }  @Override  //此方法重写父类的方法public String show(A obj){          return ("B and A");  }}class C extends B{}class D extends B{}

上述的运行结果为

1: A and A2: A and A3: A and D4: B and A5: B and A6: A and D7: B and B8: B and B9: A and D

3.2 代码分析

注意:

      当超类对象引用变量引用子类对象时,是被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须在超类中定义过,也就是被子类覆盖的方法(但是如果强制把超类转换成子类的话,就可以调用子类中新添加而超类没有的方法了。)

特别注意:当继承的时候,子类会继承父类的一切属性和方法,但是呢,只有方法支持后期绑定,而成员变量不支持,所以,使用多态的时候,成员变量的调用是和引用的类型保持一致的。方法支持后期绑定,所以对象是什么类型就用什么类型里面的方法。

但是,如果子类没有重写父类的,就会调用父类的。

       方法调用顺序优先级为(高到低):this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)

      第四个输出为  “B and A” :a2.show(b)   a2为一个引用变量,类型为A,按照上述顺序,查找过程如图所示



   第5个问题是:输出 a2.show(c)

   a2是类A的引用变量,引用的是类B的一个实例对象。c是类C的一个实例。
   1)this.show(c),在A中找show(C obj)的方法,进行第二步。
   2)super.show(c),A没有超类,进行第三步。
   3)this.show(super(c)), super(c)是B,A中是没有show(B obj)方法的,但是这时候会继续往上找,找B的父类,super(B)是A,在A中找show(A obj), 找到了。
   4)a2引用的是类B的一个对象,由B来决定调用哪个方法,因为类B中覆盖了A的show(A obj)方法,因此最终用类B中的show(A obj)方法。  

  再比如⑧,b.show(c),b是一个引用变量,类型为B,则this为b,c是C的一个实例,于是它到类B找show(C obj)方法,没有找到,转而到B的超类A里面找,A里面也没有,因此也转到第三优先级this.show((super)O),this为b,O为C,(super)O即(super)C即B,因此它到B里面找show(B obj)方法,找到了,由于b引用的是类B的一个对象,因此直接锁定到类B的show(B obj),输出为"B and B”。     


4. 论向上转型

  上述讨论之后我们可以知道,对象即可以作为它自己本身类型使用,也可以作为它的基类型使用,而这种把某个对象的引用视作对其基类型的引用的做法称做向上转型,因为在继承树中,基类放置于上方。  向上转型可能会“缩小”接口,但是不会比基类的全部接口窄。

   动态绑定:将一个方法调用同一个方法主体关联起来被称作绑定,在运行时根据对象的类型进行绑定叫做后期绑定,Java中除了static方法和final方法(private方法属于final方法)之外,其他所有方法都是后期绑定,会自动发生。为什么要把某个方法声明为final呢,除了防止他人覆盖之外,更重要的一点是可以有效地关闭动态绑定,然而大多数情况下这样做对程序的整体性能没有什么改观,所以最好根据设计来觉得是否需要用到final__——————java编程思想


  动态绑定分析见: http://hxraid.iteye.com/blog/428891


   向上转型代码就不列了,可以参考  http://blog.csdn.net/mr_jj_lian/article/details/6860845

4.1 向上转型会“覆盖”私有方法

public class PrivateOverride {private void f(){System.out.println("private f()");}public static void main(String args[]){PrivateOverride po = new Derived();po.f();}}class Derived extends PrivateOverride{public void f(){System.out.println("public f()");}}//输出结果为 private f()

      我们期望输出为public f(),但是由于private方法被自动认为是final的,对导出类是屏蔽的,这种情况下,Derived类中的f()方法时一个全新的方法,即基类中的方法f ()在子类中不可见,因此不能被重载。

      结论:只有非private方法可以被覆盖,但还是需要密切关注覆盖private方法的现象,这时编译器虽然不报错,但是也不会按照我们所期望的执行,确切的说,导出类中,对于基类的private方法,最好采用不同的名字。

4.2 缺陷2 :域和静态方法

  编程思想第156页,平时应该用不到,毕竟一般域都会设成private,子类的命名和父类也不一致,就不描述了,有兴趣的可以自己去看看。


5.构造器和多态

  通常,构造器不同于其他种类的方法,构造器实际上是static方法,只不过是隐式声明的,但需要了解构造器在多态中的初始化过程。上代码:

class Meal{Meal(){System.out.println("Meal");}}class Bread{Bread(){System.out.println("Bread");}}class Lunch extends Meal{Lunch(){System.out.println("Lunch");}}class PortableLunch extends Lunch{PortableLunch(){System.out.println("PortableLunch");}}public class Sandwich extends PortableLunch{private Bread b = new Bread();Sandwich(){System.out.println("SandWich");}public static void main(String args[]){new Sandwich();}}
输出为

MealLunchPortableLunchBreadSandWich

由此得出调用顺序:

1. 调用基类构造器。这个步骤会不断反复地递归下去,首先是构造这种层次的根,然后下一层导出类,直到导出最低层的导出类;

2.按声明的顺序调用成员的初始化方法;

3.调用导出类构造器的主体。


编写构造器的准则:用尽可能简单地方法使对象进入正常状态,如果可以的话应该尽量避免调用其他方法。

在构造器内唯一能够安全调用的那些方法是基类中的final方法(也适用于private方法),这些方法不能被覆盖。


参考:

2016年6月26日

10:13

http://www.cnblogs.com/jack204/archive/2012/10/29/2745150.html 

http://www.cnblogs.com/mengdd/archive/2012/12/25/2832288.html

http://www.cnblogs.com/qinqinmeiren/archive/2011/07/15/2151687.html

http://blog.csdn.net/thinkGhoster/article/details/2307001

http://blog.csdn.net/tophot115/article/details/3016033 

http://www.cnblogs.com/qinqinmeiren/archive/2011/07/15/2151687.html



0 0
原创粉丝点击