什么是多态
通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同 的状 态。(就像榨汁机,放入不同水果得到不同的果汁)
多态的类型
多态分为两种形式:编译时多态和运行时多态。
编译时多态:方法的重载(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.类对象的函数调用表,如果派生类重写了父类的方法,在函数调用表中,派生类方法的引用会覆盖父类方法的引用