java 代码块的意义_一文搞定Java代码块加载顺序

1. 局部代码块

形式:用用 “ { } ” 包围的代码

位置:局部位置(方法内部)

作用:限定变量的生命周期,代码块运行完毕立即释放块内变量,节约内存

调用:调用其所在的方法时执行,并且是按照其在方法中的具体位置来执行

实例代码如下:

class A {

A(){}

public void showInfo() {

System.out.println("hello");

//局部代码块

{

String msg = "A局部代码块运行";

System.out.println(msg);

}

//局部代码块的变量在局部代码块结束即释放,再次引用会报错

// System.out.println(msg);

}

}

public class Test {

public static void main(String[] args) {

A a = new A();

a.showInfo();

}

}

测试结果为:

hello

A局部代码块运行

注意:方法中的局部代码块,一般进行一次性地调用,调用完立刻释放空间,避免在接下来的调用过程中占用栈空间,因为栈空间内存是有限的,方法调用可能会会生成很多局部变量导致栈内存不足,使用局部代码块可以避免这样的情况发生。

2. 构造代码块

形式:用 “ { } ” 包围的代码

位置:类成员的位置,即类中方法之外的位置

作用:把多个构造方法共同的部分提取出来,共用构造代码块

调用:在每次new一个对象时自动调用,优先于构造方法执行,实现对对象的初始化

实例代码如下:

class A {

int value; //成员变量的初始化交给代码块来完成

A(){

System.out.println("A构造方法运行");

}

//构造代码块

{

System.out.println("A构造代码块运行");

value = 123; //初始化value值

}

}

public class Test {

public static void main(String[] args) {

A a = new A();

System.out.println("value = "+a.value);

}

}

测试结果:

A构造代码块运行

A构造方法运行

value = 123

3. 静态代码块

形式:用static关键字修饰并用 “ { } ” 包围的代码

位置:类成员位置,即类中方法之外的位置

作用:对类进行一些初始化 只加载一次,当new多个对象时,只有第一次会调用静态代码块,因为静态代码块是属于类的,所有对象共享一份

调用:加载类并初始化类时调用。

实例代码如下:

class A {

//构造方法

A(){

System.out.println("A构造方法运行");

}

//构造代码块

{

System.out.println("A构造代码块运行");

}

//静态代码块

static {

System.out.println("A静态代码块运行");

}

}

public class Test {

public static void main(String[] args) {

A a1 = new A();

A a2 = new A();

}

}

测试结果:

A静态代码块运行

A构造代码块运行

A构造方法运行

A构造代码块运行

A构造方法运行

注意:静态代码只加载一次,代码块执行顺序:静态代码块 → 构造代码块 → 构造方法

3.1 误区

简单地认为Java静态代码块在类被加载时就会自动执行。证错如下:

class A {

//构造方法

A(){

System.out.println("A构造方法运行");

}

//构造代码块

{

System.out.println("A构造代码块运行");

}

//静态代码块

static {

System.out.println("A静态代码块运行");

}

}

public class Test {

public static void main(String[] args) {

System.out.println(A.class);

}

}

测试结果:

class com.exercise.A //A类被定义在com.exercise包中

并没有输出A类中的静态代码块内容,由此可见,静态代码块不是在类被仅仅加载时调用的。

3.2 正解

如果了解JVM原理,我们知道,一个类的运行分为以下步骤:

装载

连接

初始化

使用

卸载

3.2.1 装载

装载阶段由三个基本动作组成:

通过类型的完全限定名,产生一个代表该类型的二进制数据流

解析这个二进制数据流为方法区内的内部数据结构,创建一个表示该类型的java.lang.Class类的实例

如果一个类装载器在预先装载的时遇到缺失或错误的class文件,它需要等到程序首次主动使用该类时才报告错误。

3.2.2 连接

连接阶段也分为三部分:

验证。确认类型符合Java语言的语义,检查各个类之间的二进制兼容性(比如final的类不用拥有子类等),还需要进行符号引用的验证。

准备。Java虚拟机为类变量分配内存,设置默认初始值。

解析。在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用替换成直接引用。

3.2.3 初始化

类的主动引用时,Java虚拟就会对其初始化,如下六种情况为主动使用:

当创建某个类的新实例时,如通过new或者反射,克隆,反序列化等。

当调用某个类的静态方法时。

当使用某个类或接口的静态字段时。

当调用Java API中的某些反射方法时,如类Class中的方法,或者java.lang.reflect中的类的方法时。

当初始化一个子类时,如果其父类没有被初始化,则会先初始化它的父类。

当虚拟机启动某个被标明为启动类的类(即包含main方法的那个类)。

实际上,static块的执行发生在“初始化”的阶段。故此,只要一个类不止经历了装载,并且经历了初始化阶段,那么JVM就会完成对静态变量的初始化、静态块执行等工作。

下面我们看看执行static块的几种情况:

使用new A()的过程

使用Class.forName("A")的过程,因为这个过程相当于Class.forName("A",true,this.getClass().getClassLoader());

使用Class.forName("A",false,this.getClass().getClassLoader())的过程则不会打印信息,因为false指明了装载类的过程中,不进行初始化。不初始化则不会执行static块。

4. 继承关系的代码块运行顺序

现在设计两个类:A 和 B,并且 B 继承 A 。实例代码如下:

class A {

//构造方法

A(){

System.out.println("A构造方法");

}

//构造代码块

{

System.out.println("A构造代码块");

}

//静态代码块

static {

System.out.println("A静态代码块");

}

}

class B extends A{

//构造方法

B(){

System.out.println("B构造方法");

}

//构造代码块

{

System.out.println("B构造代码块");

}

//静态代码块

static {

System.out.println("B静态代码块");

}

}

public class Test {

public static void main(String[] args) {

B b = new B();

}

}

测试结果:

A静态代码块

B静态代码块

A构造代码块

A构造方法

B构造代码块

B构造方法

分析:首先要知道静态代码块是随着类的加载而加载,而构造代码块和构造方法都是随着对象的创建而加载。

(1)在编译 B.java 时,由于 B 继承 A ,所以先加载了 A 类,因此 A 类的静态代码块首先执行,而后加载 B 类,B类的静态代码块执行。

(2)然后创建 B 的对象,大家都知道构造代码块优先于构造方法执行,这时候问题来了,这时应该先看 B 类的构造方法,B 类里的构造方法里有一句隐式的“super()”首先被执行,所以找到 A 类的构造方法,而 A 类的构造方法中也有一句隐式的“super()”执行(调用Object类的构造方法),但并没有什么返回结果,接下来才是在执行 A 类构造方法的方法体前先执行 A 类的构造代码块(输出”A构造代码块“),然后再执行 A 类构造方法的方法体(输出“A构造方法”),最后又回到 B 类的构造方法中,这时 B 类的super()已经执行完了, 然后在执行 B 类构造方法的方法体前先执行 B 类的构造代码块(输出“B构造代码块”),最后执行 B 类构造方法的方法体(输出“B构造方法”)。

5.阿里面试题

public class B {

public static B b1 = new B();

public static B b2 = new B();

{

System.out.println("构造块");

}

static {

System.out.println("静态块");

}

public static void main(String[] args) {

B b =new B();

}

}

测试结果:

构造块

构造块

静态块

构造块

为什么不是:静态块、构造块、构造块、构造块??

原来是因为静态声明的缘故,把b1,b2也上升到静态位,从而与静态块处于同一优先级,同一优先级就按先后顺序来执行,所以执行顺序是构造对象、构造对象、静态块。

6. 总结

(1)虚拟机在首次加载Java类时,会对静态代码块、静态成员变量、静态方法进行一次初始化。静态代码块只执行一次

(2)构造代码块在每次new对象都会执行

(3)程序运行时,先加载父类(类加载顺序的问题)

(4)按照父子类继承关系进行初始化,先执行父类的初始化

(5)静态方法会被首先加载,但是不会被执行,只有调用的时候才会被执行

无继承关系的初始化顺序:静态代码块/变量 → 非静态代码块/变量 → 构造方法

有继承关系的初始化顺序:父类静态代码块/成员变量 → 子类静态代码块/变量 → 父类非静态代码块/变量 → 父类构造方法 → 子类非静态代码块/变量 → 子类构造方法

补充说明:

无法在静态方法里引用实例变量、也无法调用实例方法,但是可以调用静态变量和静态方法。

无法在静态方法里使用this关键字和super关键字,因为静态方法是属于类级别的,不存在关于对象的操作。

无法在静态方法里声明其他静态变量(类变量只能定义在类中,不能存在于方法中)

无法在静态方法里使用域修饰符来声明变量:public、protected、private,只能使用默认的访问域(这一点同样适用于实例方法)

作者信息

大家好,我是 CoderGeshu,一个热爱生活的程序员,如果这篇文章对您有所帮助,还请大家给点个赞哦 👍👍👍

另外,欢迎大家关注本人同名公众号:

一个人可以走的很快,而一群人可以走的很远……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值