Java面向对象的多态

多态是指,子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。

多态这种特性也需要编程语言提供特殊的语法机制来实现
  1. 编程语言要支持父类对象可以引用子类对象;
  2. 编程语言要支持继承;
  3. 编程语言要支持子类可以重写(override)父类中的方法;
java中的多态重写示例:
public class DynamicDispatch { 
    static abstract class Human { 
        protected abstract void sayHello(); 
    }
    
    static class Man extends Human { 
        @Override 
        protected void sayHello() {
            System.out.println("man say hello"); 
        } 
    }
    
    static class Woman extends Human {  
        @Override 
        protected void sayHello() {
            System.out.println("woman say hello"); 
        } 
    }
    
    public static void main(String[] args) {
        Human man = new Man(); 
        Human woman = new Woman();
        man.sayHello();  // invokevirtual #22; //Method org/fenixsoft/polymorphic/DynamicDispatch$Human.sayHello:()V
        woman.sayHello(); // invokevirtual #22; //Method org/fenixsoft/polymorphic/DynamicDispatch$Human.sayHello:()V
        man = new Woman(); 
        man.sayHello();
    }
}
//运行结果:
man say hello 
woman say hello 
woman say hello
分析:
  1. Human man = new Man();中的“Human”称为变量的“静态类型”也即父类对象,后面的“Man”则被称为变量的“实际类型”也即子类对象;
  2. 静态类型和实际类型在程序中都可能会发生变化,区别是静态类型的变化仅仅在使用时发生,变量本身的静态类型不会被改变,并且最终的静态类型是在编译期可知的;而实际类型变化的结果在运行期才可确定;
// 实际类型变化 
Human human = (new Random()).nextBoolean() ? new Man() : new Woman(); 

// 静态类型变化
sr.sayHello((Man) human) 
sr.sayHello((Woman) human)
  1. 通过javap命令可以得到man.sayHello();和woman.sayHello();的字节码指令和参数都是一样的;都是使用invokevirtual指令进行方法调用,参数都是Human.sayHello()的符号引用。所以可以大胆猜测肯定是invokevirtual指令在执行的时候是根据实际类型找到最终执行的方法才会导致结果不一;
  2. 根据《Java虚拟机规范》, invokevirtual指令的运行时解析过程大致分为以下几步:
    1. 找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C;
    2. 如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;不通过则返回java.lang.IllegalAccessError异常。
    3. 否则,按照继承关系从下往上依次对C的各个父类进行第二步的搜索和验证过程。
    4. 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。
  3. 正是因为invokevirtual指令执行的第一步就是在运行期确定接收者的实际类型,所以两次调用中的 invokevirtual指令并不是把常量池中方法的符号引用解析到直接引用上就结束了,还会根据方法接收者的实际类型来选择方法版本,这个过程就是Java语言中方法重写的本质。这种在运行期根据实际类型确定方法执行版本的过程称为动态分派,这样的方法调用也被称为虚方法调用
java中的多态重载示例:
public class StaticDispatch { 
    static abstract class Human { }
    
    static class Man extends Human { }
    
    static class Woman extends Human { }
    
    public void sayHello(Human guy) { 
        System.out.println("hello,guy!"); 
    }
    
    public void sayHello(Man guy) { 
        System.out.println("hello,gentleman!); 
    }
    
    public void sayHello(Woman guy) { 
        System.out.println("hello,lady!"); 
    }
    
    public static void main(String[] args) { 
        Human man = new Man(); 
        Human woman = new Woman(); 
        StaticDispatch sr = new StaticDispatch(); 
        sr.sayHello(man); 
        sr.sayHello(woman); 
    }
}
//结果
hello,guy!
hello,guy!
分析:
  1. 代码中故意定义了两个静态类型相同,而实际类型不 同的变量,但虚拟机(或者准确地说是编译器)在重载时是通过参数的静态类型而不是实际类型作为 判定依据的。
  2. 由于静态类型在编译期可知,所以在编译阶段,Javac编译器就根据参数的静态类型决定 了会使用哪个重载版本,因此选择了sayHello(Human)作为调用目标,并把这个方法的符号引用写到 main()方法里的两条invokevirtual指令的参数中。
  3. 所有依赖静态类型来决定方法执行版本的分派动作,都称为静态分派。静态分派的最典型应用表现就是方法重载。

多态特性总结:

  1. 方法重写,虚拟机在运行阶段根据实际类型进行方法调用,字节码指令一般是invokeVirtual或者invokeinterface,称为动态分派;
  2. 方法重载,在编译阶段编译器根据参数的静态类型进行方法版本选择,称为静态分派;
  3. 方法的版本选择还有另一种形式——解析,解析调用一定是个静态的过程,在编译期间就完全确定,在类加载的解析阶段就会把涉及的符号引用全部转变为明确的直接引用,不必延迟到运行期再去完成。符合这一类要求的方法有5种:静态方法、私有方法、实例构造器、父类方法、final修饰的方法。一般使用invokespecial和invokestatic字节码指令
  4. 在日常开发中,多态是指子类可以替换父类,在运行时调用子类的方法实现。多态可以提高代码的扩展性和复用性,是很多设计模式、设计原则、编程技巧的代码实现基础。也是面向对象编程的重要能力特性之一。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值