Java多态的实现机制,(静态代码块,代码块,成员变量,构造方法加载执行顺序)
Java提供了 编译时多态 和 运行时多态 两种多态机制。前者是通 过方法重载 实现的,后者是通过 方法的覆盖 实现的
在方法覆盖中,子类可以覆盖父类的方法,因此同类的方法会在父类与子类中有着不同的表现形式。
在Java语言中,基类的引用变量不仅可以指向基类的实例对象,也可以指向其子类中的实例对象。同样,接口中的引用变量也可以指向其实现类的实例对象。而程序调用的方法在运行时期才动态绑定(绑定是指将一个方法调用和一个方法主体联系在一起),绑定的是引用变量所指向的具体实例对象的方法,也就是内存中正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。通过这种动态绑定实现了多态。由于只有在运行时才能确定调用哪个方法,因此通过方法覆盖实现的多态也可以被称为运行时多态。
示例一:
1 public class Base {
2 public Base(){
3 g();
4 }
5
6 public void g() {
7 System.out.println("Base g()");
8 }
9
10 public void f() {
11 System.out.println("Base f()");
12 }
13 }
1 public class Derived extends Base{
2
3 public void g() {
4 System.out.println("Derived g()");
5 }
6
7 public void f() {
8 System.out.println("Derived f()");
9 }
10
11 public static void main(String[] args) {
12 Base base=new Derived();
13 base.f();
14 base.g();
15 }
16 }
程序运行结果:

在上面的例子中,由于子类Derived的f()方法和g()方法与父类Base方法同名,因此Derived的方法会覆盖Base的方法。在执行 Base base=new Derived(); 语句时,会调用Base类的构造函数,而在Base的构造函数中,执行了g()方法,由于Java语言的多态性,此时会调用子类Derived的g()方法,而不是父类Base 的g()方法,因此会输出"Derived g()".由于实际创建的是Derived对象,后面的方法调用都会调用子类Derived的方法。
但要注意,若此时父类中没有f()方法和g()方法,会编译报错。
示例二:
1 package Test;
2
3 public class Base {
4 private String baseName="base";
5 public Base(){
6 callName();
7 }
8
9 public void callName(){
10 System.out.println(baseName);
11 }
12
13 static class Sub extends Base{
14 private String baseName="sub";
15 public void callName(){
16 System.out.println(baseName);
17 }
18 }
19
20 public static void main(String[] args) {
21 Base base=new Sub();
22 }
23 }
程序运行结果:

在上面的例子中,new Sub();在创造派生类的过程中首先创建基类对象,然后才能创建派生类。创建基类即默认调用Base()方法,在方法中调用callName()方法,由于派生类中存在此方法,则被调用的callName()方法是派生类中的方法,此时派生类中普通成员变量(private String baseName="sub";)还未构造,所以变量baseName的值为null。
如果对类的执行过程还不够了解的话我们来看下面这个例子:
示例三:
1 public class TestDuoTai { 2 private String baseName="base"; 3 public TestDuoTai(){ 4 System.out.println(this.baseName); 5 callName(); 6 } 7 8 public void callName(){ 9 System.out.println(baseName); 10 } 11 12 static class Sub extends TestDuoTai{ 13 private String baseName="sub"; 14 15 public Sub(){ 16 System.out.println("sub Constructure"); 17 callName(); 18 } 19 20 public void callName(){ 21 System.out.println(baseName); 22 } 23 } 24 25 public static void main(String[] args) { 26 TestDuoTai base=new Sub(); 27 } 28 }
执行结果如下:

这段代码的执行过程如下:
1 public class TestDuoTai { 2 private String baseName="base"; //1 3 public TestDuoTai(){ 4 System.out.println(this.baseName); //2 此时baseName已经初始化,所以输出 "base" 5 callName(); //3 扩展类中存在同名方法,执行扩展类中的方法 6 } 7 8 public void callName(){ 9 System.out.println(baseName); 10 } 11 12 static class Sub extends TestDuoTai{ 13 private String baseName="sub"; //5 父类构造方法执行完成后开始初始化扩展类 14 15 public Sub(){ 16 System.out.println("sub Constructure"); //6 类在构造时会优先构造属性然后才是构造方法,所以在属性构造完成之后开始构造 构造方法 17 callName(); //7 执行本类中的方法 18 } 19 20 public void callName(){ //3 //7 21 System.out.println(baseName); //4 由于此时扩展类中的属性还没有构造,所以为null //8 本类调用 输出"sub" 22 } 23 } 24 25 public static void main(String[] args) { 26 TestDuoTai base=new Sub();//开始 0 //结束 9 27 } 28 }
既然已经把代码写到这个份上了,让我想起了静态代码块和代码块的执行顺序问题,咱们再加个例子,并解析其执行过程
示例四:
public class TestDuoTai { private String baseName="base"; public TestDuoTai(){ System.out.println(this.baseName); callName(); } static{ System.out.println("base static"); } { System.out.println(this.baseName); System.out.println(this.baseName2); } private String baseName2="base2"; public void callName(){ System.out.println(baseName); } static class Sub extends TestDuoTai{ private String baseName="sub"; public Sub(){ System.out.println("sub Constructure"); callName(); System.out.println(this.baseName2); } private String baseName2="sub2"; public void callName(){
System.out.println(baseName); } static{ System.out.println("sub static"); } { System.out.println("sub {}"); } } public static void main(String[] args) { TestDuoTai base=new Sub(); } }
执行结果如下:

执行步骤如下
示例四:执行步骤详解
1 public class TestDuoTai { 2 private String baseName="base"; //5 3 public TestDuoTai(){ 4 System.out.println(this.baseName); //9 5 callName(); //10 6 } 7 static{ 8 System.out.println("base static");//1 9 } 10 { 11 System.out.println(this.baseName); //6 12 System.out.println(this.baseName2); //7 13 } 14 private String baseName2="base2"; //8 15 public void callName(){ 16 System.out.println(baseName); 17 } 18 19 static class Sub extends TestDuoTai{ 20 private String baseName="sub"; //12 21 22 public Sub(){ 23 System.out.println("sub Constructure"); //15 24 callName(); //16 25 System.out.println(this.baseName2); //18 26 } 27 private String baseName2="sub2"; //13 28 public void callName(){ // 29 System.out.println(baseName); //11 , 17 30 } 31 static{ 32 System.out.println("sub static"); //3 33 } 34 { 35 System.out.println("sub {}"); //14 36 } 37 } 38 39 public static void main(String[] args) { 40 TestDuoTai base=new Sub();//0 , 2 , 4 , 19 41 } 42 }
观察:父类中的非静态代码块调用了位于它代码位置之前声明的属性是有值的,但是调用位于它代码位置之后声明的属性是取不到值的,输出了一个 null ,说明在类的构造过程中非静态属性和非静态代码块是按声明位置顺序执行的,而且是先于构造方法漫于静态方法执行的
也就是说如果一个类的构造方法被执行时,他的静态代码块一定已经被执行了, 非静态代码块一定已经被执行了,他的属性一定已经完成了初始化的过程
总结:子类实例化时的步骤如下:
父类静态代码块 --> 子类静态代码块 --> 父类属性和非静态代码块按顺序执行 --> 父类的构造方法 --> 构造方法中若调用了其他方法且如果子类存在同名方法的情况下择执行子类方法(多态中的方法重写,还有一种叫重载) --> 子类的属性和非静态代码按顺序执行 --> 子类的构造方法
此外,只有类的方法才有多态的概念,类的成员变量没有多态的概念。
示例如下:
1 public class Base {
2 public int i=1;
3 }
1 public class Derived extends Base{
2
3 private int i=2;
4
5 public static void main(String[] args) {
6 Base base=new Derived();
7 System.out.println(base.i);
8 }
9 }
程序运行结果:

由此可见,成员变量是无法实现多态的,类的成员变量的值取父类还是子类并不取决于创建对象的类型,而是取决于所定义变量的类型,这是在编译期间确定的。


浙公网安备 33010602011771号