问题引言
代码运行的输出是什么?(类加载及初始化顺序)
public class InitalClassProcess {
public static int salary = getSalary(); //静态变量
private int workAge = getWorkAge(); //非静态变量 私有
static{ //静态代码块
System.out.println("static code block");
}
private static int getSalary(){ //静态方法
System.out.println("static method");
return 1500;
}
public InitalClassProcess(){ //构造方法
System.out.println("construction method");
}
{ //实例代码块
System.out.println("instance code block");
}
private int getWorkAge(){ //非静态方法
System.out.println("non static mehod");
return 10;
}
public static void main(String[] args) {
new InitalClassProcess();
new InitalClassProcess();
System.out.println("main method");
}
}
out:
static method
static code block
non static mehod
instance code block
construction method
1
non static mehod
instance code block
construction method
main method
Process finished with exit code 0
解析:首先将代码加载到内存中,类在初始化的时候首先初始化静态成分。按代码顺序执行,首先初始化静态变量salary,调用静态方法getSalary(),输出static method。
注意静态变量只能引用静态方法,不能引用非静态方法。
// 错误示范
public class InitalClassProcess {
public static int salary = getSalary(); //静态变量
private int getSalary(){ //静态方法
System.out.println("static method");
return 1500;
}
}
java: 无法从静态上下文中引用非静态 方法 getSalary()
而后第二行workAge变量是非静态变量,不进行初始化。继续往下执行静态代码块,输出static code block。再往下没有静态成分,类加载完成
然后实例化该类的两个对象,实例化的时候将非静态成分进行初始化。初始化workAge,调用非静态方法getWorkAge(),输出non static method。执行实例代码块,输出instance code block。最后执行构造方法。
类加载过程
加载(loading)
- 通过一个类的全限定名获取定义此类的二进制字节流。
- 讲这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
- 在java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中数据的访问入口。
链接
链接过程分为三步
- 验证:保证被加载类的正确性(文件格式验证、元数据验证、字节码验证、符号引用验证)
- 准备:为类的静态变量分配内存,并将其初始化为默认值。
class A{ static int a = 10; } //准备阶段会为 a 静态变量分配4个字节空间,并初始化为0
- 解析:将符号引用转化为直接引用(地址)。符号引用为变量名、方法名、类名等。
初始化
class A{
static int a = 10;
static {
System.out.println("静态代码块");
}
}
//初始化阶段会将 a 静态变量赋值为 10;执行静态代码块
这个阶段是有clinit方法实现的。当一个类编译之后,字节码文件会产生一个类构造器方法:<cinit>,cinit方法对静态成分进行初始化,即对a赋值10.
测试
public class CinitTest {
static int height;
static int age = 10;
static {
age = 20;
}
static {
name = "jack";
}
static String name = "rose";
public static void main(String[] args) {
System.out.println(height);
System.out.println(name);
System.out.println(age);
}
}
out:
0
rose
20
height没有赋值,因此在准备阶段开辟空间后默认初始化为0;name在准备阶段开辟空间,首先默认初始化为none,赋值jack,然后赋值rose;age:0->10->20。反编译结果如下,在cinit方法中,首先赋值jack,然后赋值rose,是按顺序执行。
如果静态变量只有定义,没有赋值初始化,那么只有默认值,在cinit方法中不会看到赋值指令。若没有静态变量的直接赋值或者没有静态代码块,那么就不会有cinit方法。
//反编译
// class version 52.0 (52)
// access flags 0x21
public class CinitTest {
// compiled from: CinitTest.java
// access flags 0x8
static I height
// access flags 0x8
static I age
// access flags 0x8
static Ljava/lang/String; name
// access flags 0x1
public <init>()V //init方法
L0
LINENUMBER 1 L0 //第一行
ALOAD 0 //将CinTest 类引用本地变量推送至栈顶
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this LCinitTest; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x8
static <clinit>()V //cinit方法
L0
LINENUMBER 3 L0
BIPUSH 10 //将常量值推送到栈顶
PUTSTATIC CinitTest.age : I
L1
LINENUMBER 6 L1
BIPUSH 20
PUTSTATIC CinitTest.age : I
L2
LINENUMBER 10 L2
LDC "jack"
PUTSTATIC CinitTest.name : Ljava/lang/String;
L3
LINENUMBER 13 L3
LDC "rose"
PUTSTATIC CinitTest.name : Ljava/lang/String;
RETURN
MAXSTACK = 1
MAXLOCALS = 0
}
继承中类的初始化
子类初始化的时候,先初始化父类,然后子类
class Animal1{
static int age = getAge();
static {
System.out.println(1);
}
public static int getAge(){
System.out.println(2);
return 10;
}
}
class Dog1 extends Animal1{
static int dogAge = getDoge();
static {
System.out.println(3);
}
public static int getDoge(){
System.out.println(4);
return 20;
}
}
public class ExtendsCinitTest {
public static void main(String[] args) {
Dog1 a = new Dog1();
}
}
out 2 1 4 3
对象创建和初始化过程
上述代码的反编译结果
class Animal1{
int a = 5;
static int height;
static int age = getAge();
static {
System.out.println(1);
}
public Animal1() {}
public static int getAge(){
System.out.println(2);
return 10;
}
}
public class ExtendsCinitTest {
static int aa = 1;
public static void main(String[] args) {
Animal1 a = new Animal1();
System.out.println(a.height);
}
}
反编译
// class version 52.0 (52)
// access flags 0x20
class Animal1 {
// compiled from: ExtendsCinitTest.java
// access flags 0x0
I a
// access flags 0x8
static I height
// access flags 0x8
static I age
// access flags 0x1
public <init>()V
L0
LINENUMBER 10 L0
ALOAD 0 // 将Animal1 推送至栈顶
INVOKESPECIAL java/lang/Object.<init> ()V //执行父类的方法
L1
LINENUMBER 2 L1
ALOAD 0
/*
当int取值-1~5采用iconst指令,取值-128~127采用bipush指令,取值-32768~32767采用sipush指令,取值-2147483648~2147483647采用 ldc 指令
*/
ICONST_5
PUTFIELD Animal1.a : I
L2
LINENUMBER 10 L2
RETURN
L3
LOCALVARIABLE this LAnimal1; L0 L3 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x9
public static getAge()I
L0
LINENUMBER 13 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ICONST_2
INVOKEVIRTUAL java/io/PrintStream.println (I)V
L1
LINENUMBER 14 L1
BIPUSH 10
IRETURN
MAXSTACK = 2
MAXLOCALS = 0
// access flags 0x8
static <clinit>()V
L0
LINENUMBER 4 L0
INVOKESTATIC Animal1.getAge ()I
PUTSTATIC Animal1.age : I
L1
LINENUMBER 7 L1
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ICONST_1
INVOKEVIRTUAL java/io/PrintStream.println (I)V
L2
LINENUMBER 8 L2
RETURN
MAXSTACK = 2
MAXLOCALS = 0
}
总结:每一个构造方法都会对应一个init方法,每个init方法内部都会先执行父类的init方法
类实例化顺序
类初始化阶段:
- 类初始化阶段底层有cinit方法完成,该方法是有静态变量赋值语句和静态代码块的结合而形成的。
- 静态变量赋值语句和静态代码块的执行顺序有由其本身代码顺序自上而下决定。
- 如果父类存在静态资源的初始化,那么父类会优先子类完成cinit方法。
- 如果一个类没有静态变量的赋值和静态代码块,则不存在cinit方法
对象的创建和初始化阶段
- 对象的创建和初始化阶段,对象空间的开辟是由new指令完成的。对应空间中实例变量的初始化是由init方法完成。
- init方法由实例变量变量赋值语句、实例代码块、构造方法,每个构造方法对应一个init方法。
- init方法中先执行父类init方法,然后子类
类初始化和对象初始化顺序
例1
public class ClassAndInstance {
static int a = getA(); // 静态变量
private static int getA() { //静态方法
System.out.println(1);
return 10;
}
static { //静态代码块
System.out.println(2);
a = 20;
}
int b = getB(); //非静态变量
private int getB() { //非静态方法
System.out.println(3);
return -1;
}
{
System.out.println(4);
b = 40;
}
public ClassAndInstance(){
super(); //object
System.out.println(5);
}
public static void main(String[] args) {
System.out.println(6);
new ClassAndInstance();
}
}
out 1 2 6 3 4 5
cinit方法首先执行,输出1、2;然后执行main方法,6,new一个对象,给对象开辟空间,执行init方法(构造)初始化实例变量,先执行父类object的init方法,然后是本类的实例变量,3,4,最后init方法执行结束,5。
例2
public class ClassAndInstance {
static int a = getA(); // 静态变量
static ClassAndInstance cai = new ClassAndInstance();
private static int getA() { //静态方法
System.out.println(1);
return 10;
}
static { //静态代码块
System.out.println(2);
a = 20;
}
int b = getB(); //非静态变量
private int getB() { //非静态方法
System.out.println(3);
return -1;
}
{
System.out.println(4);
b = 40;
}
public ClassAndInstance(){
super(); //object
System.out.println(5);
}
public static void main(String[] args) {
System.out.println(6);
new ClassAndInstance();
}
}
out 1 3 4 5 2 6 3 4 5
首先cinit方法,静态变量a,输出1;然后创建对象cai,此时执行构造方法,先执行父类的init方法,在执行本类构造方法,初始化实例变量、实例代码块,首先是b,输出3,然后是实例代码块,输出4,结束init方法,输出5,;然后执行main方法,输出6,再实例化对象,输出345.
例3
public class ALiTest {
public static int k = 0;
public static ALiTest t1 = new ALiTest("t1");
public static ALiTest t2 = new ALiTest("t2");
public static int i = print("i");
public static int n = 99;
public int j = print("j");
static {
print("静态块");
}
public ALiTest(String str) {
// super();
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++i;
++n;
}
{
print("构造块");
}
public static int print(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++n;
return ++i;
}
public static void main(String[] args) {
ALiTest t = new ALiTest("init");
}
}
/*
k = 0
i = 0
n = 0
t1:
object.init -> ALiTest.init
j = ? 输出 1:j i=0 n=0 (k=1,i=0,n=0) ->(k=1,i=1,n=1) j=1
21行 输出 2:构造块 i=1 n=1 (k=2,i=2,n=2)
15行 输出 3:t1 i=2 n=2 (k=3,i=3,n=3)
t2:
j = ? 输出 4:j i=3 n=3 (k=4,i=4,n=4) j=4
21行 输出 5:构造块 i=4 n=4 (k=5,i=5,n=5)
15行 输出 6:t2 i=5 n=5 (k=6,i=6,n=6)
cinit方法
i = ? 输出 7:i i=6 n=6 (k=7,i=7,n=7) i = 7
n = 99
10行 输出 8:静态块 i=7 n=99 (k=8,i=8,n=100)
main
j = ? 输出 9:j i=8 n=100 (k=9,i=9,n=101)
21行 输出 10:构造块 i=9 n=101 (k=10,i=10,n=102)
15行 输出 11:init i=10 n=102 (k=11,i=11,n=103)
*/