图解java多态的原理

什么是多态

通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状 态。(就像榨汁机,放入不同水果得到不同的果汁)

多态的类型

多态分为两种形式:编译时多态运行时多态

编译时多态:方法的重载(Overload),即在类中定义多个同名的方法,但参数列表不同,编译器根据传入的参数类型、数量、顺序等信息来确定具体调用哪个方法(派生类也能重载父类的方法)。

注意:

1. 方法名必须相同

2. 参数列表必须不同(参数的个数不同、参数的类型不同、类型的次序必须不同)

3. 与返回值类型是否相同无关

4. 编译器在编译代码时,会对实参类型进行推演,根据推演的结果来确定调用哪个方法

运行时多态:方法的重写(Override)和对象的向上转型。

在java中要实现运行时多态,必须要满足如下几个条件,缺一不可:

1. 必须在继承体系下

2. 子类必须要对父类中方法进行重写

3. 通过父类的引用调用重写的方法

重写(override):也称为覆盖。重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程 进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定 于自己的行为。 也就是说子类能够根据需要实现父类的方法。

【方法重写的规则】

1.子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致 被重写的方法返回值类型可以不同,但是必须是具有父子关系的(也叫协变)

2.访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方 法就不能声明为 protected 父类被static、private修饰的方法、构造方法都不能被重写。

3.重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心 将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法 构成重写.
 

多态的原理

        编译时多态的原理:

                         编译时多态:最典型的就是重载(Overload)。

在同一个作用域中不能定义两个相同名称的标识符。比如:方法中不能定义两个名字一样的变量,那为什么类中就 可以定义方法名相同的方法呢?

是经过编译器编译修改过之后来确定方法最终的名字:具体方式:方法名+参数列表

public class Overload1 {
    public static int add(int x, int y){
        return x + y;
    }
    public static double add(double x, double y){
        return x + y;
    }
    public static void main(String[] args) {
        add(1,2);
        add(1.5, 2.5);
    }

}

编译之后得到字节码:

也就是说:

所以虽然函数名相同但是方法最终的名字是不同,也就能区分出来了。

就能通过传入不同的参数得到不同得结果。

C语言之所以不支持重载,是因为  方法最终的名字 只通过函数名来区分,当有在java中构成重载得方法放在C语言中就会重复定义,也就是C语言不能通过编译后最终的名字来区分这些方法,所以就不支持重载

        运行时多态 的原理:

方法的重写(Override)和对象的向上转型。

先看下面的代码:

class Base{
    int ba;
    void test(){
        System.out.println("base");
    }
    void test_base(){
        System.out.println("test_base");
    }
}
//派生类,继承Base类
class Derived extends Base{
    int der;
    //重写父类的方法
    void test(){
        System.out.println("derived");
    }
    void test_derived(){
        System.out.println("test_derived");
    }
}


public class Override {
    //实现多态的方法,用父类接收派生类
    public static void func(Base base){
        base.test();
    }

    public static void main(String[] args) {

        Derived derived=new Derived();
        func(derived);
    }
}

结果:

对应的内存图:

也就是说:

虽然 derived 对象 传进func函数时被隐式转化为Base类型,但是其指向的对象会发生切片

(由Derived类型->Base类型)虽然指向的区域变小了,但是其Klass Pointer 的引用还是指向 Derived的类对象(方法区的类对象,也就是所有实例的图纸)。

当我们调对应方法时会去查类对象中的函数调用表,而函数对应表中test()指向的是重写后的方法,也就得到   派生类test()所对应的结果。

两个重要的点是:

1.对象头的Klass Pointer始终指向的是传入对象的类对象

2.类对象的函数调用表,如果派生类重写了父类的方法,在函数调用表中,派生类方法的引用会覆盖父类方法的引用

  • 40
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值