Java中的多态

Java中的多态

Copyright©Stonee新网址


 多态是面向对象语言的重要特点之一,有利于程序的可扩展和可维护。多态,即多种形态。官方对多态的解释为:一个对象变量可以指示多种实际类型的现象被称为多态。 根据我的动手实际操作,所理解的意思就是,一个对象变量,通过某种手段,在调用相同的方法之后可以产生不同的结果即为多态。下面我将从我实际的例子出发和大家一起讨论一下多态。

1. 多态的条件

  • 多态是建立在继承的基础上
  • 多态是建立在虚方法中虚调用的基础上
  • 多态是建立在覆盖(override)的基础上
Q:什么是继承?

 继承关系属于is-a关系,是一种用于表示特殊与一般的关系。如同集合一样,里面的子集属于父类/基类,外面的属于子类,显而易见,子类拥有父类的所有特性。我们说子类继承于父类,用extents表示:

class son extents father{}
Q:什么是覆盖?

 当父类有一个方法,子类此时有一个和父类方法的返回值、方法名和参数都一样的方法时,就成为子类方法覆盖了父类方法。

Q:什么是虚方法和虚调用?

 为了程序员编出更健壮的程序,我们把方法分为以下几类:

  • 静态方法(通过static关键字修饰)
  • 实例方法:
    其中实例方法又分为:
  • 实方法 :通过private关键字修饰或者是构造方法
  • 虚方法 :除了实方法外都是虚方法
    其中虚方法又分为:
  • 实调用 :具有final关键字
  • 虚调用 :除了实调外都是虚调用

2. 多态的例子

  • 虚调用的执行过程:
    先举个栗子:
package chapter05;

public class Duotai01 {
   public static void main(String [] args){        
       GrandF a = new GrandF();      //a为grandf的实例  
       a.funA();        
       a = new Father();           //a为father的实例
       a.funA();    
   }
}
class GrandF {    
   public void funA(){       
       System.out.println("A's fun he");   
    }     
}
class Father extends GrandF{   
@Override    
      public void funA() {     
           System.out.println("B's fun he");   
   }
}

输出结果为:
A’s fun he
B’s fun he

 根据栗子大致说下这个程序的执行过程:首先先在堆里面建立这三个类,加载类中的实例方法。
在这个栗子中,Grandf明显是父类,它先继承object中的5个类,槽号分别为0-4,都放在虚表(存放虚方法的表)中,然后再加载自己的虚方法,即funA,也放在虚表之中,槽号为5。之后加载Father类,它把父类grandf的所有虚方法全部继承过来,槽号不变,但是需要覆盖槽号5的funA方法
 当加载完实例之后,会在栈中给变量a分配地址,然后指向堆中的实例对象,此时实例对象指向grandf类。当开始编译器开始编译的时候,会顺着a找到grandf类,然后找到类中的方法,记录这个方法的槽号。之后交给虚拟机,虚拟机会在通过a找到实例,根据实例找到相对应的类,在通过槽号找到方法因为第一次调用a还是指向了grandf的实例,故执行grandf的方法
 当第二次把a指向father实例时,这时father实例指向father类,JVM会通过a找到father的实例,在根据槽号找到father中固定位置的方法
 因为多态会经过JVM而不是直接通过编译器,故多态过程也称为动态绑定,而如实方法直接经过编译器完成的过程称为静态绑定。

3. 拓展

① 上面讨论了虚方法的虚调用是怎么工作的,那么实方法是如何工作的呢?

 虚调用实在编译器和JVM的共同作用下才能实现,而实方法则是直接通过编译器就可以完成。直接通过编译器的过程也叫做静态绑定。
 实方法是没有槽号的,当变量找到类之后,不用记录槽号,直接就会调用类中的实方法。因为没有了槽号,所以也就不存在寻找子类中的方法。实方法不需要通过实例直接就会被找到,所以在一定程度上比虚调用更快,但是很显然,虚调用实现了多态而实方法不可以。

通过一个栗子来看一下实例的静态绑定:

class A{   
    int num= 6;   
    int getNum() {            
    //虚方法getNum,virtual,new slot      
        return num; //静态绑定A.num    
    }
}
class B extends A{    
    int num = 9;    
    int getNum() {           
    //虚方法getNum,override,reuse slot        
        return num; //静态绑定B.num    
    }
}
class Field {   
    public static void main(String[] args) {       
        A a = new A();        
        B b = new B();       
        A ab = b;        
        System.out.println(a.getNum());           //动态调用虚方法getNum  6        
        System.out.println(ab.getNum());          //动态调用虚方法getNum  9        
        System.out.println(b.getNum());           //动态调用虚方法getNum  9       
        System.out.println(a.num);                //静态绑定A.num   6  
        System.out.println(ab.num);               //静态绑定A.num   6        
        System.out.println(((B)ab).num);          //将ab强制类型转换   9        
        System.out.println(b.num);//静态绑定B.num   9    
    }
}

我们可以看出,在刚开始编译而没有通过JVM的时候,已经编译成功。所以在ab实例调用num字段的时候就不会经过b的实例直接输出在a中的6。

② 带有final的实调用工作过程

 实调用也属于虚方法,故实调用也有槽号,但是因为带有final,所以虚调用不能被子类覆盖。当子类进行实调用的时候,就会和实方法的过程一样进行,所以加上final后也会更快。

4.二更 ,动态绑定中的桥接方法

当派生类中的方法名和父类中的方法名一样,但返回值为父类方法返回值的派生类时,此时需用到动态绑定的桥接方法,如:

class GrandFather{    
        public int a;   
        public GrandFather() {        
            a = 10;  
    }    
   //虚方法method,new slot    
        public GrandFather method() {       
             System.out.println("--------调用GrandFather method()方法");       
        return new GrandFather();    
    }
 }
 //父类
 class Father extends GrandFather {   
        public int b;    
        public Father() {       
            b = 20;  
        }    
        //添加一个桥接方法,调用Father method(),      override   
        /* public GrandFather method(){
                Father method();
           }
           */
        //虚方法method,返回可协变的类Father,            new slot    public
        Father method() {        
             System.out.println("--------调用Father method()方法");                      return new Father();   
        }
 }

当父类overridemethod方法时,由于返回值变成了GrandFather的派生类即father,所以编译器会在其中添加一个桥接方法,这个桥接方法和父类的名称,返回值,形参包括槽号都一样。然后这个桥接方法再调用派生类中新槽号里的method方法,实现多态,即动态绑定
显而易见的是,这样实现多态的过程是通过方法二次调用来实现的,效率会变低

参考资料:
[1]Cay S. Horstmann. Core Java Volume I-Fundamentals[M].北京:机械工业出版社,2018第10版

评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值