在上一篇文章《详解Java中的覆写与重载》中介绍了什么是覆写以及重载,如何理解区分这两者的概念还是十分重要的。而谈到覆写和重载又会引入一个概念,那就是多态。多态有两种具体的表现,那就是上面所说的覆写以及重载了。
在说动态绑定和静态绑定之前有必要先说下多态的概念。
多态是同一个行为具有多个不同表现形式或形态的能力。
多态存在的三个条件:
先来看个小栗子:
public class Main {
public static void main(String[] args) {
Base base = new Base ();
Derived derived = new Derived();
print(base, base);
print(base, derived);
print(derived, base);
print(derived, derived);
}
private static void print(Base arg1, Base arg2){
arg1.foo(arg2);
}
}
class Base {
public void foo(Base base){
System.out.println("Base.Base");
}
public void foo(Derived derived){
System.out.println("Base.Derived");
}
}
class Derived extends Base{
@Override
public void foo(Base base){
System.out.println("Derived.Base");
}
@Override
public void foo(Derived derived){
System.out.println("Derived.Derived");
}
}
它的输出结果如下:
Base.Base
Base.Base
Derived.Base
Derived.Base
这是因为在Java中,一个方法的参数在编译阶段常被静态的绑定(前期绑定),于是在print()
方法中,由于参数arg2
的类型是Base
,所以参数arg1
调用的foo()
方法始终是foo(Base base)
。而至于参数arg1
的引用则是根据JVM在运行阶段决定的,这被称为动态绑定(后期绑定)。这里也可以看出多态是针对与方法的。
说起绑定,在Java中,几乎所有的方法都是后期绑定的,在运行时动态绑定方法属于子类还是父类。但对于static方法和final方法则是例外,这是由于声明了static或是final的方法不能被继承,因为在编译阶段就可以确定他们的值。需要说明的是,private声明的方法和成员变量不能被子类继承,所有的private方法都被隐式的指定为final的。
除了final、static、private和构造方法(构造器实际上是static方法,只是该static声明是隐式的)是静态绑定外,其他的方法全部为动态绑定。
再来看看下面这段代码:
public class Main {
public static void main(String[] args) {
Father father = new Son();
System.out.println(father.age);
father.name();
father.age();
}
}
class Father{
public int age = 50;
public static void name(){
System.out.println("Father name");
}
public void age(){
System.out.println("Father age is " + age);
}
}
class Son extends Father{
public int age = 20;
public static void name(){
System.out.println("Son name");
}
@Override
public void age(){
System.out.println("Son age is " + age);
}
}
输出结果如下:
50
Father name
Son age is 20
解释如下:
当执行main方法中的Father father = new Son()
发生了向上转型,在编译期间father就是个Father对象,编译器并不知道father对象是Son类型,这是在运行期间由JVM判断。
通过以上可知成员变量、final、static、构造器都是静态绑定,所以在处理father.age
并不是采用运行时绑定,而是直接静态绑定,即调用father
类中的age
成员变量。
在调用father.name()
时,由于这是个static方法,所以仍然采用静态绑定,仍调用father
类中的name()
方法。
在调用father.age()
时,采用动态绑定,此时father会被解析成它实际的对象,即Son对象,因此实际调用的是Son
类中的age()
方法。
如果我们现在想要调用子类的成员变量age该怎么办呢?最简单的方法就是将成员变量封装成getter形式。
class Father{
public int age = 50;
...
public int getAge(){
return age;
}
}
class Son extends Father{
public int age = 20;
...
@Override
public int getAge() {
return age;
}
}
此时通过father.getAge()
获取到的结果就是子类中的成员变量age。
还是上面这段代码,我们如果去掉子类中重写的age()
方法,那么father.age()
的打印结果会是怎样的呢?
打印出来的结果如下:
Father age is 50
这是由于在进行动态绑定时,JVM会先调用子类重写的age()
方法,如果没有找到,就是向上转型去父类中寻找。
部分代码参考源于:https://blog.csdn.net/lingzhm/article/details/44116091
更多文章请关注我的个人博客:www.zhyocean.cn