1.下面代码执行顺序(直接由程序入手)
public class javaTest2 {
//父静态初始化块
static {
System.out.println("parent 静态代码块");
}
//父初始化块
{
System.out.println("parent 代码块");
}
//父构造器
public javaTest2(){
System.out.println("parent 构造函数");
}
}
class JavaTest2Son extends javaTest2{
//子静态初始化块
static {
System.out.println("son 静态代码块");
}
//子初始化块
{
System.out.println("son 代码块");
}
//子构造器
public JavaTest2Son(){
System.out.println("son 构造函数");
}
//子普通方法
public void sonMethod(){
{
System.out.println("son 普通方法执行代码块1");
}
System.out.println("son 普通方法执行逻辑");
{
System.out.println("son 普通方法执行代码块2");
}
}
public static void main(String[] args) {
System.out.println("main 线程执行");
JavaTest2Son javaTest2Son = new JavaTest2Son();
javaTest2Son.sonMethod();
System.out.println("main 线程执行2");
}
}
2.执行结果:
parent 静态代码块
son 静态代码块
main 线程执行1
parent 代码块
parent 构造函数
son 代码块
son 构造函数
son 普通方法执行代码块1
son 普通方法执行逻辑
son 普通方法执行代码块2
main 线程执行2
3.分析
- 上面的代码第一次看也有点炸毛,不过听笔者分析一下,你就懂了,而且不仅让你懂,还知道为什么。
- 分析代码:
3.1. 首先上面有两个类,javaTest2 类和JavaTest2Son类,看名字可以知道,JavaTest2Son继承了javaTest2 类。
3.2. javaTest2类中有三部分,也就是我们要讨论的三个,静态初始化块,初始化块和构造器。
3.3. javaTest2Son类中相似,只是多了一个普通方法。
3.4. mian()方法中在创建javaTest2对象的前后分别进行打印 - 分析结果
3.5. 静态代码块优先执行,且由于继承继承关系,先执行父类再执行子类
3.6. 之后执行程序入口,按照顺序执行mian的第一个打印方法,再执行对象里面的各种操作,再执行mian第二个打印方法。
3.7. 重点就是创建对象后执行的方法顺序,会发现,父类先执行,子类后执行。
3.8. 在父类与子类中,又是先执行代码块,再执行构造方法。
3.9. 为什么不说普通方法?因为前面是对象初始化的时候的,而普通方法是在调用对象的方法的时候执行的,顺序就不言而喻了,毕竟没盖好房子怎么住人? -
根据分析可得:
分析结果
4.结论
1.java源码的执行流程
①.编译:jvm虚拟机先将我们的源码编译成Class文件。分成三个步骤:
1.分析和输入到符号表;
2.注解处理;
3.语义分析和生成Class文件
②.类加载:之后将Class文件加载到jvm内存中去,分成三个步骤:
1.装载:查找和导入类或接口的二进制数据;
2.链接:分成三步:
2.1.验证:检查导入类或接口的二进制数据的正确性;
2.2.准备:给类的静态变量分配并初始化存储空间,分配默认值
2.3.解析:为类和方法等定位直接引用。完成内存结构布局
3.初始化:为静态变量赋值(用户规定的),执行静态代码块中的内容,这些被static修饰的静态代码块和静态变量在编译时被放入类/接口初始化方法中,在本步骤覆盖准备阶段的默认初始值。
2.为什么静态初始化块在函数执行前执行?
因为静态初始化块在类加载的过程就被执行了,而且在类加载过程中jvm会保证父类先被加载,再加载子类,因此静态初始化块又被称为类初始化块,是与类相关的操作,对类进行初始化处理时执行的。而且类初始化块只执行一次。当再次使用该类创建实例的时候,就不会执行静态初始化块了
3.初始化块与构造器有什么关系?
3.1 相似点:
①.两者都是在创建对象实例时,对该对象进行初始化操作,所以两者都是作用于对象级别的,负责对对象进行初始化。
②.一个类中可以包含多个构造方法(重载),还可以包含多个初始化块。但是每创建一个实例,只能执行一个构造方法,但是所有的初始化块都会执行(根据定义的顺序)。
③.初始化块和构造方法中都可以执行定义局部变量,调用其它方法使用分支循环等操作。
3.2 不同点:
①.初始化块只能在创建对象的时,隐式执行,而且在构造器之前执行。
②.初始化块会对所有对象进行相同的处理,而构造器可以根据使用不同重载方法来实现初始化的不同。
③.初始化块主要作用是,对于多个重载构造器都需要执行的代码,且这部分代码不接受任何参数进行提取,使代码更简洁。
4.为什么初始化块在构造器之前执行?
实质上初始化块仅仅是java中,对于我们看见的源码而言有所区别,其核心实质就是构造器的一部分。在java源码被编译后,java中的所有初始化块都会消失,且初始化块中的代码会加入到该java类的构造器中,且位于构造器所有代码的前面。
5.写在方法中的初始化块有什么用?
答案是没什么软用,你就当它不存在,按照代码的逻辑顺序执行就可以。要说唯一的价值可能就是,烟雾弹迷糊你。但是有一点,静态初始化块不能写在方法中,因为静态的是类级别的操作,对象方法级别不够。只有初始化块可以写,虽然没软用
5.扩展
5.1.问题
- 初始化块,声明实例变量(类的成员变量都是对象级别的)指定的默认值都是对对象的初始化,那么若声明的一个变量且初始化了,再使用初始化块对其进行初始化,会执行哪个结果?
public class JavaTest3 {
//先执行初始化块的初始化,再执行声明实例变量的默认初始化
{
a = 6;
}
int a = 3;
//先执行声明实例变量的默认初始化,再执行初始化块的初始化
int b = 3;
{
b = 6;
}
//先执行声明实例变量的默认初始化(或初始化块的初始化),最后在构造器中进行赋值
int c = 5;
public JavaTest3(int c){
this.c = c;
}
public JavaTest3(){
}
public static void main(String[] args) {
JavaTest3 javaTest3 = new JavaTest3(33);
System.out.println("a:"+javaTest3.a);
System.out.println("b:"+javaTest3.b);
System.out.println("c:"+javaTest3.c);
}
}
5.2.输出
a:3
b:6
c:33
5.3.结论
- 不管是先对成员变量进行初始化块初始化,还是先进行声明声明变量的默认初始化,都与代码的执行顺序有关系。谁放在前面谁先执行,后面执行再对其进行覆盖。
- 构造器是在两者后面执行的,若在构造器中再赋值变量,则会覆盖之前的初始化值。
- 本质上下面的1和2的方式效果是一样的。
//1.直接声明时初始化(赋值)
int a = 3;
//2.先声明,后在初始化块中初始化(赋值)
int a;
{
a = 3;
}
参考:
- Java虚拟机之代码的编译和类加载
- Java类加载过程
- 《疯狂java讲义》
网友评论