Java中class的初始化顺序
由于Java 中的一切东西都是对象,所以许多活动 变得更加简单,这个问题便是其中的一例。
除非真的需要代码,否则那个文件是不会载入的。通常,我们可认为除非那个类的一个对象构造完毕, 否则代码不会真的载入。由于static 方法存在一些细微的歧义,所以也能认为“类代码在首次使用的时候载入”。 首次使用的地方也是static 初始化发生的地方。装载的时候,所有static 对象和static 代码块都会按照本 来的顺序初始化(亦即它们在类定义代码里写入的顺序)。当然,static 数据只会初始化一次。
简要的说就是,在类有继承关系时,类加载器上溯造型,进行相关类的加载工作。
比如:
Class B extends Class A
当我们new B()时,类加载器自动加载A的代码
class的初始化顺序
通常是以下这样的初始化顺序:
(static对象和static代码块,依据他们的顺序进行初始化)->成员变量和代码块(依据他们的顺序进行初始化)->构造函数
例如:
package cn.d; public class ClassInit { public static void main(String[] args) { new B(); System.out.println("------------"); new B(); } } class A { static { System.out.println("A的static代码块...");// 1 } { System.out.println("A的代码块...");// 1 } public String s1 = prtString("A的成员变量..."); public static String s2 = prtString("A的static变量...");// 2 public A() { System.out.println("A的构造函数..."); } public static String prtString(String str) { System.out.println(str); return null; } } class B extends A { public String ss1 = prtString("B的成员变量..."); { System.out.println("B的代码块..."); } public static String ss2 = prtString("B的static变量...");// 3. public B() { System.out.println("B的构造函数..."); } private static A a = new A();// 4. static { System.out.println("B的static代码块..."); } }
结果:
A的static代码块...
A的static变量...
B的static变量...
A的代码块...
A的成员变量...
A的构造函数...
B的static代码块...
A的代码块...
A的成员变量...
A的构造函数...
B的成员变量...
B的代码块...
B的构造函数...
------------
A的代码块...
A的成员变量...
A的构造函数...
B的成员变量...
B的代码块...
B的构造函数...
解释:
1. 首先加载A的静态代码快和静态变量,由于A中静态代码块刈写在前面,因此先加载静态代码块后加载静态变量。
2. 然后加载B的静态代码快和静态成员变量,由于B中静态变量在前面所以先加载B的静态变量,当执行到 private static A a = new A();的时候会先加载A的成员变量再执行A的构造函数。最后执行B的静态代码块
3. 接下来加载成员变量、代码块和构造函数,先加载父类的成员变量、代码块和构造函数,然后加载子类的成员变量,子类的代码块,子类的构造函数。(成员变量和代码块优先级高于构造函数,成员变量和代码块是按照顺序加载)
4. 静态的东西只会在类加载器加载类的时候初始化,当JVM的方法区已经加载该类的时候不会再次加载静态信息
5. 调用子类的构造方法会调用父类的构造方法。
总结:
1.先静后动,先父后子
2.静态代码块和静态成员变量>代码块和成员变量>构造函数
3.静态代码块和静态成员变量、代码块和成员变量是按照代码中定义顺序进行加载。
我们可以查看上面代码编译后的结果
A.class
import java.io.PrintStream; class A { public String s1; static { System.out.println("A的static代码块..."); } public static String s2 = prtString("A的static变量..."); public A() { System.out.println("A的代码块..."); this.s1 = prtString("A的成员变量..."); System.out.println("A的构造函数..."); } public static String prtString(String paramString) { System.out.println(paramString); return null; } }
B.class
import java.io.PrintStream; class B extends A { public String ss1 = prtString("B的成员变量..."); public static String ss2 = prtString("B的static变量..."); public B() { System.out.println("B的代码块..."); System.out.println("B的构造函数..."); } private static A a = new A(); static { System.out.println("B的static代码块..."); } }
补充:Java编译器会在编译的时候做一些优化,有时候我们可能考虑顺序问题,比如:
public class TestClass { static{ s = "ssssssssssssssss"; } private static String s; public static void main(String[] args) { System.out.println(s); } }
编译后代码:
import java.io.PrintStream; public class TestClass { private static String s = "ssssssssssssssss"; public static void main(String[] paramArrayOfString) { System.out.println(s); } }
补充:关于子类对象中调用父类构造方法的原因-------------这不是创建两个对象,仅创建了一个子对象。父类的构造函数被调用是考虑到其可能有私有的属性需要通过自身的构造函数初始化
在子类的构造函数(constructor)中super()
必须被首先调用,如果super()
没有被调用,则编译器将在构造函数(constructor)的第一行插入对super()
的调用。这就是为什么当创建一个子类的对象时会调用父类的构造函数(constructor)的原因。
如果父类定义了自己的有参构造方法,而且没有定义无参构造方法,则子类必须在构造方法第一行显示调用父类的有参构造方法。否则编译不通过。
如果父类既有无参构造也有有参构造,子类可以不显示调用,编译后会自动调用无参构造方法。
Java对上面的限制也满足子类的对象是父类的对象。
简单的说: 子类的构造函数必须引用父类的构造函数,由程序猿显示调用或由编译器隐式调用,对于这两种方式,被引用的父类构造函数必须已被定义。
继承的基本概念:
(1)Java不支持多继承,也就是说子类至多只能有一个父类。
(2)子类继承了其父类中不是私有的成员变量和成员方法,作为自己的成员变量和方法。
(3)子类中定义的成员变量和父类中定义的成员变量相同时,则父类中的成员变量不能被继承。
(4)子类中定义的成员方法,并且这个方法的名字返回类型,以及参数个数和类型与父类的某个成员方法完全相同,则父类的成员方法不能被继承。
可以简单的理解为创建子类的时候调用父类的构造方法是父类成员变量在子类空间中初始化。(我们可以称此为父类子对象)
相同的方法会被重写,变量没有重写之说,如果子类声明了跟父类一样的变量,那意味着子类将有两个相同名称的变量。一个存放在子类实例对象中,一个存放在父类子对象中。父类的private变量,也会被继承并且初始化在子类父对象中,只不过对外不可见。
super关键字在java中的作用是使被屏蔽的成员变量或者成员方法变为可见,或者说用来引用被屏蔽的成员变量或成员方法,super只是记录在对象内部的父类特征(属性和方法)的一个引用。啥叫被屏蔽的成员变量或成员方法?就是被子类重写了的方法和定义了跟父类相同的成员变量,由于不能被继承,所以就称作被屏蔽。(所以super关键字只能在子类中使用)
关于继承的内存分配可以参考:https://blog.csdn.net/xiaochuhe_lx/article/details/9126445