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,一个热爱生活的程序员,如果这篇文章对您有所帮助,还请大家给点个赞哦 👍👍👍
另外,欢迎大家关注本人同名公众号:CoderGeshu,一个致力于分享编程技术知识的公众号!!
一个人可以走的很快,而一群人可以走的很远……
网友评论