Java之多态(动态绑定)

来源:互联网 发布:日常流水账软件 编辑:程序博客网 时间:2024/05/29 16:54
封装通过合并特征和行为来创建新的数据类型,“实现隐藏”则通过细节“私有化”把接口和实现分离。
继承可以允许向上转型,将对象视为它自己本身的类型或其基类来加以处理,但是这必须满足“is a”关系,而多态则消除类型之间的耦合关系,允许一种类型表现出与其他相似类型之间的区别,只要他们都是从同一个基类导出而来。多态通过分离做什么和怎么做,从另一个角度讲接口和实现分离开。
但是继承的向上转型会缩小接口,使得派生类扩展的方法向上转型之后不能使用。
package jin.feng1; class Person{  void print(){  System.out.println(); } //}public class Student extends Person{ public void sst(){} public static void main(String[] args) {  Person p1=new Student();  p1.sst(); //编译错误,不能访问  p1.print(); }}

后期绑定(多态机制):派生类和基类都含有相同的方法(覆盖),调用时不知道该调用哪个方法,是调用基类的还是派生类的方法
动态绑定是对于向上转型的对象而言,如果不存在向上转型,则该调用哪个方法调用哪个方法。对于向上转型的对象调用方法,它调用的方法肯定是向上转型这个对象类型所提供的方法,即Person类型仅仅提供print()方法,所以它可选的方法是基类非private方法,它如果调用的方法是基类仅有(子类没有覆盖),则调用基类方法,如果调用的方法子类覆盖了则调用相应子类方法。
将一个方法调用同一个方法主体关联起来被称作绑定。若在程序执行之前进行绑定,叫做前期绑定。
后期绑定,是在运行时根据对象的类型进行绑定。后期绑定也叫做动态绑定或运行是绑定。以便在运行是能判断对象的类型,从而调用恰当的方法。也就是说编译器不知道到具体对象类型,但是方法调用机制能找到正确的方法体,并加以调用。
Java中除了static方法和final方法之外,其他所有的方法都是后期绑定的。它会自动发生。
 
class Person{  void print(){System.out.println(" person");}void sst(){}} class Person1 extends Person{  void print(){System.out.println(" person1");} } class Person2 extends Person{  void print(){System.out.println(" person2");} }public class Student{ static void tune(Person p1){  p1.print();//子类覆盖,则根据具体类型调用具体方法 } public static void main(String[] args) {  Person p1=new Person1();//p1是基类Person类型,它可选的调用的子类没有覆盖方法sst()和子类覆盖的方法print()p1.sst();//子类没有覆盖,则直接调用基类Person p2=new Person2();   tune(p1); tune(p2);// }}

//output
person1
person2
虽然p1,p2是Person类型,但是它是由向上转型获得的,因此在运行期间会动态绑定到相应类型。并调用相应方法。
缺陷1:“私有方法的覆盖”
覆盖是针对非私有方法而言的,而程序中如果基类的方法是私有的,但是子类也有相应的方法名,此时的方法并不是覆盖,而是子类的新方法。
public class Student{ private void print(){System.out.println(" Student");} public static void main(String[] args) {  Student p1=new Student1();  p1.print();//貌似是动态绑定,其实调用的是自己类中的private方法。 }} class Student1 extends Student{  void print(){System.out.println(" Student1");}}//outputStudent缺陷2:成员变量和静态方法package jin.feng1;class Super{ int field=0; int getField(){return field;}}class Sub extends Super{int field=1;int getField(){return field;}int getSuperField(){return super.field;}}public class Student{  public static void main(String[] args) {  Super super1=new Sub();  System.out.println("成员变量访问:"+super1.field+"方法访问:"+super1.getField());  Sub sub1=new Sub();  System.out.println(sub1.getField()+"  "+sub1.getSuperField()); }}

//output
成员变量访问:0方法访问:1
1  0
补充:如果基类和派生类都定义一个相同的成员变量名,且不是私有的,则子类同时含有这两个变量,如果需要访问基类的同名变量则需要super.field,如果访问基类的同名方法则需要super.getField()
 向上转型: Super super1=new Sub();成员变量访问的时候是基类的field,而通过方法访问则动态绑定访问派生类的getField()方法,则调用派生类的field.
发现成员变量访问操作不会动态绑定,原因是什么呢?
任何成员变量访问操作都将由编译器解析,因此不是多态的。
这在平时是不会发生的,因为所有的成员变量一般都是private,不能直接访问他们。
缺陷3、静态方法
因为静态方法属于整个类,不会跟着具体的对象而进行变换类型。可以说它跟对象没有关系,所以没有动态绑定这一说。
package jin.feng1;class Static1{ static void print(){System.out.println("Static1 method");} void ss(){System.out.println("normal1 method");}}class Static2 extends Static1{ static void print(){System.out.println("Static2 method");} void ss(){System.out.println("normal2 method");}}public class StaticDynmic{public static void main(String args[]){ Static1 static1=new Static2(); static1.print(); static1.ss();}}

//output
Static1 method
normal2 method
构造器与多态
Java中构造器不同于其他的方法,构造器不具有多态性,因为它是static方法,不过是static声明是隐式的,如Construct c1=new Construct();就会调用构造方法,它不需对象名对其进行调用,所以它在对象创建之前就要进行调用,是类的方法。其他方法都是先创建对象,再通过对象对其进行调用。

package jin.feng1; class Constructor11{  void draw(){System.out.println(" Constructor11 draw()");}    public Constructor11(){   System.out.println("Constructor11:before draw()");   draw();   System.out.println("Constructor11:after draw()");  } } class Constructor12 extends Constructor11{  private int radius=2;  public Constructor12(){   System.out.println("Constructor12:before draw()");   draw();   System.out.println("Constructor12:after draw()");  }  void draw()  {   System.out.println("Constructor12+draw "+radius);  } } public class Constuctor22 {  public static void main(String[] args)  {   new Constructor12();  } } 

//output
Constructor11:before draw()
Constructor12+draw 0
Constructor11:after draw()
Constructor12:before draw()
Constructor12+draw 2
Constructor12:after draw()
调用基类构造方法时:第一次调用draw时,radius是0,还未被初始化。
调用子类构造方法时:第二次调用draw时,radius是2,即类内初始化
首先类加载。分配给对象的存储空间初始化为0.接着初始化基类成员变量,调用基类构造器,初始化子类成员变量,调用子类构造器。
0 0