Java的初始化块、静态初始化块、构造函数的执行顺序及用途探究
Java与C++有一个不同之处在于,Java不但有构造函数,还有一个”初始化块“(Initialization Block)的概念。下面探究一下它的执行顺序与可能的用途。
执行顺序
首先定义A, B, C三个类用作测试,其中B继承了A,C又继承了B,并分别给它们加上静态初始化块、非静态初始化块和构造函数,里面都是一句简单的输出。
主类Main里面也如法炮制。
class A {
static {
System.out.println("Static init A.");
} {
System.out.println("Instance init A.");
} A() {
System.out.println("Constructor A.");
}
} class B extends A {
static {
System.out.println("Static init B.");
} {
System.out.println("Instance init B.");
} B() {
System.out.println("Constructor B.");
}
} class C extends B { static {
System.out.println("Static init C.");
} {
System.out.println("Instance init C.");
} C() {
System.out.println("Constructor C.");
}
} public class Main { static {
System.out.println("Static init Main.");
} {
System.out.println("Instance init Main.");
} public Main() {
System.out.println("Constructor Main.");
} public static void main(String[] args) {
C c = new C();
//B b = new B();
}
}
测试代码
当然这里不使用内部类,因为内部类不能使用静态的定义;而用静态内部类就失去了一般性。
那么可以看到,当程序进入了main函数,并创建了一个类C的对象之后,输出是这样子的:
Static init Main.
Static init A.
Static init B.
Static init C.
Instance init A.
Constructor A.
Instance init B.
Constructor B.
Instance init C.
Constructor C.
观察上面的输出,可以观察到两个有趣的现象:
- Main类是肯定没有被实例化过的,但是由于执行main入口函数用到了Main类,于是static初始化块也被执行了;
- 所有的静态初始化块都优先执行,其次才是非静态的初始化块和构造函数,它们的执行顺序是:
- 父类的静态初始化块
- 子类的静态初始化块
- 父类的初始化块
- 父类的构造函数
- 子类的初始化块
- 子类的构造函数
那么如果有多个实例化对象,又会不会发生变化呢?于是在第一个C类的对象后面,再实例化一个B类的对象,再观察输出:
Static init Main.
Static init A.
Static init B.
Static init C.
Instance init A.
Constructor A.
Instance init B.
Constructor B.
Instance init C.
Constructor C.
Instance init A.
Constructor A.
Instance init B.
Constructor B.
可以发现这输出跟前面的基本长得一样对吧?只是在后面多了4行,那是新的B类对象实例化时产生的信息,同样也是父类A的初始化块和构造函数先执行,再轮到子类B的初始化块和构造函数执行;同时还发现,静态初始化块的输出只出现了一次,也就是说每个类的静态初始化块都只在第一次实例化该类对象时执行一次。
无论如何,初始化块和构造函数总在一起执行是件有趣的事情,让我们反编译一下看看吧!
查看生成目录发现已经生成了4个.class文件,分别是A.class, B.class, C.class, Main.class,先看看Main.class的结构(这里重新注释了new B):
javap -c Main
Compiled from "Main.java"
public class Main {
public Main();
Code:
: aload_0
: invokespecial # // Method java/lang/Object."<init>":()V
: getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
: ldc # // String Instance init Main.
: invokevirtual # // Method java/io/PrintStream.println:(Ljava/lang/String;)V
: getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
: ldc # // String Constructor Main.
: invokevirtual # // Method java/io/PrintStream.println:(Ljava/lang/String;)V
: return public static void main(java.lang.String[]);
Code:
: new # // class C
: dup
: invokespecial # // Method C."<init>":()V
: astore_1
: return static {};
Code:
: getstatic # // Field java/lang/System.out:Ljava/io/PrintStream;
: ldc # // String Static init Main.
: invokevirtual # // Method java/io/PrintStream.println:(Ljava/lang/String;)V
: return
}
Main.class的反编译结果
可以看到整个Main类被分成三个部分,static {}部分很显然,就是我们的static初始化块,在里面调用了println并输出了String“Static init Main.”;而main入口函数也很清晰,首先新实例化了一个类C的对象,然后调用了类C的构造函数,最后返回;而上面public Main();的部分就很有意思了,这是类Main的构造函数,但我们看到里面调用了两次println,分别输出了String“Instance init Main.”和String“Constructor Main.”。难道初始化块和构造函数被合并到一起了?
我们再看看C类的反编译结果吧:
javap -c C
Compiled from "Main.java"
class C extends B {
C();
Code:
0: aload_0
1: invokespecial #1 // Method B."<init>":()V
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String Instance init C.
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #5 // String Constructor C.
17: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: return static {};
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String Static init C.
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
C.class的反编译结果
静态初始化块仍然单独分出一部分,输出了我们的调试语句。而另一部分,仍然还是类C的构造函数C();,可以看到它先调用了父类B的构造函数,接着输出了我们初始化块中的语句,然后才输出我们写在构造函数中的语句,最后返回。多次试验也都是如此。于是我们能够推断:初始化块的代码是被加入到子类构造函数的前面,父类初始化的后面了。
可能的用途:
既然执行顺序和大概原理都摸清了,那么就要探讨一下初始化块的可能的用途。
静态初始化块
1. 用于初始化静态成员变量
比如给类C增加一个静态成员变量sub,我们在static块里面给它赋值为5:
class C extends B { static public int a; static {
a = 5;
System.out.println("Static init C.");
} ...... }
main函数里输出这个静态变量C.sub:
public static void main(String[] args) {
System.out.println("Value of C.sub: " + C.sub);
}
则输出结果:
Static init Main.
Static init A.
Static init B.
Static init C.
Value of C.sub: 5
符合类被第一次加载时执行静态初始化块的结论,且C.sub被正确赋值为5并输出了出来。
但是乍一看似乎没有什么用,因为静态成员变量在定义时就可以顺便赋值了。因此在赋值方面有点鸡肋。
2. 执行初始化代码
比如可以记录第一次访问类的日志,或方便单例模式的初始化等。对于单例模式,可以先用static块初始化一些可能还被其他类访问的基础参数,等到真正需要加载大量资源的时候(getInstance)再构造单体,在构造函数中加载资源。
非静态初始化块
这个就没什么好说的了,基本跟构造函数一个功能,但比构造函数先执行。最常见的用法应该还是代码复用,即多个重载构造函数都有若干段相同的代码,那么可以把这些重复的代码拉出来放到初始化块中,但仍然要注意它的执行顺序,对顺序有严格要求的初始化代码就不适合使用了。
总结:
- 静态初始化块的优先级最高,也就是最先执行,并且仅在类第一次被加载时执行;
- 非静态初始化块和构造函数后执行,并且在每次生成对象时执行一次;
- 非静态初始化块的代码会在类构造函数之前执行。因此若要使用,应当养成把初始化块写在构造函数之前的习惯,便于调试;
- 静态初始化块既可以用于初始化静态成员变量,也可以执行初始化代码;
- 非静态初始化块可以针对多个重载构造函数进行代码复用。
本文基于知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议发布,欢迎引用、转载或演绎,但是必须保留本文的署名BlackStorm以及本文链接http://www.cnblogs.com/BlackStorm/p/5699965.html,且未经许可不能用于商业目的。如有疑问或授权协商请与我联系。
Java的初始化块、静态初始化块、构造函数的执行顺序及用途探究的更多相关文章
- Java基础 静态块、非静态块、构造函数的执行顺序
Java中经常有一些静态块,这是用来在生成类之前进行的初始化,无论java还C++语言中的static,都是最先初始化好的.结构如下: static { 静态语句代码块 } { 非静态语句代码块 } ...
- 关于java中构造方法、实例初始化、静态初始化执行顺序
在Java笔试中,构造方法.实例初始化.静态初始化执行顺序,是一个经常被考察的知识点. 像下面的这道题(刚刚刷题做到,虽然做对了,但是还是想整理一下) 运行下面的代码,输出的结果是... class ...
- C# 父子类_实例_静态成员变量_构造函数的执行顺序
今天去面试的时候被一道题问得一点脾气都没有,今天特地来研究下. 子类成员变量,子类静态成员变量,子类构造函数,父类成员变量,父类静态成员变量,父类构造函数的执行顺序. 现在贴上从另外一个.net程序员 ...
- java基础课程笔记 static 主函数 静态工具类 classpath java文档注释 静态代码块 对象初始化过程 设计模式 继承 子父类中的函数 继承中的构造函数 对象转型 多态 封装 抽象类 final 接口 包 jar包
Static那些事儿 Static关键字 被static修饰的变量成为静态变量(类变量) 作用:是一个修饰符,用于修饰成员(成员变量,成员方法) 1.被static修饰后的成员变量只有一份 2.当成员 ...
- JavaEE初始化时静态代码块加载问题
1.使用java.exe命令运行某个类的时java.exe Person2.创建一个类的对象时Person p=new Person();3.访问类中的静态成员变量(赋值/获取值)System.out ...
- 非静态代码块(非static初始化块)&静态代码块(static初始化块)
非静态代码块: TestOrder: package com.aff.singleton; /* 类的第四个成员:初始化块(代码块) 代码块: 如果有修饰的话只能使用static 分类:非静态代码块: ...
- Java子父类间静态代码块、非静态代码块、构造方法的执行顺序
子类A继承父类B,A a=new A(); 正确的执行顺序是:父类B静态代码块->子类A静态代码块->父类B非静态代码块->父类B构造函数->子类A非静态代码块->子类A ...
- java基础-Map的静态初始化以及Map的遍历等.....................
1.map的静态初始化,以及map遍历的几种方法: package com.cy.test; import java.util.HashMap; import java.util.Iterator; ...
- java中父类子类静态代码块、构造代码块执行顺序
父类静态(代码块,变量赋值二者按顺序执行) 子类静态 父类构造代码块 父类构造方法 子类构造代码块 子类构造方法 普通方法在实列调用的时候执行,肯定位于上面之后了 //父类A public class ...
随机推荐
- jquery.fn.extend与jquery.extend--(初体验二)
1.jquery.extend(object); 为扩展jQuery类本身.为类添加新的方法. jquery.fn.extend(object);给jQuery对象添加方法. $.extend({ a ...
- Hadoop Shell命令大全
hadoop支持命令行操作HDFS文件系统,并且支持shell-like命令与HDFS文件系统交互,对于大多数程序猿/媛来说,shell-like命令行操作都是比较熟悉的,其实这也是Hadoop的极大 ...
- 利用C#开发移动跨平台Hybrid App(一):从Native端聊Hybrid的实现
0x00 前言 前一段时间分别读了两篇博客,分别是叶小钗兄的<浅谈Hybrid技术的设计与实现>以及徐磊哥的<从技术经理的角度算一算,如何可以多快好省的做个app>.受到了很多 ...
- ASP.NET Core 中文文档 第三章 原理(4)路由
原文:Routing 作者:Ryan Nowak.Steve Smith. Rick Anderson 翻译:张仁建(Stoneqiu) 校对:许登洋(Seay).谢炀(kiler398).孟帅洋(书 ...
- shell 带签名请求,yii 处理带签名的请求
处理请求 class TestController extends Controller { public function init() { if(!YII_ENV_DEV){ throw new ...
- PHP如何实现网址伪静态
Apache的 mod_rewrite是比较强大的,在进行网站建设时,可以通过这个模块来实现伪静态. 主要步骤如下: 1.检测Apache是否开启mod_rewrite功能 可以通过php提供 ...
- Delphi_03_Delphi_Object_Pascal_基本语法_01
这次是一个基本语法的第一部分,包括变量.变量初始化.常量.运算符.字符串等内容. { 本程序演示 Delphi Pascal 的基本语法 1.变量及变量的初始化 2.常量 3.运算符 3. 4. } ...
- c语言实现输入一组数自动从大到小排列
#include <stdio.h>main(){ int x; printf("请输入要排序数字个数:"); scanf("%d" ...
- 模拟Bootstrap响应式网格系统
Bootstrap响应式(适应于不同的终端设备).Bootstrap栅格系统是利用百分比把视口等分为12个,然后利用媒体查询,设置float属性使之并列显示 一.媒体查询 媒体查询包含一个可选的媒体类 ...
- Egret Wiing3快捷键
删除当前行 ( Ctrl+Shift+k ),EgretWing2.5下为 Ctrl+D 折叠 ( Ctrl+Shift+[ ) 展开 ( Ctrl+Shift+] ) Ctrl+Shift+P呼出面 ...